Reorder tabs by dragging, and pop the browser view into its own window
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m31s
Build App / build-linux (pull_request) Successful in 5m35s
Build App / build-windows (pull_request) Successful in 6m9s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m31s
Build App / build-linux (pull_request) Successful in 5m35s
Build App / build-windows (pull_request) Successful in 6m9s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Two things the UI couldn't do: rearrange the tab strip, and watch the browser while working somewhere else. **Drag to reorder.** `moveTab`/`moveActiveTab` on the store, HTML5 drag on the strip with a marker showing where the drop lands, `Ctrl+Shift+←/→` for the same thing without a mouse. Reordering deliberately does not select what it moves, so a drag aimed at a background tab doesn't yank the main area away from a terminal mid-run. A tab being renamed is not draggable — a draggable ancestor swallows the mouse-drag that selects text in its input. **Pop the browser view out.** `browser_view/popout.rs` opens the view's existing token-bearing loopback URL as a second OS window, with a "Keep on top" toggle so it can float above the app. Window-only: the viewer, the proxy and the container are untouched, so popping out and back interrupts nothing. Three things it rests on: - No capability lists that window, so it has no IPC surface — right for a page served out of a container, and it must stay that way. - The app CSP is irrelevant to it: `frame-src` constrains what the app's document may *embed*, and this is a top-level document. The port range and the token gate are what actually protect it, unchanged. - The window is owned by the session, so the supervisor's teardown closes it. A window onto a viewer that no longer exists is worse than none. The pane drops its iframe while popped out — two viewers can both *drive* the browser, and two cursors on one page is not a feature. `lib.rs`'s `on_window_event` is now guarded on `label() == "main"`. It fires for every window and its body stops every container and exits, so without the guard closing a pop-out would quit the app. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,9 @@ docker exec stdout → tokio task → emit("terminal-output-{sessionId}") → li
|
||||
- **`store/appState.ts`** — Single Zustand store for all app state (projects, sessions, UI). The
|
||||
main area is a single ordered tab strip holding two tab kinds, keyed `term:<id>` and
|
||||
`home:<id>`; `activeSessionId` is *derived* from `activeTabKey` so exactly one thing is current.
|
||||
`tabOrder` is user-reorderable (drag, or `Ctrl+Shift+←/→` via `moveActiveTab`) — so **never
|
||||
treat a tab's position as identity**: address tabs by key, and index only through `tabOrder`.
|
||||
`moveTab` deliberately does not activate what it moves.
|
||||
- **`hooks/`** — All Tauri IPC calls are encapsulated in hooks (`useTerminal`, `useProjects`, `useDocker`, `useSettings`)
|
||||
- **`lib/tauri-commands.ts`** — Typed `invoke()` wrappers; TypeScript types in `lib/types.ts` must match Rust models
|
||||
- **`components/terminal/TerminalView.tsx`** — xterm.js integration with WebGL rendering, URL detection for OAuth flow
|
||||
@@ -84,8 +87,10 @@ docker exec stdout → tokio task → emit("terminal-output-{sessionId}") → li
|
||||
Use `--text-disabled` rather than `disabled:opacity-50`.
|
||||
- **Never write `focus:outline-none`.** A global `:focus-visible` ring is defined in `index.css`.
|
||||
- **Status must not be encoded in colour alone** — `StatusIndicator` pairs a glyph with a word.
|
||||
- Keyboard: `Ctrl+T` new terminal, `Ctrl+Shift+W` close tab, `Ctrl+Tab` cycle, `Ctrl+1..9` jump.
|
||||
`Ctrl+W` is intentionally left alone — it is readline's `kill-word` inside the terminal.
|
||||
- Keyboard: `Ctrl+T` new terminal, `Ctrl+Shift+W` close tab, `Ctrl+Tab` cycle, `Ctrl+1..9` jump,
|
||||
`Ctrl+Shift+←/→` move the active tab. `Ctrl+W` is intentionally left alone — it is readline's
|
||||
`kill-word` inside the terminal, and plain `Ctrl+←/→` is its word-wise cursor motion, which is
|
||||
why tab-moving takes Shift.
|
||||
|
||||
### Backend Structure (`app/src-tauri/src/`)
|
||||
|
||||
@@ -104,6 +109,18 @@ docker exec stdout → tokio task → emit("terminal-output-{sessionId}") → li
|
||||
OAuth listener, wrong for remote control of a browser. Host ports are confined to
|
||||
`47820..=47827` because CSP `frame-src` cannot express a port range and must enumerate them;
|
||||
a unit test asserts the Rust range matches `tauri.conf.json`. Opt-in per project.
|
||||
- **`popout.rs` puts the same URL in a second OS window** (`WebviewUrl::External`), so the view
|
||||
can be watched on another monitor or pinned on top while the main window is used for work.
|
||||
Three things it rests on: no capability lists that window, so it has **no IPC surface** — do
|
||||
not give it one; the app CSP does not apply, because it is a top-level document rather than a
|
||||
frame, and the token gate is what protects the port in both cases; and the window is owned by
|
||||
the *session*, so the supervisor's teardown closes it rather than leaving a window onto a
|
||||
viewer that no longer exists. It closes with `destroy()`, never `close()`, to stay clear of
|
||||
`CloseRequested`. The pane drops its iframe while popped out — two viewers can both *drive*
|
||||
the browser.
|
||||
- **`lib.rs`'s `on_window_event` fires for every window and must stay guarded on
|
||||
`label() == "main"`.** Without that guard, closing a pop-out runs the app's shutdown: every
|
||||
container stopped, process exited.
|
||||
- **Detection has to look past `node_modules`.** `claude mcp add … npx @playwright/mcp@latest`
|
||||
installs into `~/.npm/_npx/<hash>/node_modules`, not any `node_modules`, so `detect.rs`
|
||||
globs that cache as well as `/workspace`, `$HOME/node_modules` and `npm root -g`. It also
|
||||
|
||||
Reference in New Issue
Block a user