Files
stablyai--orca/docs/reference/direct-url-or-file-entry.md
2026-07-13 13:05:33 +08:00

13 KiB

Direct URL Or File Entry

Problem

The tab bar + menu only offers fixed actions: terminal, browser, new markdown, and open markdown in src/renderer/src/components/tab-bar/TabBar.tsx. It has no text entry point for a user who already knows the URL or file path they want.

Quick Open already loads files for the active worktree through listRuntimeFiles, watches the active SSH target status, excludes nested linked worktrees, ranks via prepareQuickOpenFiles/rankQuickOpenFiles, and opens a selected match with openFile. That flow is modal and file-only. It does not live in the + menu, does not accept URLs, and cannot create a named new file from the typed query.

The runtime file client has the required local/SSH/runtime primitives, with caveats:

  • listRuntimeFiles(context, { rootPath, excludePaths }) returns relative paths only and can fail for auth, missing provider, ripgrep/size, or stale worktree reasons.
  • statRuntimePath(context, absolutePath) is a one-path existence/type check.
  • createRuntimePath(context, absolutePath, 'file') creates a single empty file and creates parent directories on the current local, SSH, and runtime-backed paths. Directory creation has separate no-clobber/recursive semantics elsewhere in the file stack, so v1 should create files directly and not expose directory creation as a separate action.

Goal

Add an entry field at the top of the tab bar + menu so users can type:

  1. A URL to open an Orca browser tab.
  2. An existing file name/path to open an editor tab.
  3. A new relative file path to create in the active worktree and open.

The behavior must work from both the titlebar tab strip and split-group tab strips. File operations must route through RuntimeFileOperationArgs; browser/editor creation must preserve the target group.

Non-goals

  • Do not replace global Quick Open.
  • Do not add persisted launcher state.
  • Do not support standalone directory creation.
  • Do not open external URLs outside Orca.
  • Do not make URL/file detection configurable.

Design

  1. Add a TabBarCreateEntry surface inside TabBar's dropdown content above the fixed rows. Use the existing DropdownMenu shell, but render the input in a plain form/container, not as a DropdownMenuItem; Radix menu item typeahead/selection should not own text input keystrokes. Stop propagation only where required for input typing, and close the dropdown only after a successful submit.

  2. Extend TabBarProps with a presentation callback that resolves success by returning and failure by throwing:

    onOpenEntry?: (args: { query: string; worktreeId: string; groupId: string }) => Promise<void>
    

    TabBar owns input text, pending state, focus, and dropdown close behavior. It does not create browser tabs or files. groupId should be groupId ?? worktreeId, matching the existing resolvedGroupId fallback.

  3. Add a small hook/helper pair instead of duplicating Quick Open logic:

    • useTabEntryFileList loads the file list when the menu opens, using the same inputs as QuickOpen: active worktree path, getConnectionId(worktreeId), nested worktree exclusions, and active SSH target status. It cancels stale requests on close or key changes.
    • A pure classifier/opening helper accepts the query, file-list snapshot, load/error state, worktree metadata, runtime context, and target group.
  4. Wire the callback in both owners:

    • Terminal.tsx titlebar fallback resolves the current active worktree and target group the same way handleNewTab / handleNewBrowserTab do.
    • useTabGroupWorkspaceModel passes its explicit worktreeId and groupId. Do not rely on ambient activeGroupIdByWorktree; split-group + can be invoked from an unfocused group.
  5. Classify submissions in this order:

    • Empty after trim: reject inline.
    • Explicit URL: accept only http:// and https:// URLs with a parseable host.
    • Existing file: once the file-list snapshot is ready, normalize query separators for matching, prefer exact relative-path match, then exact basename match, then rankQuickOpenFiles. Before opening, statRuntimePath the matched absolute path and reject directories/stale missing matches instead of blindly opening stale list entries.
    • Host-like URL: only after there is no existing file match, accept strict bare hosts such as example.com, localhost:3000, or 127.0.0.1:3000, normalized to https://... when no scheme is present. Do not run host-like URL parsing for bare input containing / or \; new URL('https://docs/readme.md') parses, so parsing alone is not a path/file guard. Also reject common source/document filename extensions such as md, ts, tsx, js, jsx, json, yml, yaml, toml, css, html, and py so README.md and src/foo.test.ts stay file/create candidates.
    • New file: only after file listing has completed successfully with no existing-file match and no host-like URL match. Treat the query as a relative worktree path. If listing fails, allow explicit http:// / https:// URLs only; keep bare host-like inputs blocked because they cannot be disambiguated from files.
  6. Validate new file paths before joining:

    • Reject POSIX absolute paths, Windows drive paths, UNC paths, ~, empty path, trailing slash, control characters, . / .. segments, and empty raw segments such as a//b.
    • Normalize \ to / only after absolute-path checks, then run segment validation on the normalized path too so traversal like a\..\b is still rejected.
    • Build the absolute path with joinPath(worktreePath, relativePath), then create with createRuntimePath(context, absolutePath, 'file').
    • On EEXIST / "exists", immediately statRuntimePath; if it is now a file, open it. If it is a directory, show an error. This handles another window/process winning the create race.
  7. Open actions:

    • URL: in paired web clients, call createWebRuntimeSessionBrowserTab({ worktreeId, url, targetGroupId: groupId }); otherwise call createBrowserTab(worktreeId, url, { activate: true, targetGroupId: groupId, title: url }). Do not use openNewBrowserTabInActiveWorkspace; it only opens the default URL.
    • Existing/new file: call openFile(fileInfo, { preview: false, targetGroupId: groupId }) with language: detectLanguage(relativePath). Include the active runtime environment owner as runtimeEnvironmentId through the normal openFile fallback; do not suppress runtime ownership.
  8. Preserve current fixed actions. Existing New Terminal, New Browser Tab, New Markdown, Open Markdown..., and quick-launch rows remain below the entry and keep their current shortcuts/icons.

  9. Surface errors with existing toast or compact inline text. Keep the dropdown open and preserve the query on validation/runtime errors. For Quick Open's special ripgrep guidance, either extract its parser/UI deliberately or show the cleaned error string; do not duplicate a private parser inline.

Data Flow

  • User opens the + menu.
  • TabBarCreateEntry focuses the input and useTabEntryFileList starts/reuses the menu-local file-list request.
  • User types; the menu may show the best existing-file match or "create file" affordance based on the current snapshot.
  • Enter calls onOpenEntry({ query, worktreeId, groupId }).
  • Helper classifies and dispatches:
    • URL -> browser tab creation with target group.
    • Existing file -> stat matched path -> openFile(..., { targetGroupId }).
    • New file -> validate -> createRuntimePath(..., 'file') -> openFile(..., { targetGroupId }).
  • Success closes the dropdown. Failure keeps it open.

Edge Cases

  • No active worktree: disable the entry, including URL entry, because Orca browser tabs are worktree-scoped.
  • SSH/runtime connection not ready: mirror Quick Open by keying the list request on active target status. Non-URL submissions are disabled while loading/connecting to avoid creating a duplicate before the real list arrives.
  • File listing failure: explicit http:// / https:// URL submissions still work; file and bare host-like submissions are blocked with the cleaned list error.
  • Ambiguous host-like/file names: an existing listed file wins over bare host-like URL normalization. Explicit http:// or https:// input is the escape hatch when the user wants a browser tab despite a file-name collision.
  • File list stale because another window/process added or removed a file: stat before opening matched files; handle create EEXIST by stat-and-open.
  • Existing directory match: show an error; do not open as an editor tab.
  • Internal spaces in file paths are allowed. Leading/trailing whitespace is trimmed. Control characters are rejected.
  • Windows-style separators match existing relative paths after normalization, but Windows absolute and UNC paths are rejected for creation.
  • Duplicate Enter while pending is disabled.
  • Browser creation in paired web/mobile clients must use createWebRuntimeSessionBrowserTab; local desktop uses createBrowserTab.
  • External file-watch invalidation is not required for v1. The menu-local list reloads on each open and on worktree/connection/status changes; successful create can close the menu without mutating the list.

Test Plan

  • Unit test URL classification: schemes, host-like domains, localhost/IP ports, listed file named example.com winning over host-like normalization, README.md/readme.md, src/foo.test.ts, docs/readme.md, whitespace, and invalid schemes.
  • Unit test path validation: Windows/POSIX absolute paths, UNC, ~, traversal, empty segments, trailing slash, control characters, spaces, and separator normalization.
  • Unit test existing-file selection with prepareQuickOpenFiles/rankQuickOpenFiles: exact path beats basename, basename beats fuzzy, stale stat failure blocks open, directory stat blocks open.
  • Unit/helper test new-file creation with RuntimeFileOperationArgs, createRuntimePath, statRuntimePath, EEXIST stat-and-open, and SSH/runtime connection context.
  • Component test TabBar: input renders above fixed rows, focuses on open, typing does not close the menu, Enter awaits the callback, success closes, failure preserves text, fixed actions still fire.
  • Store/model tests: titlebar and split-group callbacks pass targetGroupId; URL creation uses createWebRuntimeSessionBrowserTab in web runtime and createBrowserTab otherwise.
  • Electron validation: open URL, open existing file by exact path/name, create nested new file, invalid traversal/absolute path error, and smoke-test existing menu actions.

UI Quality Bar

  • Follow docs/STYLEGUIDE.md and existing DropdownMenu/Input tokens. Do not add custom colors, shadows, or a modal-like panel inside the menu.
  • The input is the first focus target and must not break menu keyboarding.
  • The menu remains compact; loading, match preview, create preview, and error rows fit at the current menu width without overlap or awkward height jumps.
  • Long typed paths scroll/truncate within the input; fixed rows retain icons, shortcuts, hover states, and dense spacing.

Review Screenshots

Attach evidence to the PR conversation; do not commit images.

  1. + menu open in a normal workspace with the entry focused and fixed rows visible.
  2. Typed URL state.
  3. Typed existing-file query with visible best match.
  4. Typed new-file path/create state.
  5. Rejected absolute/traversal path error.
  6. Adjacent-feature smoke: fixed menu rows still visible and aligned.

Rollout

  1. Add classifier/path-validation helper and tests.
  2. Add menu-local file-list hook by extracting the reusable Quick Open loading inputs, not by copying private UI-only parsing.
  3. Add TabBarCreateEntry and wire it into TabBar.
  4. Add titlebar and split-group callbacks.
  5. Add component/store tests.
  6. Run typecheck, lint, targeted tests, UI review, and Electron validation with screenshots.

Lightweight Eng Review

  • Scope: Correctly scoped to the tab bar + menu. The implementation must not mutate Quick Open behavior except for extracting reusable search/listing code.
  • Architecture/data flow: Good if TabBar stays presentational and all worktree/runtime/group decisions live in owners plus a shared helper. The original onOpenEntry(query, groupId?) shape was under-specified; include worktreeId and a resolved groupId.
  • Failure modes: Must explicitly handle loading list, failed list, stale list, directory matches, create races, no active worktree, no SSH provider yet, and paired web runtime browser creation.
  • Feasibility: Specific URL browser tabs cannot go through openNewBrowserTabInActiveWorkspace because that action uses the default URL. createRuntimePath(..., 'file') is feasible and creates parent directories for current local/SSH/runtime paths, but do not expose recursive directory creation as a separate v1 behavior.
  • Concurrency/invalidation: A menu-local file list is acceptable if every matched file is statted before open and EEXIST is handled on create. No global cache is needed.
  • Performance/blast radius: Listing on menu open has Quick Open's cost class, but the menu is more casually opened than the modal; cancel stale loads and key requests tightly by worktree path, connection, exclusions, and SSH status.
  • Tests: Add pure helper tests first; then component and store/model tests. Electron screenshots are required because Radix menu focus/input behavior is the highest-risk UI part.
  • Residual risk: Host-like URL heuristics can surprise users. Keep the heuristic narrow and prefer file matches over host-like normalization whenever the query contains path separators or matches a listed file.