feat(ui): mode switcher + clickable plan cards + channel-aware plan delivery#67721
feat(ui): mode switcher + clickable plan cards + channel-aware plan delivery#67721100yenadmin wants to merge 18 commits into
Conversation
New components for the webchat control UI: 1. Mode switcher (ui/src/ui/chat/mode-switcher.ts): - Chip/pill in chat toolbar showing current execution mode - Dropdown menu with 4 modes: Ask permissions, Accept edits, Plan mode, Bypass permissions - Each mode maps to existing session fields (execSecurity + execAsk) - Keyboard shortcuts: Ctrl+1 through Ctrl+4 - No new protocol schema — pure UI abstraction 2. Plan cards (ui/src/ui/chat/plan-cards.ts): - Expandable <details>/<summary> cards for plan events in message thread - Step checklist with status markers (pending/in_progress/completed/cancelled) - Accent left-border to distinguish from tool cards - formatPlanAsMarkdown() for sidebar/detail view 3. CSS (plan-cards.css, layout.css additions): - Plan card styles mirroring tool-cards.css patterns - Mode switcher chip + dropdown menu styles
Greptile SummaryAdds a 5-mode permission chip (Default/Ask/Accept/Plan/Bypass) to the chat toolbar, expandable plan cards to the message thread, and a
Confidence Score: 4/5Safe to merge after fixing the four stale test assertions in mode-switcher.test.ts. The component logic in mode-switcher.ts and plan-cards.ts is correct; the only concrete failure is four unit tests that carry pre-Default shortcut numbering and will fail when the suite runs. Those failures block CI and mask real regressions, so they should be fixed before landing. ui/src/ui/chat/mode-switcher.test.ts — four stale assertions need updating to reflect the 5-mode shortcut layout. Prompt To Fix All With AIThis is a comment left during a code review.
Path: ui/src/ui/chat/mode-switcher.test.ts
Line: 44-101
Comment:
**Four tests fail after Default mode was inserted at position 0**
When follow-up v3 added `"default"` (shortcut `"1"`) to `MODE_DEFINITIONS`, all other shortcuts shifted by one: Ask→2, Accept→3, Plan→4, Bypass→5. These four tests still carry the pre-Default numbering and will now fail:
**Line 44–49** — `resolveCurrentMode(undefined, undefined)` now matches the `"default"` preset (its `execSecurity` and `execAsk` are both `undefined`), so `mode.id` is `"default"`, not `"custom"`.
**Line 88–92** — `makeKeyEvent("3")` matches Accept (shortcut `"3"`), so `result?.id` is `"accept"`, not `"plan"`.
**Line 94–96** — `makeKeyEvent("4")` matches Plan (shortcut `"4"`), so `result?.id` is `"plan"`, not `"bypass"`.
**Line 99–101** — `makeKeyEvent("5")` now matches Bypass (shortcut `"5"`), so the result is not `null`.
Suggested fixes for the shortcut tests:
```ts
it("returns Default mode for undefined inputs (no execSecurity / execAsk)", () => {
const mode = resolveCurrentMode(undefined, undefined);
expect(mode.id).toBe("default");
});
it("Ctrl+4 returns Plan mode", () => {
const result = handleModeShortcut(makeKeyEvent("4", true, false));
expect(result?.id).toBe("plan");
expect(result?.planMode).toBe("plan");
});
it("Ctrl+5 returns Bypass mode", () => {
const result = handleModeShortcut(makeKeyEvent("5", true, false));
expect(result?.id).toBe("bypass");
});
it("returns null for Ctrl+6 (no matching mode)", () => {
expect(handleModeShortcut(makeKeyEvent("6", true, false))).toBeNull();
});
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: ui/src/ui/chat/mode-switcher.ts
Line: 257
Comment:
**Tooltip range is off by one after Default mode was added**
There are now 5 modes with shortcuts `Ctrl+1` through `Ctrl+5`, but the tooltip still says `Ctrl+1-4`.
```suggestion
title="Switch mode (Ctrl+1-5)"
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "fix(ui): #67721 follow-up v5 — autonomou..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fcb9c3b713
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR adds new webchat control UI building blocks for (a) switching execution modes in the chat input toolbar and (b) rendering agent plans as interactive, expandable cards, plus the supporting CSS.
Changes:
- Add
mode-switcher.tsfor defining modes, rendering a mode chip + dropdown, and handling Ctrl/Meta shortcuts. - Add
plan-cards.tsfor rendering expandable plan cards and exporting plan content to Markdown. - Add
plan-cards.cssand wire it intoui/src/styles/chat.css; extendlayout.csswith mode switcher styles.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/chat/plan-cards.ts | New plan card renderer + Markdown formatter for plan content in the UI. |
| ui/src/ui/chat/mode-switcher.ts | New mode switcher definitions/rendering + keyboard shortcut helper. |
| ui/src/styles/chat/plan-cards.css | New styling for expandable plan cards in the message thread. |
| ui/src/styles/chat/layout.css | Adds CSS for the mode switcher chip and dropdown menu. |
| ui/src/styles/chat.css | Imports the new plan-cards.css stylesheet. |
…syntax Mode switcher: - Add type='button' to chip and menu item buttons (prevent form submission) - Remove redundant inline style='position: relative' (already in CSS) - Show 'Ctrl+N' in shortcut labels, not just 'N' - Use Ctrl only, not Cmd — Cmd+1-4 is browser tab switching on macOS Plan cards: - Use standard markdown checkbox syntax ([ ] and [x]) instead of non-standard [~] and [>] markers that markdown-it-task-lists doesn't recognize - Cancelled steps: '- [ ] ~~label~~ (cancelled)' instead of '- [~] ~~label~~' - In-progress steps: '- [ ] **label** (in progress)' instead of '- [>] **label**'
There was a problem hiding this comment.
Pull request overview
This PR adds new webchat Control UI building blocks for selecting an execution “mode” from the chat toolbar and displaying agent plan events as expandable checklist cards, plus the associated styling.
Changes:
- Added a new mode switcher module (mode definitions, dropdown rendering, keyboard shortcut handler).
- Added a new plan cards module (expandable
<details>plan rendering + markdown formatting helper). - Added new plan card styles and mode switcher styles, and wired plan-cards CSS into the main chat stylesheet.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/chat/plan-cards.ts | New Lit render helpers for expandable plan cards + markdown formatting. |
| ui/src/ui/chat/mode-switcher.ts | New Lit render helpers for a toolbar mode chip/menu + shortcut handling. |
| ui/src/styles/chat/plan-cards.css | New styles for plan cards in the message thread. |
| ui/src/styles/chat/layout.css | Added styles for the mode switcher chip + dropdown menu. |
| ui/src/styles/chat.css | Imports the new plan-cards stylesheet. |
There was a problem hiding this comment.
Pull request overview
Adds new webchat control UI building blocks to support execution-mode switching and rich plan display, laying groundwork for later integration into the chat toolbar/message renderer.
Changes:
- Added a new
mode-switcherUI module (mode definitions, chip + dropdown render, Ctrl+1–4 shortcut handler). - Added a new
plan-cardsUI module (expandable plan card renderer + markdown formatter). - Added new plan-card styling and wired it into the global chat stylesheet; added mode switcher styles to the chat layout stylesheet.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/chat/plan-cards.ts | New Lit render helpers for expandable plan cards + markdown formatting. |
| ui/src/ui/chat/mode-switcher.ts | New Lit render helpers for a mode chip/menu + keyboard shortcut selection. |
| ui/src/styles/chat/plan-cards.css | New CSS for plan-card visuals (details/summary, checklist steps). |
| ui/src/styles/chat/layout.css | Adds CSS rules for the mode switcher chip and dropdown menu. |
| ui/src/styles/chat.css | Imports the new plan-cards stylesheet into chat styles. |
mode-switcher.test.ts (15 tests): - resolveCurrentMode: all 4 mode mappings, unknown fallback, undefined fallback - handleModeShortcut: Ctrl+1-4 match, Ctrl+5 no match, Cmd+1 blocked (macOS), Ctrl+Cmd blocked, plain digit blocked, preventDefault called/not called plan-cards.test.ts (8 tests): - formatPlanAsMarkdown: title h2, explanation italics, all 4 step statuses, activeForm substitution, absent activeForm, missing explanation, empty steps
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 321fd64aad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- handleModeShortcut: reject Ctrl+Shift and Ctrl+Alt combos (not just Cmd) - Add aria-hidden='true' to all decorative SVGs in mode-switcher and plan-cards - Add 2 tests: Ctrl+Shift+1 and Ctrl+Alt+1 return null
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70ff5df5e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds new webchat UI building blocks for execution-mode selection and displaying agent plans as expandable “plan cards”, plus associated styling and unit tests. This fits into the control UI’s chat thread/tool-card ecosystem (Lit templates + chat CSS bundle) and is intended to be wired into the existing chat toolbar and message rendering.
Changes:
- Introduces
mode-switcher.ts(mode definitions, dropdown render, Ctrl+1..4 shortcut handler) + tests. - Introduces
plan-cards.ts(expandable plan card renderer + markdown formatter) + tests. - Adds plan card CSS + mode-switcher menu styling, and imports plan-cards CSS into the chat stylesheet bundle.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/chat/plan-cards.ts | New plan-card renderer + markdown formatting helper for plans |
| ui/src/ui/chat/plan-cards.test.ts | Unit tests for formatPlanAsMarkdown() |
| ui/src/ui/chat/mode-switcher.ts | New mode switcher definitions, rendering, and keyboard shortcut handler |
| ui/src/ui/chat/mode-switcher.test.ts | Unit tests for mode resolution + shortcut handling |
| ui/src/styles/chat/plan-cards.css | New styling for expandable plan cards in the message thread |
| ui/src/styles/chat/layout.css | Adds styling for the mode switcher chip + dropdown menu |
| ui/src/styles/chat.css | Imports the new plan-cards stylesheet into the chat bundle |
- aria-expanded now renders as string 'true'/'false' instead of boolean (Lit boolean binding removes the attribute entirely when false) - formatPlanAsMarkdown() normalizes newlines in title, explanation, and step labels to prevent broken markdown structure
There was a problem hiding this comment.
Pull request overview
Adds new webchat UI building blocks for execution-mode switching and interactive plan display, plus the associated styling and unit tests.
Changes:
- Introduce
mode-switcherUI renderer + mode definitions + keyboard shortcut helper (with tests). - Introduce
plan-cardsUI renderer + markdown formatter for plans (with tests). - Add new plan-card styles and wire them into the main chat stylesheet; add mode-switcher styles to chat layout CSS.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/chat/plan-cards.ts | New plan card rendering + plan→markdown formatting helper |
| ui/src/ui/chat/plan-cards.test.ts | Unit tests for markdown plan formatting |
| ui/src/ui/chat/mode-switcher.ts | New mode definitions, mode resolver, dropdown renderer, shortcut handler |
| ui/src/ui/chat/mode-switcher.test.ts | Unit tests for mode resolver + shortcut handler |
| ui/src/styles/chat/plan-cards.css | New plan card styling |
| ui/src/styles/chat/layout.css | Adds mode switcher chip/menu styling |
| ui/src/styles/chat.css | Imports the new plan card stylesheet |
Critical bug found in review: the Plan mode entry mapped to execSecurity:'deny' which would block ALL exec including read-only commands (ls, cat, git status). This directly contradicts #67538's plan-mode design where read-only exec MUST work for research. Worse, this UI toggle didn't actually activate #67538's mutation gate — it just set deny flags on a separate session field. Two parallel 'plan mode' mechanisms that didn't talk to each other. Fix: remove Plan mode from the 4-mode switcher. The remaining 3 modes (Ask/Accept/Bypass) cleanly map to execSecurity/execAsk without collision. Plan mode UI toggle requires a protocol contract change (planMode field in sessions.patch) and lands as a separate PR after #67538 merges. - Mode count: 4 → 3 (Ask, Accept, Bypass with shortcuts Ctrl+1/2/3) - Removed unused listIcon SVG constant - Tooltip updated: 'Ctrl+1-3' instead of 'Ctrl+1-4' - Tests updated: removed Plan mode test, added test that Ctrl+4 returns null - Test for resolveCurrentMode('deny','off') now expects fallback to Ask
Architectural fix: Removed broken Plan mode UI toggleA reviewer (Opus 4.7) flagged a critical architectural bug: the Plan mode entry in the mode switcher was setting
Fixed in Plan mode UI toggle is a separate concern that requires:
That sequence belongs in its own PR after #67538 lands. Better to ship 3 working modes than 4 with a broken one. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 187b7a9b04
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…tic dismissal, hide input Five user-reported bugs from end-to-end test: 1. Agent never auto-continued after Accept. Fix: send synthetic [PLAN_DECISION] user-turn message via handleSendChatInternal after sessions.patch succeeds. New buildPlanDecisionUserMessage helper in app.ts. 2. Optimistic card dismissal — clear UI state BEFORE round-trip; restore on error. Closes the stale-card double-click failure. 3. Revise popup → inline textarea. window.prompt's Cancel returned null which still fired reject; new textarea is opt-in via handlePlanApprovalReviseOpen and Cancel correctly does nothing. 4. Hide chat input bar while plan-approval card is showing. 5. Differentiate Accept vs Accept-Allow-Edits in [PLAN_DECISION] text. Files (6 modified, all UI).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c1b80e8f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…efault permissions mode Two UX fixes from end-to-end test: 1. Auto-open the full plan in the right sidebar when an approval event arrives. Previously the user had to click "Open plan" to see the full markdown — now it pops open automatically. New openPlanInSidebar() method on the app + optional callback on PlanApprovalHost that handlePlanApprovalEvent invokes after setting state. 2. New "Default permissions" mode entry in mode-switcher that clears execSecurity/execAsk overrides (sends explicit `null` to sessions.patch). When the user picks this, the runtime falls back to whatever's in agents.defaults / per-agent config (the "normal agentic" mode the operator configured). Reorders chip: Default / Ask / Accept / Plan / Bypass. Fixes the user complaint that exiting plan mode locked them into Ask instead of restoring their config-default permissions. Files: - ui/src/ui/app.ts: openPlanInSidebar() method - ui/src/ui/app-tool-stream.ts: openPlanInSidebar callback hook - ui/src/ui/chat/mode-switcher.ts: Default mode entry + shortcut renumber - ui/src/ui/app-render.ts: explicit null for execSecurity/execAsk on Default mode select
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd94b1534e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…live plan, allow-edits gate, scroll preserve Five fixes from end-to-end test: 1. Stall after rejection: buildPlanDecisionUserMessage reject now reads "Your next response MUST include an exit_plan_mode tool call. Do NOT just acknowledge with chat text — call the tool. Do NOT stop after a one-line 'Revising' message." 2. Default permissions as post-approval state: handlePlanApprovalDecision now sends execSecurity:null + execAsk:null on approve/edit so chip falls back to "Default" not the prior Ask preset. Reject leaves them alone (still in plan mode). 3. Accept-allow-edits gate spelled out: ">95% confidence to deviate; else use read-only tools / spawn subagents to validate; record deviation via update_plan; continue autonomously until completion, real blocker, or destructive action requiring confirmation." 4. Live plan view: maybeForwardLivePlanUpdate detects update_plan tool calls and refreshes the right sidebar markdown in-place. New refreshLivePlanSidebar method on app — boxes tick off live as the agent steps through the approved plan. 5. Open Plan scroll: handleOpenSidebar captures chat-thread scrollTop pre-reflow and restores after RAF. Sticky-bottom if user was at bottom; absolute restore otherwise. Required PR-8 backend follow-up (commit 3bd8ae6 on feat/plan-mode-integration): enter_plan_mode tool result content tells the agent to call exit_plan_mode next. Files (2 modified): - ui/src/ui/app.ts: buildPlanDecisionUserMessage rewrite, exec*:null on approve/edit, refreshLivePlanSidebar, handleOpenSidebar scroll preserve - ui/src/ui/app-tool-stream.ts: maybeForwardLivePlanUpdate detector
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9689450235
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…DECISION] approve/edit messages
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e12a6d09a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Plan-mode rollout series — status updateThis PR is part of a 10-PR plan-mode rollout. The cumulative state ships locally as Series overview + landing order: see #68441 (latest comment). This PR's role in the series: see the original PR description above. Live test status: covered by the cumulative install — all behaviors from this PR are verified in production-like usage on Telegram + webchat. No regressions detected since the live install (2026-04-18). |
There was a problem hiding this comment.
Pull request overview
Adds plan-mode and permission-mode UX to the webchat: a toolbar mode-switcher, inline plan-approval UI, and rendering/formatting for plan content (cards + sidebar markdown), plus wiring for consuming plan/approval-related agent events.
Changes:
- Add a toolbar mode-switcher chip (with menu + shortcut scaffolding) and wire it into the chat composer toolbar.
- Add inline plan-approval card UI and host/state wiring to surface and resolve plan approvals.
- Add plan card rendering + markdown formatting utilities and corresponding styles/tests; add
/planslash command plumbing.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/views/plan-approval-inline.ts | New inline plan approval card renderer (buttons + revise textarea). |
| ui/src/ui/views/chat.ts | Wires mode-switcher + inline plan-approval UI into the chat view and hides composer during approvals. |
| ui/src/ui/types.ts | Extends session row types with exec/plan-related fields for UI rendering. |
| ui/src/ui/chat/slash-commands.ts | Adds /plan to slash-command definitions. |
| ui/src/ui/chat/slash-command-executor.ts | Implements /plan execution via sessions.patch and exports setSessionPlanMode(). |
| ui/src/ui/chat/plan-cards.ts | New plan card renderer and markdown formatter. |
| ui/src/ui/chat/plan-cards.test.ts | Unit tests for plan markdown formatting + DOM render shape. |
| ui/src/ui/chat/mode-switcher.ts | New mode-switcher component: definitions, rendering, and shortcut handler. |
| ui/src/ui/chat/mode-switcher.test.ts | Unit tests for mode resolution, shortcut behavior, and rendering. |
| ui/src/ui/app.ts | Adds plan-approval state + decision handler, sidebar plan rendering/refresh, and plan decision message builder. |
| ui/src/ui/app-view-state.ts | Extends view-state contract with plan-approval fields/handlers. |
| ui/src/ui/app-tool-stream.ts | Adds parsing/wiring for approval-stream plan payloads + forwarding update_plan updates to sidebar refresh. |
| ui/src/ui/app-render.ts | Wires chat props for mode setting + plan approval behaviors and sidebar “Open plan” rendering. |
| ui/src/styles/chat/plan-cards.css | New styles for plan cards. |
| ui/src/styles/chat/layout.css | Adds mode-switcher styles and inline plan-approval card styles. |
| ui/src/styles/chat.css | Imports plan-cards stylesheet. |
There was a problem hiding this comment.
Pull request overview
Adds plan-mode/permission-mode UI affordances to the webchat, including a mode switcher in the input toolbar, inline plan approval UX, and plan checklist rendering (plus wiring to session patching and agent event streams).
Changes:
- Introduces a toolbar mode-switcher chip (with shortcuts) and wires it to
sessions.patchupdates. - Adds inline plan approval UI + runtime event handling for
exit_plan_modeapprovals andupdate_planlive updates. - Adds plan cards rendering + styling, plus
/planslash command plumbing and session type fields (execSecurity,execAsk,planMode).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/views/plan-approval-inline.ts | New inline plan approval card renderer (accept/edit/revise + open plan). |
| ui/src/ui/views/chat.ts | Renders mode switcher + inline plan approval; hides input bar during approval. |
| ui/src/ui/types.ts | Exposes permission/plan-mode session fields to the UI session row type. |
| ui/src/ui/chat/slash-commands.ts | Registers /plan as a local slash command with arg hints. |
| ui/src/ui/chat/slash-command-executor.ts | Implements /plan command and setSessionPlanMode() helper. |
| ui/src/ui/chat/plan-cards.ts | Adds expandable plan cards + markdown formatting helper. |
| ui/src/ui/chat/plan-cards.test.ts | Unit tests for plan card rendering + markdown formatting. |
| ui/src/ui/chat/mode-switcher.ts | New mode switcher component + shortcut handling + mode resolution. |
| ui/src/ui/chat/mode-switcher.test.ts | Unit tests for mode resolution, shortcuts, and rendering. |
| ui/src/ui/app.ts | Adds plan approval state + decision handling; sidebar scroll preservation; live sidebar refresh helpers. |
| ui/src/ui/app-view-state.ts | Extends app view state with plan approval fields + handler signatures. |
| ui/src/ui/app-tool-stream.ts | Parses plan approval stream events and forwards update_plan updates to sidebar hooks. |
| ui/src/ui/app-render.ts | Wires mode selection + plan approval props into renderChat; provides sidebar open callback. |
| ui/src/styles/chat/plan-cards.css | New styles for plan cards in the message thread. |
| ui/src/styles/chat/layout.css | Adds styles for mode switcher popover and inline plan approval card. |
| ui/src/styles/chat.css | Imports plan-cards stylesheet into chat bundle. |
📋 Architecture & Status — single source of truth (commit
|
|
To use Codex here, create an environment for this repo. |
There was a problem hiding this comment.
Pull request overview
This PR extends the webchat UI to support plan-mode workflows by adding: (a) an execution-mode switcher chip in the chat input toolbar, (b) richer plan rendering (plan cards + markdown formatting), and (c) an inline plan-approval card that drives sessions.patch plan-approval decisions and auto-opens/refreshes the plan in the sidebar.
Changes:
- Add mode switcher UI (chip + dropdown + shortcut handler) and wire it into the chat toolbar via
sessions.patch. - Add plan presentation surfaces: message-thread plan cards + sidebar markdown formatting.
- Add inline plan-approval card and event-stream wiring to surface/resolve approvals and forward live
update_planupdates to the sidebar.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/views/plan-approval-inline.ts | New inline plan-approval card UI (Approve/Edit/Revise + inline feedback textarea). |
| ui/src/ui/views/chat.ts | Wires the inline plan approval card and mode switcher into the chat UI; hides input while approval is present. |
| ui/src/ui/types.ts | Exposes session plan/permission mode fields to the UI (execSecurity, execAsk, planMode). |
| ui/src/ui/chat/slash-commands.ts | Adds /plan to the slash command list (UI-only command metadata). |
| ui/src/ui/chat/slash-command-executor.ts | Implements /plan via sessions.patch helper (setSessionPlanMode). |
| ui/src/ui/chat/plan-cards.ts | Adds expandable plan card rendering + markdown formatter for plans. |
| ui/src/ui/chat/plan-cards.test.ts | Adds unit tests for plan markdown formatting and DOM rendering. |
| ui/src/ui/chat/mode-switcher.ts | Adds mode switcher rendering, current-mode resolution, and shortcut handling. |
| ui/src/ui/chat/mode-switcher.test.ts | Adds unit tests for mode resolution, shortcut handling, and rendering. |
| ui/src/ui/app.ts | Adds plan approval state, decision dispatch (sessions.patch + synthetic follow-up user message), sidebar scroll preservation, and sidebar live-plan refresh hooks. |
| ui/src/ui/app-view-state.ts | Adds view-state fields and handlers for plan approval UX. |
| ui/src/ui/app-tool-stream.ts | Adds approval-stream handling for plan approval requests + forwards update_plan tool updates to sidebar refresh. |
| ui/src/ui/app-render.ts | Wires mode selection + plan approval props/handlers into renderChat, plus “Open plan” sidebar rendering. |
| ui/src/styles/chat/plan-cards.css | New CSS for plan cards in the message thread. |
| ui/src/styles/chat/layout.css | Adds CSS for mode switcher and inline plan approval card. |
| ui/src/styles/chat.css | Imports plan-cards.css. |
| it("returns Custom for undefined inputs (no execSecurity / execAsk)", () => { | ||
| const mode = resolveCurrentMode(undefined, undefined); | ||
| expect(mode.id).toBe("custom"); |
| it("returns correct mode for Ctrl+1 through Ctrl+4 (Ask/Accept/Plan/Bypass)", () => { | ||
| for (const mode of MODE_DEFINITIONS) { | ||
| const result = handleModeShortcut(makeKeyEvent(mode.shortcut, true, false)); | ||
| expect(result).not.toBeNull(); | ||
| expect(result!.id).toBe(mode.id); | ||
| } | ||
| }); | ||
|
|
||
| it("Ctrl+3 returns Plan mode", () => { | ||
| const result = handleModeShortcut(makeKeyEvent("3", true, false)); | ||
| expect(result?.id).toBe("plan"); | ||
| expect(result?.planMode).toBe("plan"); | ||
| }); | ||
|
|
||
| it("Ctrl+4 returns Bypass mode", () => { | ||
| const result = handleModeShortcut(makeKeyEvent("4", true, false)); | ||
| expect(result?.id).toBe("bypass"); | ||
| }); | ||
|
|
||
| it("returns null for Ctrl+5 (no matching mode)", () => { | ||
| expect(handleModeShortcut(makeKeyEvent("5", true, false))).toBeNull(); | ||
| }); |
| title="Switch mode (Ctrl+1-4)" | ||
| aria-haspopup="menu" | ||
| aria-expanded="${menuOpen ? "true" : "false"}" | ||
| > | ||
| ${currentMode.icon} | ||
| <span class="agent-chat__mode-chip-label">${currentMode.shortLabel}</span> | ||
| <svg | ||
| width="10" | ||
| height="10" | ||
| viewBox="0 0 24 24" | ||
| fill="none" | ||
| stroke="currentColor" | ||
| stroke-width="2" | ||
| aria-hidden="true" | ||
| > | ||
| <path d="M6 9l6 6 6-6" /> | ||
| </svg> | ||
| </button> | ||
| ${menuOpen | ||
| ? html` | ||
| <div class="agent-chat__mode-menu"> | ||
| <!-- | ||
| Codex/Copilot #67721 r3095798757: previously declared | ||
| role="menu" / role="menuitem" but did NOT implement the | ||
| WAI-ARIA menu keyboard contract (arrow nav, Home/End, | ||
| roving tabindex, focus trap). Per WAI-ARIA, claiming the | ||
| menu role without those keyboard semantics misleads | ||
| assistive tech. Rather than ship a partial menu, we | ||
| expose this surface as a popover of plain <button>s — | ||
| native button focus + Escape-on-chip already gives | ||
| keyboard users a usable interaction with no false ARIA | ||
| promise. | ||
| --> | ||
| ${MODE_DEFINITIONS.map( | ||
| (mode) => html` | ||
| <button | ||
| type="button" | ||
| class="agent-chat__mode-menu__item ${mode.id === currentMode.id | ||
| ? "agent-chat__mode-menu__item--active" | ||
| : ""}" | ||
| @click=${() => { | ||
| onSelectMode(mode); | ||
| }} | ||
| > | ||
| <span class="agent-chat__mode-menu__label">${mode.label}</span> | ||
| <kbd class="agent-chat__mode-menu__shortcut">Ctrl+${mode.shortcut}</kbd> | ||
| </button> | ||
| `, | ||
| )} | ||
| </div> | ||
| ` | ||
| : nothing} | ||
| </div> | ||
| `; | ||
| } | ||
|
|
||
| /** | ||
| * Handles keyboard shortcuts for mode switching (Ctrl+1 through Ctrl+4). | ||
| * Returns the selected mode if a shortcut matched, or null. |
| /** | ||
| * `/plan on|off|status` — toggle the session into or out of plan mode. | ||
| * Routes through `setSessionPlanMode` (sessions.patch with `planMode`) | ||
| * which the gateway's PR-8 handler validates against the | ||
| * `agents.defaults.planMode.enabled` opt-in gate. | ||
| */ | ||
| async function executePlan( | ||
| client: GatewayBrowserClient, | ||
| sessionKey: string, | ||
| args: string, | ||
| ): Promise<SlashCommandResult> { | ||
| const raw = normalizeLowercaseStringOrEmpty(args); | ||
| if (!raw || raw === "status") { | ||
| return { | ||
| content: formatDirectiveOptions( | ||
| "Usage: `/plan on` to enter plan mode, `/plan off` to exit.", | ||
| "on, off, status", | ||
| ), | ||
| }; |
There was a problem hiding this comment.
Pull request overview
Adds plan-mode and permission-mode UX to the webchat, including a toolbar mode switcher, inline plan-approval flow, and webchat plan rendering.
Changes:
- Introduces a mode switcher chip (Default/Ask/Accept/Plan/Bypass) wired to
sessions.patchand adds/plan on|off|status. - Adds an inline “Agent proposed a plan” approval card (Accept / Accept allow edits / Revise) plus sidebar plan viewing and live sidebar refresh from
update_plan. - Adds webchat plan cards (
<details>/<summary>) + CSS styling and markdown formatting helper.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/views/plan-approval-inline.ts | New inline plan approval card renderer used above the composer. |
| ui/src/ui/views/chat.ts | Integrates mode switcher and inline plan approval into the chat view; hides composer during plan approval. |
| ui/src/ui/types.ts | Exposes execSecurity, execAsk, and planMode fields to the UI session row. |
| ui/src/ui/chat/slash-commands.ts | Adds /plan command metadata and categorization. |
| ui/src/ui/chat/slash-command-executor.ts | Implements /plan execution and setSessionPlanMode() helper. |
| ui/src/ui/chat/plan-cards.ts | Adds plan card rendering and a markdown formatter for plans. |
| ui/src/ui/chat/plan-cards.test.ts | Tests plan card rendering + markdown formatting. |
| ui/src/ui/chat/mode-switcher.ts | New mode switcher component + shortcut handler + mode resolution. |
| ui/src/ui/chat/mode-switcher.test.ts | Tests for mode switcher behavior (currently inconsistent with implementation). |
| ui/src/ui/app.ts | Wires plan approval decision flow, sidebar scroll preservation, and sidebar plan markdown rendering/refresh. |
| ui/src/ui/app-view-state.ts | Adds plan approval state + handlers to app view state typing. |
| ui/src/ui/app-tool-stream.ts | Parses approval events into planApprovalRequest and forwards update_plan updates to sidebar refresh hook. |
| ui/src/ui/app-render.ts | Wires mode switching and plan approval props into renderChat(). |
| ui/src/styles/chat/plan-cards.css | New styles for plan cards in the message thread. |
| ui/src/styles/chat/layout.css | Adds mode switcher styles and inline plan approval card styles. |
| ui/src/styles/chat.css | Imports the new plan-cards stylesheet. |
| return { | ||
| content: | ||
| mode === "plan" | ||
| ? "Plan mode **enabled** — write/edit/exec tools blocked until plan approved." | ||
| : "Plan mode **disabled** — mutations unblocked.", | ||
| action: "refresh", |
| { | ||
| key: "plan", | ||
| name: "plan", | ||
| description: "Toggle plan mode (blocks write/edit/exec until approved)", | ||
| args: "<on|off|status>", | ||
| icon: "book", | ||
| category: "session", | ||
| executeLocal: true, | ||
| argOptions: ["on", "off", "status"], |
| // Restore card so the user can retry. | ||
| this.planApprovalRequest = snapshotRequest; | ||
| this.planApprovalReviseDraft = snapshotReviseDraft; | ||
| this.planApprovalError = `Plan approval failed: ${String(err)}`; |
| const stepLines = plan | ||
| .map((step, i) => { | ||
| const marker = | ||
| step.status === "completed" ? "[x]" : step.status === "cancelled" ? "[ ] ~~" : "[ ]"; | ||
| const close = step.status === "cancelled" ? "~~" : ""; | ||
| const label = | ||
| step.status === "in_progress" && step.activeForm ? step.activeForm : step.step; | ||
| return `${i + 1}. ${marker} ${label}${close}`; | ||
| }) | ||
| .join("\n"); | ||
| const md = `# ${summary || "Active plan"}\n\n${stepLines}\n`; | ||
| this.sidebarContent = { kind: "markdown", content: md }; | ||
| } |
| * actual values — instead of silently mislabeling the chip as "Ask". | ||
| * | ||
| * Codex P1 (PR #67721 r3094970182): the prior fallback to | ||
| * `MODE_DEFINITIONS[0]` (Ask) made the UI report Ask for valid |
| const stepLines = request.plan | ||
| .map((step, i) => { | ||
| const marker = | ||
| step.status === "completed" | ||
| ? "[x]" | ||
| : step.status === "cancelled" | ||
| ? "[ ] ~~" | ||
| : "[ ]"; | ||
| const close = step.status === "cancelled" ? "~~" : ""; | ||
| const label = | ||
| step.status === "in_progress" && step.activeForm | ||
| ? step.activeForm | ||
| : step.step; | ||
| return `${i + 1}. ${marker} ${label}${close}`; | ||
| }) | ||
| .join("\n"); | ||
| const md = `# ${request.summary || "Proposed plan"}\n\n${stepLines}\n`; | ||
| state.handleOpenSidebar({ kind: "markdown", content: md }); | ||
| }, |
| export interface ModeDefinition { | ||
| id: string; | ||
| label: string; | ||
| shortLabel: string; | ||
| shortcut: string; | ||
| /** | ||
| * Permission mode mapping (Ask/Accept/Bypass only). | ||
| * Plan mode does NOT set these — see `planMode` field instead. | ||
| */ | ||
| execSecurity?: string; | ||
| execAsk?: string; | ||
| /** |
| /** | ||
| * Set the session's plan-mode flag on the backend (PR-8). | ||
| * | ||
| * - `"plan"`: arms the runtime mutation gate — write/edit/exec/etc. are |
| it("returns Custom for undefined inputs (no execSecurity / execAsk)", () => { | ||
| const mode = resolveCurrentMode(undefined, undefined); | ||
| expect(mode.id).toBe("custom"); |
| onToggleMenu(); | ||
| } | ||
| }} | ||
| title="Switch mode (Ctrl+1-4)" |
|
Closing in favor of consolidated PR #68939. Rationale: this branch was 734 commits behind upstream/main and the The full iteration history + decision log lives in |
TL;DR
Adds two webchat UI components: (1) a 3-mode permission switcher chip in the chat input toolbar, (2) expandable plan cards in the message thread. Plan mode UI toggle is intentionally NOT included — see "Out of scope" below.
Tracking
Status: Components built + tested, integration WIP
Components and tests complete:
ui/src/ui/chat/mode-switcher.ts— 3 modes, render functions, keyboard shortcuts (15 tests)ui/src/ui/chat/plan-cards.ts— expandable plan card rendering (8 tests)ui/src/styles/chat/plan-cards.css— plan card stylesui/src/styles/chat/layout.css— mode switcher chip + menu stylesRemaining integration work (next commits on this branch):
chat.tstoolbar-left sectionapp-tool-stream.ts(currently dropped at line 468)grouped-render.tsmessage renderingdispatch-from-config.tsFeature 1: Mode Switcher (3 modes)
Position: left side of chat input toolbar, after mic button.
Maps to existing session fields. Only Ctrl+digit (no Cmd/Shift/Alt) triggers — preserves macOS browser tab switching.
Out of scope: Plan mode UI toggle
Plan mode is owned by the runtime
PlanModeSessionStateintroduced in #67538 (mutation gate + approval state machine). Adding a UI toggle requires a protocol contract change (newplanModefield insessions.patch) which doesn't belong in a UI PR. The follow-up sequence:sessions.patchschema withplanMode: "plan" | "normal"PlanModeSessionStateon patchSetting
execSecurity: "deny"to fake plan mode would have actively broken plan mode investigation — the design explicitly allows read-only exec (ls,cat,git status) during planning.Feature 2: Plan Cards
<details>/<summary>pattern from tool cardsaria-hidden="true"Feature 3: Channel-Aware Plan Delivery (TODO)
Not yet in this PR — will be added in a follow-up commit on this branch:
Files changed
ui/src/ui/chat/mode-switcher.tsui/src/ui/chat/mode-switcher.test.tsui/src/ui/chat/plan-cards.tsui/src/ui/chat/plan-cards.test.tsui/src/styles/chat/plan-cards.cssui/src/styles/chat.cssui/src/styles/chat/layout.cssDependencies