Commit Graph
31 Commits
Author SHA1 Message Date
tao.chen 7b40b23d33 feat(web): multi-terminal tabs per workspace
Frontend wiring for the new multi-shell backend. The bottom panel's
Terminal tab now has a tab strip: one tab per shell (showing
shellId[:8] + an X to close), plus a + button to spawn a new shell.
Tab state lives in workspace-ui-store (shellsByWorkspace +
activeShellIdByWorkspace). Auto-start creates the first shell if the
list is empty.

- web/src/lib/api/process.ts:
  - useShellStart returns ShellInfo on 201
  - useShellStop / useShellRestart take {shellId}
  - useShellStatus(workspaceId, shellId) requires both
  - useShellWebSocket(workspaceId, shellId, enabled) refactored:
    shellId change closes the current socket intentionally and opens
    a new one with ?shellId= appended
  - useShellResize takes {shellId, cols, rows}
  - new useShellList(workspaceId)
- web/src/lib/store/workspace-ui-store.ts: shellsByWorkspace +
  activeShellIdByWorkspace maps + setters (addShellToWorkspace,
  removeShellFromWorkspace, setActiveShell, setShellsForWorkspace)
- web/src/components/terminal/TerminalPanel.tsx: requires shellId
  prop; clears/resets terminal on shellId change (in addition to
  workspaceId); welcome banner shows
  '[connected to <workspaceId>/<shortShellId>]'
- web/src/components/layout/WorkspaceShell.tsx: new TerminalTabs
  component:
  - lists shells as tabs, + to add, x to close
  - useShellList hydrates the store on mount
  - auto-start guard via useRef so we don't double-fire
  - close-pending tracked per shellId via Set
  - renders the active <TerminalPanel workspaceId shellId>

Diff: 4 files. pnpm lint + pnpm build clean.

Conversation: B975103F-4528-493E-8F8A-91D20506631D
2026-07-06 15:48:12 +08:00
tao.chen f5a6ff8b0d feat(shell): PTY resize via HTTP + frontend xterm.onResize hook
The PTY was hardcoded to 80x24 at start, so full-screen programs
(htop, vim, less, tmux, top) drew themselves for 80x24 regardless
of the actual xterm window. Add a path for the frontend to push the
real cols/rows to the backend, which calls pty.Setsize on the pty
file.

Backend:
- internal/shell/manager.go: Resize(workspaceID, cols, rows) added to
  the Manager interface and implemented on LocalManager. Looks up the
  session, type-asserts sess.Stdin.(*os.File), calls pty.Setsize.
  Validates cols/rows in [1, 10000] and returns CodeBadRequest on
  bad input, CodeNotFound when no session.
- internal/service/shell_service.go: ShellService.Resize wrapper that
  checks workspace existence first.
- internal/api/shell_handler.go: resize handler, 204 on success.
- internal/api/router.go: register POST /workspaces/:id/shell/resize.
- internal/model/shell.go: ShellResizeRequest{Cols, Rows}.
- internal/shell/manager_test.go: 3 new tests (valid resize via
  pty.Getsize round-trip, invalid size, missing session).

Frontend:
- web/src/lib/api/process.ts: useShellResize mutation hook.
- web/src/components/terminal/TerminalPanel.tsx: terminal.onResize
  subscription with 100ms debounce; filters 0x0; only fires when
  workspaceId is set; cleanup clears timeout + disposes listener.

E2E: 'stty size' after POST /shell/resize {cols:200, rows:50} returns
'50 200'. 0x40 → 400, missing workspace → 404, valid → 204.

Conversation: 019f360a-5eba-7c81-94d3-e5d58ad3c026
2026-07-06 14:16:09 +08:00
tao.chen 789f62669c fix(acp): group agent chunks by messageId (backend) and coalesce consecutive same-role entries in the UI (frontend)
The opencode acp binary streams agent_message_chunk notifications with
an EMPTY messageId. The backend was already correct (it groups when a
messageId is present), but the UI rendered every chunk as its own
bubble, so a single 'count 1 to 5' reply showed as 9 separate bubbles.

Backend:
- internal/acp/history.go: add MessageID to Message; new AppendOrCreate
  method that finds or creates an entry by messageId and appends text
  to it (preserving the original timestamp).
- internal/acp/client.go: agent_message_chunk and user_message_chunk
  use AppendOrCreate.
- internal/model/acp.go: expose MessageID in the wire response.
- internal/service/acp_service.go: copy MessageID into the API DTO.
- internal/acp/client_test.go: add TestHistoryAppendOrCreateGroupsByMessageID.

Frontend:
- web/src/components/acp/groupMessages.ts: new pure helper that
  coalesces consecutive same-role history entries into a single bubble
  (keeps the first chunk's time + messageId).
- web/src/components/acp/AcpPanel.tsx: render coalesced messages;
  auto-scroll effect now depends on coalesced so it fires as the
  agent streams.

Verified via e2e: 'count from 1 to 5' now returns a single agent entry
with text '1\n2\n3\n4\n5' instead of 9 separate entries.
2026-07-06 13:48:56 +08:00
tao.chen 041c3dc002 fix(web): clear terminal on workspace change; disable Mac Option as Meta
- terminal: on workspaceId change call terminal.clear() and
  terminal.reset() so the previous workspace's scrollback is wiped
  before the new shell's output appears.
- terminal: set macOptionIsMeta: false. With the default (true),
  Mac Option+key sequences (e.g. Option+[ emits ESC) leak a literal
  0x1b into the shell's stdin, which crashes downstream REPLs like
  Python's with "invalid non-printable character U+001B".
2026-07-06 13:39:20 +08:00
tao.chen a633af272b feat(web): add ACP chat panel with tabs (Terminal | Agent)
- web: add react-markdown + remark-gfm + react-syntax-highlighter
- web: new lib/api/acp.ts (useAcpStatus/History/Prompt/Cancel)
- web: new components/acp/ (AcpPanel + MessageBubble + MarkdownView +
  ThinkingIndicator). Markdown rendering with code highlighting via
  Prism oneDark. Auto-scroll to bottom when near it; error toasts
  fade after 5s.
- web: WorkspaceShell now renders a tabbed bottom panel (Terminal |
  Agent) when the bottom panel is visible. Tab state in
  workspace-ui-store.
- web: StatusBar shows agent status (ready / initializing / error)
  when the ACP session is initialized.
- web: pnpm-workspace.yaml: add packages ['.'] so pnpm install works
  in this workspace.

Conversation: 019f3562-9382-7e00-ad92-3162341e9274
2026-07-06 11:30:43 +08:00
tao.chen 18cb3a0151 feat(web): replace workspace native select with shadcn select 2026-07-03 18:48:01 +08:00
tao.chenandClaude c065079d87 feat(web): migrate prompts and confirms to shadcn dialog and dropdown
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 18:38:28 +08:00
tao.chenandClaude 3c6e0c3121 fix(web): move save logic from monaco to react with manual and auto-save
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 18:25:47 +08:00
tao.chenandClaude 7a6e7e7ebb feat(web): replace hover buttons with right-click context menu in file tree
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 17:40:07 +08:00
tao.chenandClaude fcb678773c feat(web): suppress browser ctrl+s and add file tree CRUD
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 17:17:55 +08:00
tao.chenandClaude ac87454711 feat(web): add file tree manual refresh + 5s polling + focus refetch
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 16:57:20 +08:00
tao.chenandClaude 53fe249dd6 feat(web): wire terminal panel to workspace shell ws
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 16:48:49 +08:00
tao.chenandClaude 75e807c154 fix(web): only connect terminal ws when process is running
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 16:15:51 +08:00
tao.chenandClaude 11ac0299a6 fix(web): treat server clean close as session end without reconnect
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 16:07:46 +08:00
tao.chenandClaude 60524bad0e fix(web): show toolbar and status bar when no workspace selected
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 16:00:58 +08:00
tao.chenandClaude 09150c2a7d fix(web): enable websocket proxying in vite dev server
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:47:43 +08:00
tao.chenandClaude 03377eec3c docs: document terminal websocket endpoint
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:36:35 +08:00
tao.chenandClaude d8c8c7fc83 feat(web): wire terminal panel to live process websocket
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:36:12 +08:00
tao.chenandClaude fa5b53939d feat(web): add useProcessWebSocket hook
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:28:42 +08:00
tao.chenandClaude a7be9d3f0c feat: wire OpenCode process control UI
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 11:46:13 +08:00
tao.chenandClaude 58aa6077d8 feat: wire Monaco editor to real file read/write
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 11:27:19 +08:00
tao.chenandClaude d587c94f50 feat: wire file explorer to real backend API
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 11:19:30 +08:00
tao.chenandClaude 867f1d204e feat: wire workspace CRUD UI to backend
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 11:05:56 +08:00
tao.chenandClaude b3d0b63f4b fix: defer xterm fit to ResizeObserver ticks
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 11:03:05 +08:00
tao.chenandClaude b5fababeea docs: add web frontend usage
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 19:38:19 +08:00
tao.chenandClaude 062cde88f8 feat: add web editor and terminal panels
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 18:35:17 +08:00
tao.chenandClaude 0f634981b5 feat: add resizable workspace shell
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 18:15:52 +08:00
tao.chenandClaude 593d2aee5e feat: wire web app infrastructure
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 17:23:48 +08:00
tao.chenandClaude 27503781fa feat: add web ui foundation
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 16:57:37 +08:00
tao.chenandClaude 4039d9fa48 feat: scaffold web frontend
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 16:48:19 +08:00
tao.chenandClaude Fable 5 411ed1f8ba feat: scaffold codespace backend
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:07:54 +08:00