04. E2E Test Plan
Scope: MVP acceptance scenarios plus current shipped product increments for PI-Desktop Status: Accepted (protocol/Electron automation is active; full desktop Playwright remains planned) Cross-references: acceptance-criteria · milestones · ai-development-workflow · change-checklist
1. Goals
- Document every user-visible and protocol-visible behavior that MVP must verify.
- Provide a scenario catalog that maps to acceptance criteria (A–H) and milestones (M1–M6).
- Serve as the traceability backbone: scenario ID ↔ acceptance criterion ↔ spec.
- Prepare for future automation without requiring implementation now.
2. Non-goals
- Full UI-driven automated coverage; protocol and source-contract automation is active while the broader desktop suite remains planned.
- Performance / stress testing (post-MVP).
- Native Windows/Linux release qualification (published artifacts exist; native qualification gaps remain documented).
- Marketplace publisher provenance and hostile-plugin sandbox scenarios; basic browse/install/update flows are already part of the catalog.
- Remote gateway / control-plane scenarios (post-MVP — ADR 0004 / baseline #20).
3. Test Pyramid
╱ E2E ╲ — few, high-value, cross-system
╱ Integration ╲ — IPC/RPC contracts, host↔renderer
╱ Unit ╲ — per-module, fast, isolated| Level | Scope | Count target | Tooling |
|---|---|---|---|
| Unit | Single module, no IPC | Many | Vitest / Rust #[test] |
| Integration | IPC contract, host↔renderer, host↔sidecar | Moderate | Vitest + IPC mocks or live Electron |
| E2E | Full user journey through the desktop app | 100+ functional + US-UI visual catalog | protocol smoke + Electron probes now; Playwright later |
Strategy: document all E2E scenarios now; add or update unit/integration tests alongside code when change risk makes them necessary; automate E2E after M5.
4. Tooling Intent
| Tool | Purpose | Status |
|---|---|---|
| Vitest | Unit + integration (TS side) | Active (pnpm test, shared package) |
| Rust #[test] | Host-core unit tests | Active (cargo test -p host-core) |
| Protocol smoke | Host RPC + tools + plugins headless | Active (test:e2e, 20 checks) |
| Electron probes | Boot bridge + crash supervision | Active (test:e2e:boot, test:e2e:supervision) |
| Playwright | Full UI-driven journeys | Planned (post-M5) |
Decision: document scenarios now; pick concrete E2E runner when code is ready for M5 hardening.
5. Environment Requirements
| Requirement | Detail |
|---|---|
| Platform | macOS arm64, Windows x64, and Linux x64 release targets (D126) |
| Profile | Clean ~/.pi-desktop profile (no prior config) |
| Fixtures | Sample project directory (examples/fixtures/sample-project/) |
| Sample plugin | examples/plugins/hello loaded from local path |
| Provider | At least one provider with a valid key (test account) |
| Display | Headless-capable Electron or real display |
6. Scenario Template
Each scenario is documented in this format:
### E2E-<ID>: <title>
- **Preconditions**: what must be true before steps start
- **Steps**: ordered list of user / system actions
- **Expected**: observable outcome that proves correctness
- **Specs linked**: relevant spec file(s)
- **Acceptance criterion**: which A–H letter(s) this verifies
- **Milestone**: M1–M6 target
- **Status**: Draft | Documented | Partially automated | Automated | Passed7. MVP Scenario Catalog
Runtime Resource Governance
E2E-097: Tool burst is bounded and recovers after host restart
- Preconditions: Host-core is healthy; one session has a workspace; the supervision probe can terminate the host process.
- Steps: 1) Dispatch a burst larger than the host tool budget containing read tools and shell commands. 2) Observe
app.healthwhile the burst runs. 3) Terminate host-core during active calls. 4) Wait for one supervised restart. 5) Allow the persistence outbox to flush. - Expected: Active shell processes never exceed the configured global and per-session limits. Excess work returns
HOST_OVERLOADEDor waits in the bounded queue. Only one restart loop runs; stale-generation calls fail fast asHOST_UNAVAILABLE; no repeatedERR_STREAM_DESTROYEDpersistence storm is emitted. Temporary OS thread pressure during the same burst does not terminate host-core through its stdio control path; the host remains on one generation and capacity errors stay structured. Completed assistant/tool messages are persisted once after recovery. - Specs linked:
03-runtime/06-host-rpc-protocol.md,03-runtime/07-process-model.md,03-runtime/08-error-codes.md,03-runtime/09-logging-and-observability.md, ADR 0051 - Acceptance: A (runtime health), C (tool execution and recovery)
- Milestone: M5
- Status: Documented; automation pending
Boot & Healthcheck
E2E-001: App launches and shows main window
- Preconditions: macOS arm64; no prior
~/.pi-desktopprofile. For the development lane, workspace package build outputs are absent or older than their TypeScript sources. - Steps: 1) Launch PI-Desktop. In the development lane, use
pnpm dev. 2) Observe main window appears. - Expected: Development launch rebuilds all workspace dependencies before host-core and Electron startup. Window first shows the branded startup splash while bootstrap runs, then reveals the main shell in English with the current locale catalog; no compile error, missing-menu runtime error, or crash; version info visible.
- Specs linked:
03-runtime/07-process-model.md,04-ux/01-ui-ia.md - Acceptance: A (app startup)
- Milestone: M1
- Status: Partially automated (
runtime-build-contract.test.mjscovers the dependency build contract; Electron window launch remains Draft)
E2E-002: IPC bridge is functional
- Preconditions: App is running.
- Steps: 1) Trigger an action that calls preload IPC (e.g. version query). 2) Observe result in renderer.
- Expected: Main↔renderer IPC returns expected data; no error.
- Specs linked:
03-runtime/01-ipc-protocol.md - Acceptance: A (bridge normal)
- Milestone: M1
- Status: Automated (
scripts/e2e-electron-boot.mjs— sandboxed preload bridge + IPC round-trip)
E2E-003: Rust host healthcheck responds
- Preconditions: App is running; Rust host-core sidecar started.
- Steps: 1) Electron handshakes with protocol version 9. 2) Call the host healthcheck RPC. 3) Repeat boot with mismatched older and newer protocol fixtures.
- Expected: The protocol v9 host returns
okand the handshake is logged. Every version other than v9, whether older or newer, is rejected before the conversation surface becomes interactive, so Plan approval/state events and context checkpoints cannot be silently lost. - Specs linked:
03-runtime/05-host-core-rust.md,03-runtime/06-host-rpc-protocol.md - Acceptance: A (bridge normal)
- Milestone: M1
- Status: Automated (protocol smoke)
E2E-004: First-run inline checklist appears
- Preconditions: Fresh profile (no
~/.pi-desktop). - Steps: 1) Launch app on fresh profile. 2) Observe onboarding checklist.
- Expected: Inline checklist is displayed; provider/key items open Settings → Agent, and the optional plugin item opens the app-shell Plugins destination.
- Specs linked:
04-ux/05-onboarding.md - Acceptance: A (first-run checklist)
- Milestone: M2
- Status: Automated (protocol smoke: host onboarding state; UI checklist manual)
Provider & Key
E2E-005: Add a provider and save API key
- Preconditions: App running; no provider configured.
- Steps: 1) Open Settings → Agent. 2) Open the add-provider dialog. 3) Enter name, base URL, model id, and API key. 4) Save.
- Expected: Provider appears as a card with secret badge; key stored securely (not in plaintext config); hero summary counts update.
- Specs linked:
03-runtime/12-provider-config-schema.md,03-runtime/14-secrets-storage.md - Acceptance: B (add provider, save key)
- Milestone: M2
- Status: Automated (protocol smoke: provider create + secret, no plaintext echo)
E2E-006: Key survives restart
- Preconditions: Provider + key configured.
- Steps: 1) Quit app. 2) Relaunch. 3) Open Settings → Agent → Providers.
- Expected: Provider still listed; key usable (no re-entry needed).
- Specs linked:
03-runtime/14-secrets-storage.md - Acceptance: B (key survives restart)
- Milestone: M2
- Status: Draft
E2E-007: No-provider blocking prompt
- Preconditions: App running; no provider configured.
- Steps: 1) Attempt to start a chat.
- Expected: Clear blocking prompt explaining that a provider must be configured.
- Specs linked:
04-ux/06-settings-ia.md - Acceptance: B (blocking prompt)
- Milestone: M2
- Status: Draft
Conversation Stream & Abort
E2E-008: New session and send message
- Preconditions: Provider configured.
- Steps: 1) Create new session. 2) Type a message. 3) Send.
- Expected: The transcript immediately shows a compact localized
Working…status after send, before the first assistant or tool event. It yields to concrete thinking/tool/answer feedback, and disappears when the turn ends. - Specs linked:
03-runtime/02-agent-runtime.md,03-runtime/10-session-state-machine.md - Acceptance: C (new session, send message)
- Milestone: M2
- Status: Automated (protocol smoke, live-model lane; requires PI_DESKTOP_TEST_API_KEY)
E2E-008a: First-turn tools load on demand
- Preconditions: Agent mode; provider configured;
BrowserPreviewor an enabled plugin tool is available; request capture can inspect the first and subsequent provider payloads. - Steps: 1) Create a fresh session and send a simple prompt. 2) Inspect the first provider request's tool list. 3) Ask the agent to create or edit an HTML page and observe the tool activity. 4) Start a second user prompt after the preview task completes.
- Expected: The first request contains only the mode core tools (Agent:
Read/Bash/Edit/Write; Chat:Read/Glob/Grep) and localToolSearch; deferred schemas are represented only by a bounded# On-demand toolscatalog. The agent callsToolSearchbeforeBrowserPreview(or the selected plugin/Skilltool) when the capability is needed, and the matching schema is available on the next model turn. For a user-visible HTML deliverable,BrowserPreviewis called once after creation or the first meaningful visual edit, then reused through live reload while the page is refined. Generated, test-only, and non-visual HTML files do not trigger a preview call. The loaded set does not leak into the next prompt's first request. Tool activation markers survive transcript reload without granting a host permission or workspace escape. - Specs linked:
03-runtime/02-agent-runtime.md§7.1,03-runtime/03-tools-and-permissions.md§2.1, ADR 0048,08-meta/decisions-log.md(D185) - Acceptance: C (first turn and stream) + E (tool execution)
- Milestone: M5
- Status: Unit-covered (
agent-runtimedeferred-tool tests); live-model request capture and full Electron journey pending
E2E-009: Streamed tokens visible in UI
- Preconditions: Session active; message sent.
- Steps: 1) Request a long answer containing Markdown and inline/display math. 2) Observe the assistant response as it streams. 3) Let the answer complete and inspect the renderer console.
- Expected: Runtime chunks appear progressively through the incremental Markdown renderer and the final response is complete. The renderer does not start a second animation-frame typewriter loop, raise React error 185, or reject Vite-inlined KaTeX fonts under CSP.
- Specs linked:
03-runtime/02-agent-runtime.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md,05-security/01-security.md - Acceptance: C (streamed output), Quality
- Milestone: M2
- Status: Partially automated (protocol live-model stream plus renderer source regression in
renderer-stream-safety.test.mjs; full UI observation remains Draft)
E2E-010: Abort generation
- Preconditions: A session can produce both a deliberately delayed first token and a streaming response.
- Steps: 1) Send ordinary text and stop before assistant text, thinking, or a tool row begins. 2) Confirm the user row is undone and the text returns to the composer. 3) Send again, wait for partial output, then stop during the stream. 4) Observe the transcript and composer.
- Expected: The unanswered send is undone and its draft restored. The streaming send stops with its partial response preserved and no draft restoration or duplicate user turn. The session remains usable.
- Specs linked:
03-runtime/02-agent-runtime.md - Acceptance: C (abort)
- Milestone: M2
- Status: Draft
E2E-011: Switch between project and temporary sessions
- Preconditions: One retained project session and one path-less Temporary session exist. Both transcripts exceed one viewport and have distinct final records.
- Steps: 1) Open the project session from its exact-path sidebar group. 2) Scroll to an earlier record and confirm the jump-to-latest control appears. 3) Open the Temporary session and observe its first painted frame. 4) Switch rapidly project → Temporary → project while the first two transcript reads are delayed, and observe which row responds and which destination commits. 5) Hover/focus Temporary, switch to it again to exercise the warm cache, then repeat with reduced motion enabled. 6) Observe chat content and workspace chrome.
- Expected: The sidebar contains no Recents aggregate; retained projects have scoped groups and path-less sessions remain under Temporary; each transcript loads correctly; selecting Temporary clears project context and inherits no workspace access; both sessions remain persisted. Every session activation paints its distinct final record at the transcript bottom without first exposing the transcript top, the previous session's scroll position, or a stale jump-to-latest control. The latest clicked row responds immediately and its transcript request does not wait for superseded reads; only the final project/session/work-panel tuple commits. During cold/deferred loading the previous complete view is dimmed and non-interactive under a thin busy indicator; a warm revisit reuses the bounded cache and revalidates. Reduced motion renders a static progress track and preserves the same stable destination without an animated traversal through history.
- Specs linked:
03-runtime/10-session-state-machine.md,04-ux/01-ui-ia.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (switch sessions)
- Milestone: M2
- Status: Source-level regression covered; full visual scenario Draft
E2E-011a: New session while another session is still streaming
- Preconditions: Provider configured; session A is streaming a long response (composer shows the stop/abort control).
- Steps: 1) While A is still streaming, click New task / New chat. 2) Observe the fresh session's composer. 3) Type a prompt and send it while A continues streaming in the background. 4) Let A finish and observe the fresh session's composer again.
- Expected: The new session immediately shows the idle Send control (never a stuck stop/abort control) and its textarea is enabled; the prompt sends and streams normally while A keeps running in the background. When A ends, its cross-session
agent_enddoes not change the new session's composer state, which remains idle with the Send control. - Specs linked:
04-ux/08-component-spec.md(§11.4),04-ux/09-interaction-patterns.md(§1.6, §11) - Acceptance: C (session isolation, chat & stream)
- Milestone: M2
- Status: Unit-covered (
composer-send-state.test.mjs); full UI scenario Draft
E2E-011b: Create a new session from a retained project group
- Preconditions: Provider configured; at least one retained project is visible in the sidebar; the current conversation may be idle or streaming.
- Steps: 1) Click the project group's New session control. 2) Wait for the project conversation to load. 3) Type a prompt and inspect the Send control. 4) Send without clicking New session again.
- Expected: Project activation and session creation commit as one renderer navigation flow. The destination composer becomes editable with the Send control enabled as soon as a valid draft and model are present; an earlier project's background turn cannot leave it disabled. The prompt is accepted on the first send attempt.
- Specs linked:
04-ux/09-interaction-patterns.md(§1.6),04-ux/08-component-spec.md(§11.4) - Acceptance: C (project session creation and send readiness)
- Milestone: M2
- Status: Source-level regression covered (
app-store-sidebar.test.mjs); full UI scenario Draft
Conversation Top Bar
E2E-087: Conversation top bar renders on the chat route
- Preconditions: Provider configured; at least one session exists.
- Steps: 1) Open the chat route. 2) Inspect the 46px bar at the top of the conversation area. 3) Confirm it shows the concise session/task title, the model picker, and the New task / Search / Commands action buttons; confirm the sidebar toggle appears only when the sidebar is collapsed (when expanded, the sidebar owns that control). 4) Switch to the Pull requests, Scheduled, Plugins, or Settings routes and inspect the same top region.
- Expected: On the chat route the conversation top bar renders with its title, model picker, and actions; it has no Agent|Plan|Goal mode control. The left-of-input Composer chip owns the active session's Agent/Plan/Goal switch. The task title is the only visible title text and is capped at 10 characters with an ellipsis; project scope is available through its tooltip. A compact status dot appears while running. The sidebar toggle is present only in the collapsed state (no duplicate of the sidebar's control). On every other route the frameless drag band renders instead (no top-bar controls). The bar is draggable to move the window; interactive controls do not start a window drag. macOS leaves the left ~76px clear for traffic lights only while the sidebar is collapsed (8px in fullscreen); Windows/Linux leave the right 112px clear for native window controls.
- Specs linked:
04-ux/08-component-spec.md(§2 Topbar) - Acceptance: C (send/UI), Quality
- Milestone: M2
- Status: Draft
E2E-088: Composer Agent/Plan/Goal chip updates the session
- Preconditions: Chat route active; a session selected.
- Steps: 1) Click the left-of-input Composer mode chip to enter Plan. 2) Send a prompt that would normally require Write/Edit and observe behavior. 3) Click the same Composer chip to return to Agent. 4) Begin a turn and try to toggle mode mid-run or while a pending Plan approval is visible.
- Expected: The Composer chip updates the active session
mode(Plan and Goal hard-deny Write/Edit and plugin tools while Bash follows the selected permission mode; Agent allows its normal tools per permission settings). The chip is disabled while a turn or active pending approval exists and re-enables after the session returns idle/planning. No top-bar mode control is rendered. - Specs linked:
04-ux/08-component-spec.md(§2, §11),03-runtime/03-tools-and-permissions.md(§10),03-runtime/04-data-storage.md(§8) - Acceptance: C, E
- Milestone: M2
- Status: Draft
E2E-088a: Composer configuration controls survive project/session initialization
- Preconditions: Provider configured; a new project or new session flow is visible while the destination
activeSessionIdis still resolving. - Steps: 1) Inspect the Composer mode, Thinking, and permission controls during the empty/home transition. 2) Click the mode control and confirm it advances to the next mode. 3) Open Thinking and select a supported level. 4) Open permission mode and select Auto. 5) Inspect the destination session after navigation completes.
- Expected: None of the idle configuration triggers is disabled merely because the destination session has not been projected yet. The first configuration action creates or reuses the destination draft, persists the selected value, and does not require a second click. Running turns and pending approvals still disable the controls.
- Specs linked:
04-ux/08-component-spec.md(§11),04-ux/09-interaction-patterns.md(§5A) - Acceptance: C (new project/session composer)
- Milestone: M2
- Status: Draft
E2E-089: Top-bar model picker opens downward and switches model
- Preconditions: Chat route active; provider configured.
- Steps: 1) Click the model picker in the top bar. 2) Confirm the dropdown opens downward from the bar. 3) Select a different provider/model. 4) Open Settings from the command palette or application menu (the top bar no longer has a Settings action button).
- Expected: The menu lists enabled runnable providers with a default model and opens downward (anchored to the top bar); selecting updates the active session model; Settings opens from the command palette/menu. The model trigger ellipsizes long IDs.
- Specs linked:
04-ux/08-component-spec.md(§2, model dropdown),03-runtime/13-model-catalog-and-selection.md - Acceptance: C
- Milestone: M2
- Status: Draft
E2E-090: Transcript bottom reserve tracks the docked composer height
- Preconditions: Chat route active; a session with a transcript that exceeds one viewport so the last message sits near the docked composer.
- Steps: 1) Scroll the transcript to the latest message. 2) Measure the vertical gap between the last message and the top of the docked composer. 3) Type several lines into the composer so the draft grows multi-line. 4) Re-measure the gap and confirm the last message is still fully visible above the composer (not overlapped). 5) Collapse the draft back to a single line and confirm the gap shrinks back toward the tight ~16px reserve.
- Expected: The last message sits close above the composer (a small, consistent gap) rather than far below it; the reserve follows the composer's real height via
--composer-dock-heightso a taller multi-line draft pushes the transcript up instead of covering it. The jump-to-latest button and the minimap stay anchored just above the composer at every draft height. - Specs linked:
04-ux/08-component-spec.md(§4.3 MainChat layout) - Acceptance: C (send/UI), Quality
- Milestone: M2
- Status: Draft
E2E-091: Sending a prompt keeps the transcript at the latest turn
- Preconditions: Chat route active; the selected session contains enough history to overflow the transcript viewport; the transcript is at the latest message or has been scrolled upward.
- Steps: 1) Send a prompt. 2) Observe the transcript from the first send state through the persisted user-message event and the first streamed row. 3) Repeat after manually scrolling upward before sending.
- Expected: Send immediately hides the jump control and re-pins the transcript in the layout phase. The historical rows move upward only as the new turn is added; the viewport never flashes to the top of the conversation, and the new user turn plus streamed response remain visible at the bottom.
- Specs linked:
04-ux/08-component-spec.md(§4.3, §4.4),04-ux/09-interaction-patterns.md(§9.1, §10.4) - Acceptance: C (send/UI), Quality
- Milestone: M2
- Status: Draft
Workspace Open
E2E-012: Open a project directory
- Preconditions: App running; no project open.
- Steps: 1) Open project directory via UI. 2) Select a local folder.
- Expected: Project path displayed; tool paths resolve relative to project root.
- Specs linked:
03-runtime/15-workspace-ignore-rules.md - Acceptance: D (open project, show path)
- Milestone: M3
- Status: Draft
E2E-013: Read-only tools work in project
- Preconditions: Project directory open.
- Steps: 1) Ask agent to read a file in the project. 2) Observe result.
- Expected:
Readreturns immediately within project scope. In Agent mode, the agent activatesGloborGrepthroughToolSearchbefore using it; Plan keeps its read/search core available from the first request. All results remain within project scope. - Specs linked:
03-runtime/03-tools-and-permissions.md - Acceptance: E (Read/Glob/Grep work), D (tools based on project)
- Milestone: M3
- Status: Automated (protocol smoke: Read + Glob in sample project)
Permission Allow / Deny / Timeout
E2E-014: Write/Edit/Bash triggers permission card
- Preconditions: Agent mode; project open.
- Steps: 1) Ask agent to write a file. 2) Observe permission card.
- Expected: Permission card appears inline in the originating transcript with tool name, workspace, arguments preview, countdown, and allow/deny options. It creates no backdrop or modal and does not cover another session.
- Specs linked:
04-ux/03-permission-ux.md,03-runtime/03-tools-and-permissions.md - Acceptance: E (Write/Edit/Bash trigger confirmation)
- Milestone: M3
- Status: Draft
E2E-015: Denied permission blocks execution
- Preconditions: Permission card displayed.
- Steps: 1) Click deny on permission card. 2) Observe agent response.
- Expected: Tool not executed; agent receives denied result; no file changed.
- Specs linked:
03-runtime/03-tools-and-permissions.md - Acceptance: E (denied → not executed)
- Milestone: M3
- Status: Draft
E2E-016: Allowed permission executes tool
- Preconditions: Permission card displayed.
- Steps: 1) Click allow on permission card. 2) Observe agent response and UI.
- Expected: Tool executed; result returned to model and displayed in UI; file modified.
- Specs linked:
03-runtime/03-tools-and-permissions.md - Acceptance: E (allowed → result returned)
- Milestone: M3
- Status: Draft
E2E-017: Permission timeout defaults to deny
- Preconditions: Permission card displayed; no user action.
- Steps: 1) Wait 120 seconds without responding to permission card. 2) Observe outcome.
- Expected: Permission auto-denied after timeout; tool not executed.
- Specs linked:
03-runtime/03-tools-and-permissions.md - Acceptance: E (timeout → deny)
- Milestone: M3
- Status: Draft
E2E-018: Plan denies workspace mutation and plugin tools
- Preconditions: Plan mode active with Auto selected and a plugin agent tool registered.
- Steps: 1) Ask the Agent to call Write, Edit, and the plugin tool. 2) Ask it to run a Bash command that creates a marker file. 3) Repeat the Bash call with Ask selected and inspect the permission card.
- Expected: Write, Edit, and the plugin tool are not visible and direct attempts return
WRITE_DISABLED_IN_PLAN,EDIT_DISABLED_IN_PLAN, orPLUGIN_DISABLED_IN_PLAN; no file is changed by those tools. Bash runs without a confirmation under Auto and may mutate; under Ask it waits for the ordinary permission card. No Chat-mode error or command exists. - Specs linked:
03-runtime/03-tools-and-permissions.md - Acceptance: E (Plan policy)
- Milestone: M3
- Status: Documented (M6; no E2E execution requested)
E2E-019: Workspace-outside paths follow permission mode
- Preconditions: Agent or Plan mode; project open; a readable file exists outside both the session project and scratch roots.
- Steps: 1) With Ask selected, ask the agent to
Readthe external file and observe the inline permission card. 2) Deny once and verify no content is returned. 3) Repeat and allow once; verify the tool result carriesroot: "external"and the canonical absolute path. 4) Switch to Auto and repeat withGreporGlob; verify no card appears and the bounded result returns. 5) Repeat with Accept edits; verify the external read/search still asks for permission. - Expected: An explicit outside path never hard-fails before the user can decide. Ask and Accept edits request permission; Auto executes. Denial, timeout, or cancellation returns
TOOL_DENIEDand performs no operation. - Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/15-workspace-ignore-rules.md,04-ux/03-permission-ux.md - Acceptance: E (workspace-outside permission policy)
- Milestone: M3
- Status: Automated (host-core protocol/unit coverage; desktop journey pending)
E2E-019e: Bounded search parameters stay portable across platforms
- Preconditions: Agent or Plan mode; project open; the host tool catalog is available on macOS, Linux, or Windows.
- Steps: 1) Activate
Glob/Grepwhen deferred and inspect their schemas. 2) Search with workspace-relativepath,include,headLimit, andoutputMode: "filesWithMatches"or"count", first with a directory and then one explicit file. 3) CallReadwith a directory and follow its structured Glob suggestion. 4) Repeat with the platform's native shell selected, without changing the tool arguments. - Expected: The schemas expose the same bounded search controls on every platform;
Readdeclares file-only input,Globdeclares directory input, andGrepaccepts a file or directory.filesWithMatchesis accepted as the canonical output mode. A directory Read returnsINVALID_ARGUMENTwithsuggestedTool=Globand bounded args; the corrected call succeeds. Search results use workspace-relative paths inside the project and absolute paths only for approved external locations. No shell-specific path syntax is required and oversized results remain bounded. - Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/16-tool-result-limits.md, ADR 0057, ADR 0069 - Acceptance: E (bounded cross-platform search)
- Milestone: M5
- Status: Unit-covered (host-core and agent-runtime); live multi-platform protocol capture pending
E2E-019a: Scratch-directory writes stay out of the workspace (D114)
- Preconditions: Agent mode; project open; session started.
- Steps: 1) Ask the agent to produce a temporary/intermediate file (e.g. a one-off script). 2) Observe where it writes and whether a permission card appears. 3) Check
git statusand the work-panel state. 4) Delete the session and check<data_dir>/scratch/. - Expected: The file lands under
<data_dir>/scratch/<sessionId>/without a permission card; projectgit statusstays clean; no file or Review artifact tab opens for the scratch write; deleting the session removes the scratch directory. - Specs linked:
03-runtime/03-tools-and-permissions.md §4b,03-runtime/04-data-storage.md - Acceptance: E (temp files isolated from workspace)
- Milestone: M5
- Status: Partially automated (host-core unit tests: dual-root resolve, scratch write/read, PI_SCRATCH_DIR, sweep)
E2E-019b: Scratch containment matches workspace defenses (D114)
- Preconditions: Agent mode; project open.
- Steps: 1) Attempt Write with
..traversal from the scratch root. 2) Attempt Write through a symlink planted inside scratch pointing outside. 3) Attempt the same Write calls in Plan. - Expected: Both escapes return
PATH_OUTSIDE_WORKSPACE; Plan returnsWRITE_DISABLED_IN_PLANbefore any scratch path can make Write available. - Specs linked:
03-runtime/03-tools-and-permissions.md §4b - Acceptance: E (scratch root cannot be escaped)
- Milestone: M5
- Status: Automated (host-core unit tests)
E2E-019c: Permission modes govern high-risk approval (D115/D132)
- Preconditions: Agent mode; project open; global default
ask. - Steps: 1) With a newly inherited session and global default Ask every time, open the composer menu — expect Ask every time to be selected with no global-default/inherit label — then ask the Agent to write a workspace file and expect a permission card. 2) Switch the session chip to Accept edits; repeat — expect no card for Write/Edit but still a card for Bash. 3) Switch to Auto — expect no card for Bash either. 4) Create another inherited session after setting the global default to Accept edits in Settings — expect the composer chip and menu selection to display Accept edits directly and Write/Edit to be auto-allowed. 5) Switch the session to Plan, then Goal, with Auto set — expect Write/Edit/plugin denied but Bash allowed without confirmation.
- Expected: Effective mode = session override → global default → ask; Plan and Goal Write/Edit/plugin hard denies outrank every mode while their Bash follows the selected mode; the composer chip and menu always display the effective mode without default/inherit provenance.
- Specs linked:
03-runtime/03-tools-and-permissions.md §6,03-runtime/04-data-storage.md,08-meta/decisions-log.md(D115/D132) - Acceptance: E (permission modes resolve and enforce host-side)
- Milestone: M5
- Status: Partially automated (host-core unit tests: evaluate matrix, Plan policy precedence, session grants under ask; renderer source test: effective-only composer options and selection)
E2E-019d: Bash tool sees the user's login-shell toolchain (D181)
- Preconditions: Agent mode; project open; the OS user has a login shell (default on macOS) whose profile exports at least one tool not on the app's minimal GUI PATH (e.g. nvm/Homebrew).
- Steps: 1) Ask the agent to print
$PATHand run a toolchain check such ascommand -v node && node -v. 2) Compare with the PATH a fresh terminal shows for the same user. 3) Optionally remove~/.bash_profiletemporarily and repeat on a machine where only.zshrcinitializes the toolchain. - Expected: The Bash tool resolves tools the user's own login shell exports (nvm, pnpm, Homebrew) even though commands run through bash; the probed login PATH is a subset of the effective child PATH (bash profile may prepend/dedupe). A missing or wedged user shell degrades to the host PATH without failing the tool.
- Specs linked:
03-runtime/03-tools-and-permissions.md §5,08-meta/decisions-log.md(D181), ADR 0045 - Acceptance: E (user toolchain visible in Bash tool)
- Milestone: M5
- Status: Automated (host-core unit tests: login-PATH probe + child-PATH injection)
Session Persistence
E2E-020: Session survives restart
- Preconditions: Session with message history exists.
- Steps: 1) Quit app. 2) Relaunch. 3) Open session list.
- Expected: Previous session appears; messages recoverable.
- Specs linked:
03-runtime/04-data-storage.md,03-runtime/10-session-state-machine.md - Acceptance: F (session survives restart)
- Milestone: M2
- Status: Automated (protocol smoke: host-level persistence; full restart lane manual)
E2E-021: Delete session works
- Preconditions: Session exists.
- Steps: 1) Delete a session. 2) Observe session list.
- Expected: Session removed from list; data gone.
- Specs linked:
03-runtime/04-data-storage.md - Acceptance: F (delete session)
- Milestone: M2
- Status: Draft
E2E-036: Localized import grouping starts collapsed
- Preconditions: Supported local agent stores contain importable sessions across at least two project paths and two sources, including one session without a project path; the app can be launched once with an English system locale and once with a Simplified Chinese system locale.
- Steps: 1) Launch in English and open Settings → Import. 2) Scan for sessions. 3) Inspect the initial source groups. 4) Expand one group and select a session. 5) Change Group by to Project path. 6) Switch back to Source. 7) Repeat the flow after launching with a Simplified Chinese system locale.
- Expected: Source/来源 is the initial grouping; all groups are collapsed after the scan and after either grouping change; project-path mode shows exact project paths and a final No project/未关联项目 group; expanding one group leaves the others collapsed; the selected session remains selected across grouping changes; counts, dates, selection labels, accessible names, and the import result use the active locale without raw keys or unresolved double-brace placeholders.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/02-i18n-english-first.md,04-ux/08-component-spec.md - Acceptance: F (session import review)
- Milestone: M2
- Status: Draft
E2E-037: Import creates durable project entries
- Preconditions: Import candidates include two sessions at path A, one at path B, and one without a project path; neither project is the active workspace.
- Steps: 1) Import all candidates. 2) Open Settings → Project archive. 3) Inspect and expand paths A and B. 4) Return home and inspect Temporary sessions. 5) Repeat the import.
- Expected: Project archive contains exactly one durable row for A and one for B; the matching imported sessions appear under their exact project rows; the path-less session appears only under Temporary sessions; the active workspace does not change; repeating import duplicates neither sessions nor project rows; no missing filesystem path is created on disk.
- Specs linked:
03-runtime/04-data-storage.md,04-ux/01-ui-ia.md,04-ux/08-component-spec.md - Acceptance: F (session/project persistence)
- Milestone: M2
- Status: Draft
E2E-038: Settings owns the project archive destination
- Preconditions: App running with at least one configured provider, one supported local session store, one retained project, and one archived project.
- Steps: 1) Open Settings. 2) Inspect the complete settings rail. 3) Open Basics and change the theme in its Appearance card using the theme preview cards. 4) Open 全局 AI and inspect the Permissions and Context management cards. 5) Open Shortcuts and inspect the Keyboard shortcuts card. 6) Open Instructions and save global instructions. 7) Open Model configuration and inspect the provider studio. 8) Open Import, Project archive, and Info in order. 9) Search Settings for "project" or "archive". 10) In Project archive, read the overview counters and compare them with the rendered sections. 11) Switch the sort control from Recent to Name. 12) Search for a known session title, inspect its expanded project row, then reveal more than eight sessions; clear the search with the clear affordance. 13) Open a row menu, dismiss it with Escape and with an outside press. 14) Restore the archived project, then activate it. 15) Return to the app shell and open Plugins.
- Expected: The rail contains exactly Basics, 全局 AI/AI, Shortcuts, Instructions, Model configuration, Import, Project archive, and Info in that order, each with its semantic Lucide icon (Sliders / Sparkles / Keyboard / FileText / Bot / Download / Archive / Info); Appearance and Providers remain merged into their owning destinations; Permissions and Context management live under 全局 AI; Keyboard shortcuts and global instructions have their own destinations; Developer lives under Info; Project archive shows active, closed, and archived durable rows without a visibility toggle, grouping them under the always-visible Pinned / All projects / Archived sections (D168) with per-section counts. The overview banner's projects, open, archived, and session counters agree with the rendered rows; sorting by Name reorders rows inside every section without hiding any; search matches project fields and session titles and reports a match count, a session-title result expands its owning project, lists sessions by latest activity with relative update times, and reveals history in batches of eight; clearing the search restores the complete index. The row menu closes on Escape and on an outside press. Restore keeps the archive open and activation returns to chat with the restored project retained in the sidebar; the home sidebar and global page results have no standalone Projects destination; Settings search finds Project archive; Plugins remains an independent app-shell destination.
- Specs linked:
04-ux/06-settings-ia.md,04-ux/01-ui-ia.md,03-runtime/11-provider-model-system.md - Acceptance: B (model configuration), F (session import)
- Milestone: M4
- Status: Unit-covered (
settings-project-archive.test.mjs,sidebar-navigation.test.mjs); rendered scenario Draft
E2E-091: Appearance card selects theme and language via preview cards
- Preconditions: App running on macOS with a Simplified Chinese system locale.
- Steps:
- Open Settings → Basics.
- In the Appearance card, confirm the Theme row shows three preview cards (System, Light, Dark) with the System card first; select Dark and confirm the selected card shows a check badge and the UI switches to dark.
- Select Light and confirm the UI switches to light.
- In the Language row, confirm three preview cards (Auto, 简体中文, English); with the OS locale set to Chinese, the Auto card description reads "当前:简体中文" and selecting Auto applies Simplified Chinese.
- Select English and confirm the UI switches to English; select 简体中文 and confirm it switches back.
- Expected: Theme and Language are card grids (not native selects), each with a selected check badge and a per-option description; the Auto language card resolves the OS locale through the main process (
app.getLocale()), passes it safely through the sandboxed preload bridge, and reflects it inline; switching options updates the live UI without a reload. - Specs linked:
04-ux/06-settings-ia.md,04-ux/02-i18n-english-first.md - Acceptance: A (core shell), H (localization)
- Milestone: M4
- Status: Documented
E2E-039: Settings titlebar drag moves the window
- Preconditions: App running windowed on macOS with Settings open.
- Steps: 1) Record the window position. 2) Drag the empty 46px band above the settings rail. 3) Drag the same band above the content pane. 4) Use Back, search, Project archive, and navigation controls.
- Expected: Either top-band drag moves the native window; Back, search, and navigation remain interactive and never initiate a window drag.
- Specs linked:
04-ux/06-settings-ia.md,04-ux/01-ui-ia.md - Acceptance: Quality (key operations feel polished)
- Milestone: M5
- Status: Draft
E2E-043: Settings content follows window width
- Preconditions: App running windowed on macOS with Settings open.
- Steps: 1) Open Basics at the default window width and record the content-card width. 2) Expand the window to 1600px wide. 3) Open Model configuration, Import, and Project archive. 4) Shrink the window to the supported 1040px minimum.
- Expected: The right-side content cards expand and contract with the available pane at every tested width; the 275px rail and pane gutters remain stable; controls remain visible without clipping or horizontal page scrolling.
- Specs linked:
04-ux/06-settings-ia.md,04-ux/07-ui-design-system.md - Acceptance: Quality (key operations feel polished)
- Milestone: M5
- Status: Unit-covered (
settings-responsive-layout.test.mjs); scenario Documented
E2E-040: Codex-style tool activity survives transcript reload
- Preconditions: Provider configured; project open; a session can run a successful tool and a failing or aborted tool.
- Steps: 1) Run representative read, search, and command tools. 2) Inspect the collapsed processing header while it is active. 3) Wait for completion and expand the processing group. 4) Expand a completed row and copy its output. 5) Click the vertical rule beside the expanded row, then keyboard-focus and activate the processing group's vertical rule. 6) Reload the session and expand the restored group.
- Expected: Consecutive calls are collapsed by default under one localized processing header that updates and then freezes its elapsed time and shows a step count. Expanded calls use transparent semantic activity rows with an action icon, natural-language verb, monospace primary argument, and quiet disclosure. The processing group uses the full assistant-column width, so a short label or payload does not shrink expanded details into a content-sized chip. Each expanded-content vertical rule is a pointer and keyboard-focusable collapse control for its owning disclosure. Nested expansion shows output before raw input in clamped scroll regions. Live partial output updates in place. Reloaded rows preserve the tool name, arguments, result, and status.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (chat stream), E (tools), F (persistence)
- Milestone: M3
- Status: Draft
E2E-041: Conversation minimap navigates long transcripts
- Preconditions: A session contains enough user and assistant turns to scroll beyond one viewport and densely fill the minimap, including one AI response emitted as multiple assistant fragments around tool activity; a second session has at least two eligible turn markers that still fit in one viewport.
- Steps: 1) Open the long session. 2) Scroll through the transcript and observe the active minimap marker. 3) Hover a marker and inspect its preview. 4) Use keyboard focus to reach another marker. 5) Activate a marker. 6) Open a session with fewer than two eligible turn markers. 7) Open the multi-message session that still fits one viewport. 8) Resize the long session window taller until content no longer overflows, then shorter again. 9) At a short window height, inspect and activate the first and last markers near the minimap's vertical bounds, including from the titlebar-facing side.
- Expected: The rail contains one marker per visible user turn and one per AI response. Multiple assistant fragments between two user messages share a single marker and combined bounded preview, while tool-only rows create no marker and do not split the response. The marker near the upper-third reading anchor exposes
aria-current; hover and focus show the same localized sender and preview; nearby markers magnify horizontally without shifting the stack; activation smoothly scrolls to the first contentful message in that response; the rail is absent when fewer than two eligible markers exist or when content does not overflow one viewport; the rail reappears once overflow returns after a resize. Dense markers remain centered in the unobstructed span between the 46px titlebar and docked composer, compress uniformly, and remain interactive without entering the native window drag region. - Specs linked:
04-ux/08-component-spec.md - Acceptance: C (chat stream), Quality (keyboard and long-thread navigation)
- Milestone: M3
- Status: Draft
E2E-042: Pre-v7 storage archives via breaking reset; transcripts live in session files
- Preconditions: A fixture data directory contains a
pi.sqlitewhosePRAGMA user_versionis between 1 and 6 (pre-D119 content-in-DB schema) with representative rows. - Steps: 1) Start host-core against the fixture. 2) Create a session and append messages through host RPC. 3) Stop and restart host-core. 4) Reload the session through RPC and inspect the data directory.
- Expected: Host-core renames the legacy file to exactly one
pi.sqlite.v6.bak, bootstraps a fresh schema-v7 database (index-onlymessages), writessessions/<id>.jsonlwith a session-header line plus one line per message, reloads the transcript from the file after restart with identical logical results, and deleting the session removes both the index rows and the session files. No Electron-owned persistence file is authoritative. - Specs linked:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md, ADR 0014 - Acceptance: F (persistence), H (reset failures are diagnosable)
- Milestone: M2
- Status: Unit-covered (
db::tests::archives_pre_v7_database_and_starts_fresh,sessions::tests::transcript_survives_reopen_from_file,sessions::tests::delete_session_removes_transcript_files); full fixture scenario Draft
Plugin Load / Command / Disable
E2E-022: Load local plugin
- Preconditions: App running; sample plugin available at local path.
- Steps: 1) Open Extensions from the sidebar footer Plugins icon. 2) Choose Load local plugin from the header overflow menu. 3) Enable it with the row switch.
- Expected: Plugin loads; manifest validated; contributions registered; the row appears under Active with a Local tag.
- Specs linked:
07-plugins/01-plugin-system.md,07-plugins/05-plugin-lifecycle.md - Acceptance: G (load local plugin)
- Milestone: M4
- Status: Automated (protocol smoke: plugins.loadDev)
E2E-022A: Create a plugin from a template
- Preconditions: App running; an empty folder available.
- Steps: 1) Open Extensions. 2) Choose New plugin from template in the header overflow menu (or use the empty-state button). 3) Pick each of the four templates in turn and read its description. 4) Choose the folder. 5) Cancel the folder picker on a second attempt.
- Expected: The picker lists exactly
panel-basic,agent-tool-basic,skill-pack,full-demo, each named and described in the active locale; choosing a folder writes the template files, loads the plugin as a development plugin, refreshes the list, then opens that folder as the active project — the app lands on chat with the new folder as the workspace, it appears in the sidebar project list, and the toast reads "<name> created, loaded, and opened for development"; the plugin's contributions are immediately usable and the built-in plugin-development skill is active in the new workspace. That skill teaches the current globalpiAPI (onLoad()pluspi.commands.register) and the fixedwindow.pluginBridgeboundary, and does not teach the retiredonLoad(pi)/pi.registerCommandshape. A canceled folder picker changes nothing and reports no error. - Specs linked:
07-plugins/10-plugin-devex.md,../../plugin-development.md, ADR 0039 - Acceptance: G (create plugin from template)
- Milestone: Post-MVP
- Status: Automated in part (
apps/desktop/test/plugin-template-action.test.mjs: channel, template-id parity with the devkit, locale coverage, canceled-pick ordering, project activation); UI walk-through Documented
E2E-022B: Development plugin hot reload
- Preconditions: A plugin loaded from a local folder and enabled.
- Steps: 1) Edit
main.jsto change a command title and save. 2) Save several files at once. 3) Introduce a syntax error and save. 4) Fix the error and save. 5) Add a new permission tomanifest.jsonand save. 6) Click Reload on the development-plugin card. 7) Edit again and restart the app. - Expected: The single edit reloads the plugin without re-picking the folder and the command palette shows the new title; a save burst produces one reload, and writes under
dist/ornode_modules/produce none; the syntax error reports a reload failure without crashing the app, and the fixing save recovers the plugin; the added permission refuses automatic reload withPERMISSION_DENIED, while the explicit Reload action loads the registered folder, refreshes the permission ceiling, and reports success; a later edit uses the refreshed ceiling, and after a restart the folder is still watched. - Specs linked:
07-plugins/10-plugin-devex.md§7,07-plugins/13-plugin-permissions-matrix.md, ADR 0039, ADR 0075 - Acceptance: G (hot reload), D (permissions cannot widen without review)
- Milestone: Post-MVP
- Status: Automated in part (
apps/desktop/test/plugin-hot-reload.test.mjs: debounce, ignore list, permission ceiling, recovery, teardown); manual edit loop Documented
E2E-022C: Check, pack, install round-trip
- Preconditions: A scaffolded plugin directory.
- Steps: 1)
pnpm pi-plugin check <dir>. 2) Delete the file named bymainand runcheckagain. 3) Restore it, declarecontributes.skillswithoutagent.prompt.inject, and runcheckagain. 4)pnpm pi-plugin pack <dir>. 5) Install the resulting.piplugfrom the plugins page. 6) Ask the agent to runPluginCheckandPluginPackon the same directory. - Expected: A scaffolded plugin checks clean and reports its file count and size; the missing
mainis an error that blockspack; the inert-skills case is a warning that does not block;packwritesdist/<id>-<version>.piplugwith store-only entries and prints its sha256; the package installs through the normal permission review and appears under Active; the agent tools produce the same verdicts and refuse any directory outside the session workspace. - Specs linked:
07-plugins/10-plugin-devex.md§5–§6,07-plugins/06-plugin-packaging.md, ADR 0039 - Acceptance: G (local packaging round-trip)
- Milestone: Post-MVP
- Status: Automated in part (
packages/plugin-devkitvitest: scaffold→check→pack per template, store-method headers, every check rule); install step Documented
E2E-023: Plugin command in global search and executes
- Preconditions: Plugin loaded and enabled.
- Steps: 1) Open global search (Cmd/Ctrl+K or Cmd/Ctrl+Shift+P). 2) Find the plugin command under the Commands section. 3) Execute.
- Expected: Command appears in global search results; execution produces expected result.
- Specs linked:
07-plugins/09-plugin-command-palette.md - Acceptance: G (plugin command appears and executes)
- Milestone: M4
- Status: Draft
E2E-024: Plugin registers and calls agent tool
- Preconditions: Plugin loaded; plugin declares an agent tool.
- Steps: 1) Ask agent to use the plugin's tool. 2) Observe permission card if required. 3) Allow.
- Expected: Tool registered with forced prefix (
plugin_<id>_<name>); call succeeds. - Specs linked:
07-plugins/03-plugin-api.md,07-plugins/13-plugin-permissions-matrix.md - Acceptance: G (plugin agent tool)
- Milestone: M4
- Status: Automated (protocol smoke: dispatch roundtrip host->runner->host; in-app JS execution via PluginRuntime)
E2E-024G: Marketplace detail sheet shows README, permissions, versions
- Preconditions: Official marketplace catalog available.
- Steps: 1) Open Extensions → Marketplace. 2) Open details for
demo.workspace-summary. 3) Inspect README / risk-grouped permissions / version rows. 4) Pick a version and install after permission review. 5) Dismiss the sheet with Escape and by clicking the scrim. - Expected: Detail sheet loads via
market.getDetail; README, safety notes, and per-risk permission explanations render; the picked version drives the sticky install action; Escape and scrim both close the sheet without closing the permission dialog underneath. - Specs linked:
07-plugins/07-plugin-marketplace.md - Acceptance: G (marketplace detail UX)
- Status: Documented
E2E-024F: Refresh official remote marketplace repository
- Preconditions: Network available to GitHub raw content.
- Steps: 1) Open Extensions → Marketplace. 2) Use the header Refresh marketplace action. 3) Confirm the source line points at
vastsa/pi-desktop-plugins. - Expected: Catalog refreshes from the remote official repo; card grid updates; offline fallback still works if fetch fails.
- Specs linked:
07-plugins/07-plugin-marketplace.md - Acceptance: G (remote marketplace source)
- Status: Documented / host-core unit covered
E2E-024B: Marketplace install with permission review
- Preconditions: App running; official market catalog available.
- Steps: 1) Open Extensions → Marketplace. 2) Install
demo.workspace-notes. 3) Read the risk-tiered permission dialog. 4) Accept high-risk permissions. - Expected: Permissions are grouped High / Medium / Low with plain-language explanations before any download; plugin installed from the marketplace package, checksum verified, permissions granted, panel/tools available; the installed tab and risk-grouped rows reflect the new plugin without a separate overview card row.
- Specs linked:
07-plugins/07-plugin-marketplace.md,07-plugins/13-plugin-permissions-matrix.md - Acceptance: G (marketplace install + permission review)
- Status: Documented / host-core covered by unit tests + protocol methods
E2E-024C: Plugin package install and auto-update path
- Preconditions: Marketplace catalog has a newer version or local
.piplug. - Steps: 1) Install package from the header overflow menu. 2) Enable auto-update from the row overflow menu. 3) Run Check for updates, then Apply automatic updates.
- Expected: The row moves to Updates available and the update banner reports the count; permissions the new version adds are tagged New in the review dialog; auto-update applies only when the permission diff is empty or pre-granted.
- Specs linked:
07-plugins/06-plugin-packaging.md,07-plugins/08-plugin-signing-updates.md - Acceptance: G (package install + update policy)
- Status: Documented
E2E-024H: Installed plugins surface state, risk, and failures
- Preconditions: At least one enabled plugin, one disabled plugin, and one plugin whose load failed.
- Steps: 1) Open Extensions → Installed. 2) Read the tab and group counts. 3) Confirm the failed plugin sits under Needs attention with its error message. 4) Search by author and by permission. 5) Clear the search.
- Expected: Rows group as Needs attention / Updates available / Active / Turned off with counts;
status: "error" | "load_error"renders the error message inline instead of being silent; each row defaults to a two-line name/id/version summary, while a Details disclosure reveals risk-tinted permission chips, capabilities, and resident service status; the single scope trigger opens explained Off / This project / Everywhere choices and the project picker; row icon actions expose hover/focus labels; the result count reflects the filtered subset and clearing restores every group. - Specs linked:
04-ux/01-ui-ia.md,07-plugins/13-plugin-permissions-matrix.md - Acceptance: G (installed plugin management)
- Status: Documented
E2E-024D: Isolated plugin panel host bridge
- Preconditions: Plugin with
ui.panelenabled. - Steps: 1) Open the plugin panel on macOS. 2) Confirm the 46px custom titlebar, inset traffic lights, title truncation, drag behavior, and content clearance. 3) Repeat on Windows and Linux; exercise minimize, maximize, restore, close, keyboard focus, light/dark system appearance, and reduced motion. 4) Reopen a minimized panel. 5) Invoke panel bridge APIs (
ui.showToast, optional fs/net with grants). - Expected: Panel runs in its sandboxed window/partition; macOS uses hidden-inset native traffic lights while Windows/Linux use a frameless host-owned titlebar with working, accessible 112px window controls; plugin content starts below the titlebar without losing its original top padding; reopening restores the existing panel; bridge calls remain permission-checked and the host remains stable on panel close.
- Specs linked:
03-runtime/01-ipc-protocol.md,04-ux/08-component-spec.md,07-plugins/03-plugin-api.md,07-plugins/04-plugin-security.md, ADR 0081 - Acceptance: G (isolated panel)
- Status: Documented
E2E-024E: High-risk plugin APIs require grants
- Preconditions: Notes plugin installed with explicit grants.
- Steps: 1) Call
fs.writeText/net.fetch/shell.openExternalthrough plugin runtime or panel bridge. 2) Revoke one permission and retry. - Expected: Granted calls succeed with audit; revoked/undeclared calls fail with
PERMISSION_DENIEDand do not crash the app. - Specs linked:
07-plugins/13-plugin-permissions-matrix.md,07-plugins/04-plugin-security.md - Acceptance: Security + G
- Status: Documented
E2E-024I: Plugin skills reach the agent and load on demand
- Preconditions:
examples/plugins/helloenabled withagent.prompt.injectgranted; a second copy of the manifest without that permission available; one workspace that is a plugin directory and one that is not. - Steps: 1) Start a session and ask the agent what skills it has. 2) Ask it to follow the Hello demo skill so it calls the
Skilltool. 3) Edit the skill document and repeat step 2. 4) Disable the plugin and start a new turn. 5) Load the variant withoutagent.prompt.injectand repeat step 1. 6) Declare a document larger than the per-skill cap. 7) Open each of the two workspaces in turn. - Expected: The catalog lists the skill id, name, and trimmed description but no body, after the built-in skills and before the project instruction chain; the
Skillschema is loaded throughToolSearchonly when requested and reads the edited file without a restart; disabling the plugin rebuilds the runtime so the skill disappears from the next turn; the variant without the permission loads normally and contributes no skills; the oversized document is skipped with an audit line rather than clamped into the prompt; the built-inplugin-developmentskill is catalogued in the plugin workspace and absent in the other, whilePluginCheckis listed in the bounded on-demand tool catalog in both. - Specs linked:
07-plugins/02-plugin-manifest-schema.md,07-plugins/04-plugin-security.md§7.1,07-plugins/10-plugin-devex.md, ADR 0039, ADR 0037, D174 - Acceptance: G (skill activation) + E (tools & permissions) + D (high-risk permission gating)
- Status: Unit-covered (
plugin-skills.test.mjs, agent-runtime prompt/digest tests); agent-facing scenario Draft
E2E-024J: Plugin theme applies and falls back when withdrawn
- Preconditions:
examples/plugins/helloenabled withui.themegranted; a plugin whose CSS uses@importor a remoteurl()available for the rejection case. - Steps: 1) Open Settings → General → Theme and pick
Hello Midnight. 2) Restart the app. 3) Disable the providing plugin. 4) Re-enable it, then uninstall it. 5) Load the plugin with unsafe CSS. - Expected: The plugin theme appears in the picker alongside the built-ins and applies immediately; the choice survives restart as
plugin:demo.hello:midnight; disabling or uninstalling the provider falls back tosysteminstead of an unstyled shell; unsafe CSS is refused at load with the reason logged and no<style>element injected. - Specs linked:
07-plugins/04-plugin-security.md§3.1,04-ux/07-ui-design-system.md, D175 - Acceptance: G (theme contribution) + Security
- Status: Unit-covered (
plugin-themes.test.mjs,theme-cssSDK tests); visual scenario Draft
E2E-024K: Plugin MCP server tools reach the agent
- Preconditions: A plugin declaring one
stdioand onehttpMCP server against local stubs;mcp.server.localandmcp.server.remotegranted; a settings key holding the stub credential. - Steps: 1) Enable the plugin and confirm no server process starts yet. 2) Ask the agent to call a discovered tool. 3) Inspect the stub's received environment/headers. 4) Make the stub fail a call and time one out. 5) Disable the plugin.
- Expected: Servers connect lazily on first use; tools appear as
plugin_demo_*_<serverId>_<tool>atrisk: "medium"with per-call audit; the stdio child receives only the declaredenvvalues plus PATH/temp/locale, never host provider keys; failures and timeouts return tool errors without crashing the plugin or the host; disable disconnects both servers. - Specs linked:
07-plugins/02-plugin-manifest-schema.md,07-plugins/04-plugin-security.md§8.1, ADR 0038, D176 - Acceptance: G (MCP bridge) + E (tools & permissions) + Security
- Status: Unit-covered (
plugin-mcp.test.mjsstdio + HTTP stubs); agent-facing scenario Draft
E2E-024L: Resident plugin service is supervised and visible
- Preconditions:
examples/plugins/helloenabled withbackground.servicegranted. - Steps: 1) Open Extensions → Installed and expand Details on the plugin row to read the
Greeter heartbeatchip. 2) Kill the plugin's utility process and watch the chip. 3) Kill it repeatedly past the restart ceiling. 4) Disable and re-enable the plugin. 5) Revokebackground.serviceand reload. - Expected: The Details disclosure exposes a chip that reports
runningafter load; a kill showsfailedthenrunningagain with an incremented restart count and backoff between attempts; past five attempts the plugin staysfailedand stops retrying; manual disable/enable cancels the pending timer and resets the counter; without the permission the service never starts and the skip is audited. - Specs linked:
07-plugins/05-plugin-lifecycle.md§3.1, ADR 0040, D177 - Acceptance: G (resident services)
- Status: Unit-covered (
plugin-services.test.mjssupervision + backoff); manual kill scenario Draft
E2E-024M: Bus messages cross plugins only as declared
- Preconditions: Two plugins enabled — one publishing
demo.*topics, one subscribingdemo.**— withbus.publish/bus.subscribegranted. - Steps: 1) Run the publisher's command and watch the subscriber. 2) Publish a topic absent from
contributes.bus.publish. 3) Subscribe to a pattern absent fromcontributes.bus.subscribe. 4) Publish a payload over 64KB and exceed 100 publishes in 10s. 5) Unload the subscriber and publish again. - Expected: The subscriber receives
{ topic, from, payload, at }and the publisher never receives its own message; undeclared publish and subscribe both failPERMISSION_DENIEDwith an audit line naming the topic; the oversized payload and the rate burst failLIMIT_EXCEEDED/RATE_LIMITED; publishing to a departed subscriber succeeds with a smaller fan-out and no host error. - Specs linked:
07-plugins/02-plugin-manifest-schema.md§5.1,07-plugins/04-plugin-security.md§5.1, ADR 0040, D178 - Acceptance: G (message bus) + Security
- Status: Unit-covered (
plugin-bus.test.mjsdelivery, filtering, caps); two-plugin manual scenario Draft
E2E-024N: Extensions page density and theme-readable actions
- Preconditions: App running with at least one installed extension and one available marketplace action; dark and light themes available.
- Steps: 1) Open Extensions in dark theme. 2) Confirm the header and Installed / MCP / Skills / Subagents / Marketplace tabs reach the content without a four-card numeric overview band or explanatory header/section paragraphs. 3) Confirm installed rows begin as a quiet two-line summary, then expand Details on one row and inspect its capabilities, service status, and permissions. 4) Use the compact scope control and its explained scope menu, contextual primary action, and a secondary update/action button. 5) Switch to light theme and repeat. 6) Keyboard-focus the Details disclosure, scope states, and each action.
- Expected: The four numeric overview cards are absent; tab counts, installed group counts, and any update alert remain available in their relevant surfaces. The page header, section headers, empty states, and update alert use compact labels and actions; decision-specific explanations remain in disclosures, details, and dialogs. Installed rows keep their default height low while the disclosure exposes the complete secondary readout. The scope trigger stays aligned with the row action rail, its menu explains each state, and icon actions remain visible at rest while showing labels on hover and focus. Primary and secondary buttons keep visible semantic surfaces, text, borders, hover states, and focus rings in both themes, and keyboard focus does not depend on pointer hover.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md,07-plugins/07-plugin-marketplace.md, ADR 0058, D196 - Acceptance: G (Extensions page) + Quality
- Status: Unit-covered (
extensions-page.test.mjs,plugins-page-style.test.mjs); visual scenario Draft
E2E-024O: Marketplace hides development-only sample plugins
- Preconditions: App running; the official catalog contains the development fixtures
demo.helloordemo.workspace-summaryand at least one product plugin. - Steps: 1) Open Extensions → Marketplace with an empty search. 2) Search for
Hello,Workspace Notes, andWorkspace Summaryin turn. 3) Inspect the category filters and result cards. 4) Open Installed and verify an already-installed sample remains manageable. - Expected: Entries whose IDs begin with
demo.never appear in the marketplace cards, categories, or search results; product plugins remain discoverable. An already-installed sample is still listed under Installed so it can be disabled or uninstalled rather than becoming an unmanaged runtime. - Specs linked:
07-plugins/07-plugin-marketplace.md,04-ux/01-ui-ia.md - Acceptance: G (Extensions page) + Quality
- Status: Unit-covered (
extensions-page.test.mjs); visual scenario Draft
E2E-025: Disable plugin removes contributions
- Preconditions: Plugin enabled and contributions visible.
- Steps: 1) Disable the plugin on the Extensions page. 2) Check global search and agent tools.
- Expected: Commands and tools disappear; no leftover contributions.
- Specs linked:
07-plugins/05-plugin-lifecycle.md - Acceptance: G (disable removes contributions)
- Milestone: M4
- Status: Automated (protocol smoke: disable clears enabled flag; global search removal manual)
E2E-026: Plugin error does not crash app
- Preconditions: Plugin loaded.
- Steps: 1) Trigger a scenario where plugin throws an error. 2) Observe app behavior.
- Expected: App remains running; error is captured and reported; no crash.
- Specs linked:
07-plugins/04-plugin-security.md - Acceptance: G (plugin error → no crash)
- Milestone: M4
- Status: Draft
Security — No Secret Leakage
E2E-027: Secrets not in logs for normal flows
- Preconditions: Provider configured with API key.
- Steps: 1) Perform a chat session. 2) Inspect log files.
- Expected: API keys / tokens not present in any log output for normal flows.
- Specs linked:
05-security/01-security.md,03-runtime/09-logging-and-observability.md - Acceptance: H (secrets not in logs)
- Milestone: M2
- Status: Automated (protocol smoke: provider list carries no secret material)
E2E-028: Renderer has no Node integration
- Preconditions: App running.
- Steps: 1) Inspect renderer process flags.
- Expected:
nodeIntegration: false;contextIsolation: true; preload is the only bridge. - Specs linked:
05-security/01-security.md - Acceptance: Security (no Node in renderer)
- Milestone: M1
- Status: Draft
E2E-029: Unwhitelisted IPC cannot be called
- Preconditions: App running.
- Steps: 1) Attempt to invoke an IPC method not on the whitelist from renderer.
- Expected: Call blocked; no data returned; error or no response.
- Specs linked:
03-runtime/01-ipc-protocol.md,05-security/01-security.md - Acceptance: Security (IPC whitelist enforced)
- Milestone: M1
- Status: Draft
E2E-030: Plugin cannot read API key
- Preconditions: Plugin loaded; provider configured.
- Steps: 1) Plugin attempts to access provider secret via any API. 2) Observe result.
- Expected: Access denied; no secret data returned to plugin.
- Specs linked:
07-plugins/04-plugin-security.md,03-runtime/14-secrets-storage.md - Acceptance: Security (plugin cannot read API key)
- Milestone: M4
- Status: Draft
E2E-031: Error codes are stable and readable
- Preconditions: App launched through the normal desktop development command; provider configured.
- Steps: 1) Select or enter a model ID that the provider rejects. 2) Send a prompt. 3) Inspect the assistant error message and its detail disclosure. 4) Switch sessions and reload the failed session. 5) Repeat with an invalid provider key.
- Expected: The run stops and the transcript contains one durable
role=assistant,status=errormessage instead of a toast, floating banner, or blank row. It shows a localized summary and stableMODEL_NOT_CONFIGUREDorPROVIDER_UNAUTHORIZEDcode. Details expose the redacted provider response plus provider/model IDs and can be copied; no API key or Authorization value appears. The configuration failure links to settings, retriable failures offer Retry, the composer becomes usable again, and reload preserves the error message. The development launch executes a sidecar rebuilt from current runtime source. - Specs linked:
03-runtime/02-agent-runtime.md,03-runtime/07-process-model.md,03-runtime/08-error-codes.md - Acceptance: C (failed chat settles), H (errors expose stable codes)
- Milestone: M2
- Status: Unit-covered (agent-runtime error message/redaction, host persistence, desktop transcript contract, and predev build contract); full Electron UI scenario Draft
Hardening (M5)
E2E-032: Backend crash triggers supervised restart
- Preconditions: App running; host-core and sidecar healthy.
- Steps: 1) Kill the host-core (or sidecar) process externally. 2) Observe app behavior.
- Expected: In-flight RPCs fail fast (no long hang);
hostStatusshows degraded then restored; child restarts with backoff; after 3 failed restarts in 2 minutes the app stays degraded with a visible fatal status. - Specs linked:
03-runtime/07-process-model.md - Acceptance: Quality (main path no crash)
- Milestone: M5
- Status: Automated (
scripts/e2e-supervision.mjs— SIGKILL host-core, assert restart + healthy RPC)
E2E-033: Window bounds persist across restart
- Preconditions: App running with default window size.
- Steps: 1) Resize/move the window to distinct normal bounds A (≥1040×700), maximize before the 600ms save debounce ends, quit, and relaunch. 2) Restore, resize/move to distinct bounds B, quit before the debounce ends, and relaunch again.
- Expected: Each relaunch restores the latest normal bounds (A, then B), including when quit occurs while maximized or with a pending save. Maximized/fullscreen geometry is never stored as normal bounds; invalid/tiny saved bounds fall back to the 1200×800 default.
- Specs linked:
04-ux/09-interaction-patterns.md - Acceptance: Quality (key operations feel polished)
- Milestone: M5
- Status: Documented
E2E-034: NDJSON log files are written and redacted
- Preconditions: Fresh profile; provider configured; one chat turn completed.
- Steps: 1) Run a prompt with a tool call. 2) Open
~/.pi-desktop/logs/. 3) Inspect the categorized files underapp/,host/, andagent/. - Expected: NDJSON records exist with
ts/level/channel/category/message; tool start/end carrysessionId/toolCallId; no API key material appears; each category file rotates at 5 MB. Additionally (D183)host/timing.loghas onetool timingrecord per tool call carryingprompted/permission_wait_ms/execute_ms/overhead_ms/total_ms, andagent/timing.loghas matching[timing] kind=tooland[timing] kind=modellines, so an approval wait, a slow tool body, and a slow provider are distinguishable for the sametoolCallId. - Specs linked:
03-runtime/09-logging-and-observability.md - Acceptance: H (diagnostics)
- Milestone: M5
- Status: Documented
E2E-035: Bash tool uses the effective catalog shell
- Preconditions: Workspace open; agent mode.
- Steps: 1) Select an available catalog shell and run
Bash(e.g.echo ok). 2) Make the persisted selection unavailable and inspect the effective catalog before running the next turn. 3) Run with the previous turn snapshot. - Expected: The unchanged
Bashprotocol call uses the selected catalog entry. A later unavailable persisted choice falls back to the first available platform shell and marks the catalog fallback; the previous turn snapshot is rejected as stale byCOMMAND_SHELL_CHANGEDrather than silently changing shell. No partial execution occurs; E2E-113 covers the stale identity path. - Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md,03-runtime/08-error-codes.md, ADR 0054 - Acceptance: H (errors expose stable codes)
- Milestone: M5
- Status: Unit-covered (
tools::shell::tests); scenario Documented
E2E-044: Development launch uses PI-Desktop Dock branding
- Preconditions: macOS development checkout with canonical
build/icon_1024.png. - Steps: 1) Run
pnpm dev. 2) Inspect the running application's Dock icon. - Expected: The Dock shows the PI-Desktop brand icon, not Electron's default icon; packaged builds continue to use
build/icon.icns. - Specs linked:
06-delivery/06-release-runbook.md - Acceptance: Quality (development shell matches release branding)
- Milestone: M5
- Status: Unit-covered (
development-branding.test.mjs); visual scenario Documented
E2E-045: Global text selection preserves editing and copying
- Preconditions: App running with a chat transcript containing a user message, an assistant Markdown response with a code block, and an expanded tool result.
- Steps: 1) Drag across sidebar/titlebar chrome and a button label. 2) Drag across user/assistant prose, code, and tool output. 3) Focus the composer and a settings/search input, then use
Cmd/Ctrl+Aand replace the selected text. 4) Copy selected transcript and code text. - Expected: Chrome does not leave an accidental text selection; message prose, code, tool input/output, and editable controls remain selectable and copyable; native editing shortcuts, focus-visible rings, and window drag behavior remain intact.
- Specs linked:
04-ux/07-ui-design-system.md,04-ux/09-interaction-patterns.md - Acceptance: Quality (key operations feel polished)
- Milestone: M5
- Status: Unit-covered (
user-select.test.mjs); scenario Documented
E2E-046: PI-Desktop renderer branding and composer icon boundary
- Preconditions: App running in both English and zh-CN locales, with an empty home and a docked transcript available.
- Steps: 1) Inspect the expanded and collapsed sidebar. 2) Inspect the empty-home hero and docked composer. 3) Hover the mascot and confirm it continues playing without the idle pause, then move the pointer away and confirm the idle pause returns. 4) Focus the footer Settings and Plugins icons, then each project/Temporary session create control. 5) Open Settings and the composer input.
- Expected: Visible shell identity reads
PI-Desktop; the empty-home hero renders the 100pxHomeMascotLogosprite, randomly selecting a pose group and selecting a different group after playback inside a fixed viewport without translating the mascot horizontally; pointer hover continuously advances the groups while leaving the mascot resumes the slower idle cadence. The expanded/collapsed sidebar renders the canonicalbuild/icon_1024.pngasset throughBrandLogoand the docked composer prompt row has no leading brand icon or reserved icon slot and its text aligns directly with the input gutter. The footer Settings and Plugins actions are compact icon buttons; Plugins sits immediately to the right of Settings and exposes a localized accessible name. Every scoped session-creation control uses the dedicated message-plus icon with localized labels and accessible names.Codexremains visible only as the external import-source label or in non-runtime design-reference text. - Specs linked:
04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md,08-meta/decisions-log.md(D094/D160),../../adr/0031-icon-free-composer-prompt-row.md - Acceptance: Quality (brand consistency and key operations feel polished)
- Milestone: M5
- Status: Unit-covered (
renderer-branding.test.mjs); scenario Documented
E2E-047: Retain, collapse, switch, and close multiple project tabs
- Preconditions: Projects A and B each have at least one durable session; neither path is archived; a Temporary session also exists.
- Steps: 1) Open project A from Settings → Project archive. 2) Open project B without closing A. 3) Click A's directory row on its chevron, folder, label, and trailing disclosure hit area in turn to collapse/expand it; use B's directory row to activate and collapse B; verify
+and overflow do not toggle B. 4) Hover and keyboard-focus A's project title and confirm the full path is exposed; open A's project overflow or right-click menu and choose Open folder; confirm conversation overflow no longer offers Open folder. 5) Select A's conversation. 6) Close B. 7) Restart the app. 8) Reopen B from Settings → Project archive. - Expected: A and B render as separate exact-path sidebar groups in a compact continuous list with one keyboard stop per directory disclosure; every non-action point in A's row toggles only A, project actions appear on hover/focus without shifting labels, the project title hover/focus path shows A's full absolute path, Open folder is a project-menu action only and opens A in the system file manager, and collapse survives restart; activating a group or its conversation clears the previous visible transcript, updates the selected workspace and session binding, and then loads only the selected project's conversation; Temporary remains separate; closing B removes only its retained tab and deletes neither its project row nor sessions; reopening B restores the same sessions without duplication.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md, ADR 0016 - Acceptance: C (switch sessions), D (workspace), F (local presentation persistence)
- Milestone: M5
- Status: Unit-covered (
sidebar-preferences.test.mjsfor retained paths and collapse persistence); full UI scenario Draft
E2E-048: Pin, archive, restore, and sort project/conversation rows
- Preconditions: Two retained projects contain conversations with distinct titles and created/updated timestamps; archived view is initially disabled.
- Steps: 1) Inspect the Sessions and Projects heading actions at rest. 2) Hover each heading and keyboard-focus each action to confirm the controls reveal without moving the labels. 3) Inspect the
Sessionstoolbar and verify that Sort appears before New Chat. 4) Open Sort and inspect its placement, then select Recently updated, Created date, Oldest first, and Name in turn. 5) Pin one project and one conversation. 6) Archive another conversation and project. 7) Enable Show archived and restore both. 8) Restart the app. 9) Delete a disposable conversation through the distinct Delete action. - Expected: Section create and sort controls are visually quiet at rest and reveal on toolbar hover or keyboard focus; project
+and overflow actions follow the same rule without shifting labels. Sort precedes New Chat in the Sessions toolbar; the sort menu remains content-sized, opens to the trigger's right without flipping left, and session/project/section body-level menus use the same right-side rule with a narrow-viewport width cap. Pinned rows remain ahead of unpinned rows under every selected secondary order; each sort produces the documented stable order; archived rows disappear from the default view but retain transcripts/project records and reappear in Show archived; restore returns them to the selected order; archiving the active row selects a visible non-archived fallback or creates the documented empty fallback instead of leaving hidden active context; pin/archive/sort choices survive restart; only Delete removes the disposable durable session. A legacymanualpreference loads safely without exposing or implying a drag-reorder workflow. - Specs linked:
03-runtime/04-data-storage.md,04-ux/01-ui-ia.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (session organization), F (persistence)
- Milestone: M5
- Status: Unit-covered (
sidebar-preferences.test.mjsfor metadata, filtering, and sort behavior); full UI scenario Draft
E2E-048A: Project session lists fold after the ten most recent rows
- Preconditions: One retained project contains more than ten durable sessions with distinct updated timestamps; the sidebar uses the default Recently updated sort; another retained project has ten or fewer sessions.
- Steps: 1) Inspect the large project's session rows and count them. 2) Select the Load N more… control. 3) Switch the sort to Name and inspect the same group before expanding. 4) Restart the app and inspect the group again.
- Expected: The group shows exactly ten session rows by default plus a Load N more… control (N = remaining session count) styled like the time-grouped overflow; the ten rows are the first rows in the active sort order, so pinned rows are never pushed behind unpinned rows and a Name sort folds everything after the first ten alphabetically; selecting Load more expands the full time-grouped list (Yesterday/Previous 7 days/Previous 14 days/Older headers appear as applicable) and the control disappears; expansion is per group and resets on restart (not persisted); the project with ten or fewer sessions shows no fold control.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/09-interaction-patterns.md - Acceptance: C (session organization)
- Milestone: M5
- Status: Scenario Documented
E2E-049: Background sessions keep their originating workspace
- Preconditions: Projects A and B are retained; each contains a session in Agent mode; both workspaces contain different marker files with the same relative name.
- Steps: 1) In session A, start a turn that reads the marker and performs a permission-gated long-running tool. 2) While A is running, activate project B and open session B. 3) Read B's marker and allow a tool only in B. 4) Wait for both turns to complete. 5) Open a Temporary session and attempt a workspace-required tool.
- Expected: Switching tabs aborts neither turn; A's tool cwd/path sandbox remains project A and B's remains project B; A's events and grants never appear in B's transcript/session; each sidebar row reports its own running/completed state; the Temporary session inherits no project and receives
WORKSPACE_REQUIRED; returning to A restores A's completed transcript. - Specs linked:
02-architecture/01-architecture.md,03-runtime/02-agent-runtime.md,03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md,03-runtime/10-session-state-machine.md, ADR 0016 - Acceptance: C (parallel sessions), D (workspace), E (tool/permission isolation), Security (workspace boundary)
- Milestone: M5
- Status: Unit-covered (
rpc::testsfor project-bound, Temporary, and missing-session workspace resolution); full multi-turn UI scenario Draft
E2E-050: Thinking selector follows exact model capability
- Preconditions: One catalogued reasoning model, one non-reasoning model, and one unknown free-form model id.
- Steps: 1) Select each provider/model in turn. 2) Inspect the composer controls beside Agent / Plan / Goal. 3) Open the Thinking trigger and choose multiple supported levels. 4) Inspect the unknown model's menu and Provider Settings.
- Expected: Reasoning models show the current Thinking level immediately to the right of Agent / Plan / Goal, expose only their sparse supported levels as a single-column list in canonical order, mark the selected row with a trailing check, expose no inherit/default row, size the menu to its content without exceeding 160px or the available viewport, truncate overlong labels, and close the menu after selection. Non-reasoning models show no Thinking trigger. Every level comes from pi's selected model record. The unknown model exposes neither a Thinking trigger nor an Enable thinking action, and Provider Settings exposes no reasoning/level override. Refreshing discovered model data cannot replace pi's known-model semantics or invent capabilities for the unknown model.
- Specs linked:
03-runtime/11-provider-model-system.md,03-runtime/12-provider-config-schema.md,03-runtime/13-model-catalog-and-selection.md, ADR 0018, ADR 0027 - Acceptance: B (model config), Quality
- Milestone: M5
- Status: Unit-covered (
thinking-ui.test.mjs, agent-runtime capability tests); full UI scenario Draft
E2E-051: Thinking level persists with the session
- Preconditions: A reasoning-capable session is idle.
- Steps: 1) Select
high. 2) Change Plan/Goal/Agent mode without changing the thinking level. 3) Restart the app and reopen the session. 4) Switch to another session and back. - Expected: Every configuration update sends the complete session config;
highsurvives the permission-mode change, session switches, host reload, and app restart. A v2 database migrates the same field tooffwithout transcript loss. - Specs linked:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,04-ux/08-component-spec.md, ADR 0018 - Acceptance: F (persistence)
- Milestone: M5
- Status: Unit-covered (host schema/session tests,
thinking-ui.test.mjs); full restart scenario Draft
E2E-052: Thinking level reaches the pi request
- Preconditions: Instrumented reasoning-capable provider with a sparse level set and request capture; one session configured above and below gaps.
- Steps: 1) Select each available level and run a prompt. 2) Seed an unsupported stored level and run again. 3) Repeat with a pi-catalogued non-reasoning model.
- Expected: Main resolves capability using the session's actual model id; Composer, main, sidecar, and pi use the same upward-first/downward-second clamp; pi receives the effective level and the non-reasoning model receives
off. Model-specific request semantics, including adaptive thinking and whetheroffis expressible, match the pinned pi record without a desktop rewrite. - Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md,03-runtime/13-model-catalog-and-selection.md, ADR 0018, ADR 0027 - Acceptance: B (model config), C (chat and stream)
- Milestone: M5
- Status: Unit-covered (agent-runtime prompt/clamp tests); integration scenario Draft
E2E-053: Thinking streams separately from the answer
- Preconditions: Provider emits thinking deltas before and between answer deltas.
- Steps: 1) Start a turn in both light and dark themes. 2) Observe a thinking-only phase. 3) Let the answer complete. 4) Toggle the disclosure, test keyboard focus, enable reduced motion, and use Copy answer.
- Expected: The transcript opens during thinking-only streaming; one open Thinking disclosure updates without an empty answer bubble or duplicate Working indicator. The disclosure uses the transcript surface, theme tokens, a Sparkles/chevron trigger, and a left rule instead of an inset card; collapsed content leaves focus traversal and reduced motion disables shimmer and transitions. Final answer markdown renders separately; Copy answer contains no thinking text.
- Specs linked:
03-runtime/01-ipc-protocol.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md, ADR 0018 - Acceptance: C (chat and stream), Quality
- Milestone: M5
- Status: Unit-covered (
thinking-ui.test.mjs, agent-runtime event tests); full streaming scenario Draft
E2E-054: Stored thinking reloads losslessly
- Preconditions: A completed assistant message contains both reasoning and final answer blocks; another contains reasoning only.
- Steps: 1) Complete both turns. 2) Restart the host/app. 3) Reopen the session. 4) inspect search results and answer copy.
- Expected: Host returns the same separate
thinkingandcontentvalues after reload/import/replace round-trips; both messages remain visible; search and answer copy exclude reasoning. - Specs linked:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,04-ux/08-component-spec.md, ADR 0018 - Acceptance: C (chat and stream), F (persistence)
- Milestone: M5
- Status: Unit-covered (host message/import tests,
thinking-ui.test.mjs); full reload scenario Draft
E2E-055: Unsupported provider transition clamps safely
- Preconditions: Session on a reasoning provider at
max; target providers include non-reasoning and sparse-level variants. - Steps: 1) Switch to the non-reasoning provider. 2) Run a turn. 3) Switch to sparse variants around the previous level. 4) send malformed/legacy payloads lacking capability or thinking fields.
- Expected: Non-reasoning persists and sends
off; sparse variants choose the same nearest level everywhere; missing fields fall back safely; malformed thinking is not rendered and never contaminates answer content. - Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/13-model-catalog-and-selection.md,04-ux/08-component-spec.md, ADR 0018 - Acceptance: B (model config), C (chat and stream), Quality
- Milestone: M5
- Status: Unit-covered (
thinking-ui.test.mjs, host validation tests); full UI scenario Draft
E2E-056: Work panel shell docking and persistence
- Preconditions: App running with any workspace state.
- Steps: 1) Relaunch and inspect the titlebar and application menu; confirm the panel starts closed. Press Cmd/Ctrl+J and inspect the empty panel title and context menu, then press it again to confirm the open state is idempotent and no tab is created. 2) Open two distinct file artifacts, the same first file again, a URL preview, and a completed command artifact. 3) Open the header's unified context menu: verify the four tools appear once, in a fixed order, with active, open-inactive, and closed states, and that transcript-opened resources appear only in the second section. Open/select each tool with pointer and keyboard, reopen a Browser that already has a URL and confirm the URL survives, walk the rows with ArrowDown/ArrowUp/Home/End (focus must skip the close buttons), close an inactive row with Delete and confirm the menu stays open with focus on the neighbor, press Escape and confirm focus returns to the trigger, then close the active item from the header. Confirm the right action cluster stays at the header's right edge for both the shortest and longest labels. 4) Close active middle and edge items and verify neighbor selection. 5) Use the sole session-pane collapse control and trigger another artifact. 6) In session A, leave the panel open with multiple tabs and a Browser resource; switch to session B, create a different tab set, then switch repeatedly between A and B and select a project without an active conversation. Generate a background artifact in the non-visible session. 7) Drag the left-edge handle below 244px and above 720px; verify pointer-down does not jump the divider, cancel one gesture with Escape, then focus the handle and exercise Arrow/Shift+Arrow/Home/End. Commit a different width with Browser active. 8) On a display with enough work area, record MainChat width, native bounds, and
window/setWorkPanelReservationresults while opening, repeating the same open target, committing a divider width, collapsing, reopening, and closing the final resource. With Browser active on Windows, repeat collapse while watching the entire frameless window. 9) With the panel open, resize the native window from both left and right edges; repeat after toggling the sidebar. 10) Repeat open/resize/collapse on a work area too narrow to supply the complete reservation. 11) Open or collapse while maximized and fullscreen, then return to normal. 12) Move the normal window between displays with different work areas, change the active display's work-area geometry, and perform ordinary moves within one unchanged work area; include a transition where the window manager compresses and relocates the outer window before the display-change callback. 13) Inject one rejected reservation while opening and one while collapsing, then retry each action. 14) Send string, boolean, null, fractional, and out-of-range reservation payloads. 15) Relaunch. - Expected: Startup shows no panel, welcome chooser, fixed tool buttons, or titlebar/menu launcher. Cmd/Ctrl+J opens the active session's panel at its committed width without creating a resource tab; repeating it is idempotent, and the shortcut does nothing without an active session or while Settings is open. Each artifact atomically opens the docked third column and creates or activates one resource; file resources are path-keyed and repeated resources deduplicate. Opening, collapse, and closing animate the panel's width/flex allocation with its bounded opacity/slide, so MainChat reflows continuously without a pre-animation jump. Opening the panel, collapsing it, or committing a divider resize never changes the OS window size — only MainChat reflows inside the fixed client area (ADR 0033). Once the panel is open, a single unified context trigger opens one dropdown that lists the four tools first, in a fixed order, with a fill plus 2px edge marker for the active row and a dot for open inactive ones, each open row carrying its own close control in an always-reserved trailing slot; a second section appears after a divider only for transcript-opened resources (full-path tooltips, per-item close), so no entry is listed twice. The menu fades in over ≤4px and is static under reduced motion. Arrow/Home/End move focus across rows only and skip the close buttons, ArrowDown/ArrowUp on the trigger open on the active/last row, Delete/Backspace closes the focused row while the menu stays open with focus on its neighbor, and Escape/Tab/selection restore focus to the trigger. Reopening an already-open tool activates it and preserves its Browser URL. The right action cluster stays pinned to the header's right edge regardless of label length. Opening the menu temporarily hides the native Browser preview so it is never occluded. The sole collapse control sits in the session pane top-right rather than the content header. Active close selects the right neighbor then left; closing the last tab hides the panel. Collapse retains runtime tabs but hides the panel until another artifact reopens it. Width clamps to the fixed
244px–720pxrange, exposes those current/minimum/maximum values to assistive technology, and supports the documented keyboard steps. Pointer-down preserves the starting width, movement follows the pointer continuously, and release commits once only when the width changed. A zero-movement release, Escape, or cancellation preserves both the prior preferred width and native reservation. Browser preview does not intercept an active divider drag. A and B independently restore their runtime open state, ordered tabs, active tab, and Browser resource; selecting a project without an active conversation hides the panel, and no relative resource crosses session/workspace context. Background artifacts update only their retained context and never change the visible reservation. Before exit motion, the native Browser preview detaches from the window. On Windows the dock remains opaque through its bounded exit slide, and collapse produces no white/full-pane flash or stale preview frame while native bounds return to the base width. Only{width}is restored after relaunch; every session's open state, tabs, active tab, and Browser resource reset. The open panel remains exactly at its committed width through native-edge and sidebar changes; those gestures resize MainChat only and never rewrite the preference. In normal state, open returns{requested: committedWidth, reserved: committedWidth}and grows/shifts the native window inside the work area so MainChat width stays unchanged. Repeating the target is a no-op. Divider commit updates the target once. Collapse and final close return{requested: 0, reserved: 0}and symmetrically restore the base bounds and x position. On a constrained work area,reservedreports all available added width belowrequested; the panel remains fixed and only MainChat absorbs the shortfall. Maximized/fullscreen calls retain the latest requested target without changing geometry, then reconcile once on return to normal. Display/work-area changes reconcile the same target against current available width and update the native minimum; ordinary movement within one unchanged work area does not reapply geometry. System compression or relocation during a display transition does not overwrite the confirmed base bounds, and returning to a roomier display restores the prior chat width. Relaunch restores the user's window size (the native reservation is always 0, ADR 0033). Malformed reservation payloads fail withINVALID_ARGUMENTand never coerce. A rejected reservation keeps the last confirmed panel presentation until a later successful request; a superseded success cannot commit stale presentation. No transition produces a second resize or position drift. The former context-panel overlay no longer exists. - Specs linked:
03-runtime/01-ipc-protocol.md,04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md, ADR 0032, ADR 0068, D163, D207 - Acceptance: F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
work-panel-resize.test.mjs,work-panel-window.test.mjs,work-panel-presentation.test.mjs,work-panel.test.mjs); full UI scenario Draft
E2E-057: Message-owned review history survives commits and rolls back safely
- Preconditions: A project-bound Agent session with a writable workspace; no Git repository is required.
- Steps: 1) Ask the active agent to edit an existing file and create a new file in session A. 2) Expand the activity group and inspect each review card directly after its corresponding tool row; verify every card starts collapsed with its added/modified status and +/− counts visible in the header, then expand it to verify the exact hunks. 3) Commit the files outside the app, close and reopen the Review panel, then reload session A. 4) Verify the same cards and counts remain because they come from the transcript messages. 5) Use a card's rollback action and verify the created file is removed or the previous file bytes are restored. 6) Edit that file again outside the app and retry rollback; inspect the conflict result and verify the later bytes remain. 7) Switch to session B and a background project session, then return to A. 8) Repeat with failed, denied, and scratch writes.
- Expected: Each successful workspace Write/Edit creates one message-owned review record and one adjacent keyboard-accessible card; the card is never a bottom/global entry. Every review card, inline and in the Review tab, is collapsed by default and expands on demand. The Review tab lists A's chronological recorded changes, independent of Git status, repository presence, commit state, focus refresh, or workspace switching. Added, modified, and deleted statuses plus line additions/deletions and hunks are shown when bounded evidence is available. Successful rollback updates the card to Rolled back and survives restart. A post-tool file change returns Conflict and does not overwrite it. Failed, denied, and scratch writes do not create cards; session B cannot inherit A's records. Binary or oversized snapshots show bounded metadata and disable rollback when the previous bytes were not retained.
- Specs linked:
03-runtime/01-ipc-protocol.md§13a,03-runtime/03-tools-and-permissions.md§4c,04-ux/08-component-spec.md§5, ADR 0043 - Acceptance: D (workspace), Quality
- Milestone: M5
- Status: Unit-covered (
chat-review-entry.test.mjs); full UI scenario Draft
E2E-058: Interactive terminal session lifecycle
- Preconditions: A workspace is open; a successful completed-command artifact exists.
- Steps: 1) Activate the completed command artifact to create Terminal, then run
pwdandls. 2) Switch to another artifact tab and back. 3) Collapse and reopen via another command artifact. 4) Drag-resize the panel and toggle light/dark theme. 5) Runexit. 6) Restart via the overlay button. 7) Quit the app and check for orphan shells. - Expected: The shell starts in the workspace directory as a login shell with 256-color TERM; output/scrollback survive tab switches and panel close (same PTY reattached); resize refits columns without garbling; theme switch recolors the terminal;
exitshows the ended-session overlay whose restart starts a fresh shell; app quit kills all PTYs; with no workspace the tab shows its empty state and no PTY spawns. - Specs linked:
03-runtime/01-ipc-protocol.md§13a, ADR 0019 - Acceptance: D (workspace), Quality
- Milestone: M5
- Status: Draft (manual)
E2E-059: Embedded browser preview isolation and overlays
- Preconditions: A local dev server is running; a URL or BrowserPreview artifact exists.
- Steps: 1) Activate the artifact, enter
localhost:<port>without a scheme, and submit. 2) Navigate site links; use back/forward/reload/stop. 3) Trigger awindow.openpopup and a permission-requesting page (e.g. notification prompt). 4) Open global search, then Settings. Return to chat and trigger an inline tool permission card. 5) Switch to another panel tab and back; close the panel. 6) Use open-external. - Expected: Scheme-less input normalizes to http; nav state (URL bar, back/forward enablement, load spinner) mirrors the page. Popups open in the default browser (never in-app); permission requests are denied; non-http(s) navigation is blocked. The preview hides under every blocking overlay and while unmounted, reappearing with correct bounds afterwards. An inline permission card does not hide or remount the preview; resize/drag keeps the view aligned with the placeholder rect. Open-external launches the current URL in the default browser. The view uses an isolated persist partition (no session bleed from the app shell).
- Specs linked:
03-runtime/01-ipc-protocol.md§13a, ADR 0019 - Acceptance: Quality, Security
- Milestone: M5
- Status: Draft (manual)
E2E-060: Files tab browsing stays inside the workspace
- Preconditions: Workspace with file artifacts for nested source, large (>512KB), image, and binary files.
- Steps: 1) Activate each file artifact and verify a distinct path-keyed resource in the header switcher; browse the tree, expanding nested folders. 2) Open a source file, the image, the binary, and the large file. 3) Use reveal-in-Finder. 4) Attempt a traversal read (
../outside) via devtools IPC. 5) Switch workspaces. - Expected: Directories list lazily, folders first, with
.git/node_modules/ build outputs hidden; text renders with syntax highlight (capped at 5000 lines), images preview inline, binary and oversized files show fallbacks with reveal still available. Traversal attempts are rejected withINVALID_ARGUMENT; no workspace → empty state; switching workspaces resets the tree and viewer. - Specs linked:
03-runtime/01-ipc-protocol.md§13a, ADR 0019,03-runtime/15-workspace-ignore-rules.md - Acceptance: D (workspace), Security
- Milestone: M5
- Status: Unit-covered (
fs-panel-guard.test.mjs); full UI scenario Draft
E2E-059a: Transcript message plates follow WorkBuddy density
- Preconditions: A session contains at least one short user prompt, one longer user prompt, and a completed assistant answer; light and dark themes available.
- Steps: 1) Open the session in dark theme. 2) Inspect user and assistant rows at rest and on hover. 3) Start a streaming assistant answer. 4) Switch to light theme and repeat. 5) Focus the copy control with the keyboard.
- Expected: User turns are right-aligned, theme-neutral soft plates capped near 560px, derived from each theme's primary text ink rather than an accent tint, with a subtle border; assistant answers remain transparent full-width prose in the 720px content band. Row spacing is denser (~10px). Copy chips are hidden at rest, appear on hover/focus-within, and stay right-aligned under user turns. Streaming assistant answers show a thin accent left rule without boxing the whole answer. Both themes keep readable contrast on the user plate.
- Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,04-ux/10-workbuddy-benchmark-ux.md - Acceptance: C (chat stream), Quality
- Milestone: M5
- Status: Unit-covered (
transcript-style.test.mjs); full visual scenario Draft
E2E-060b: Neutral gray accent across chrome
- Preconditions: App running in dark and light themes; plugins page and a chat with markdown links/blockquotes available.
- Steps: 1) Inspect focus rings, primary buttons, toggles, selected session ring, plugin market primary CTAs. 2) Open an assistant answer with links and a blockquote. 3) Switch theme and re-check.
- Expected: No blue brand accent remains. Interactive accent, markdown links/rules, and plugin primary actions resolve through the neutral gray accent tokens (
white/graydark, dark-ink light). The plugins installed/market UI (tabs, search, cards, permission modal, and primary / secondary buttons) uses only--ds-*tokens with no blue-slate fallbacks in either theme; button surfaces and ink remain visible in dark mode. Semantic success/warning/error colors are unchanged. - Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md - Acceptance: Quality
- Milestone: M5
- Status: Unit-covered (
neutral-accent.test.mjs,plugins-page-style.test.mjs); visual scenario Draft
E2E-060c: Assistant markdown prose hierarchy and code chrome
- Preconditions: A completed assistant answer containing headings, a blockquote, a GFM table, a fenced code block with a language tag, inline code, a task list, and a remote image link; light and dark themes available.
- Steps: 1) Open the session in dark theme and scroll the answer. 2) Hover the code-block copy control and the table rows. 3) Expand a thinking disclosure that contains markdown. 4) Switch to light theme and re-check contrast on inline code, blockquote rule, and code card.
- Expected: Answer prose uses the
.prose-chathierarchy (h1–h6 ramp, accent-tinted blockquote, hairline-bordered inline code, zebra/hover table shell, inset code card with monospace language tag). Thinking prose stays secondary/smaller and does not merge into the answer. Both themes keep readable contrast; copy still copies raw fence text. - Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md§8.7 - Acceptance: C (chat stream), Quality
- Milestone: M5
- Status: Unit-covered (
user-select.test.mjs,thinking-ui.test.mjs); full visual scenario Draft
E2E-061: User message plaintext layout survives wrapping and reload
- Preconditions: Provider configured; composer can accept multi-line input via Shift+Enter (or Enter-to-send disabled).
- Steps: 1) Compose a three-line prompt with two hard newlines and a URL whose encoded path is wider than the user plate. 2) Send. 3) Inspect the user bubble in the transcript. 4) Copy the user message and paste into an external editor. 5) Reload the session.
- Expected: The user plate shows three distinct lines (not collapsed to a single paragraph). The linked URL wraps inside the plate without horizontal overflow, and every continuation line stays logical-start aligned with the first line instead of being centered. Copied text retains the original newlines. After reload the same line breaks remain.
- Specs linked:
04-ux/08-component-spec.md - Acceptance: C (chat stream), Quality
- Milestone: M5
- Status: Unit-covered (
transcript-style.test.mjs); full visual scenario Draft
E2E-060d: Assistant meta chips, context inspector, and retry action
- Preconditions: A completed assistant message includes modelId and token usage; another completed assistant message has content but no usage.
- Steps: 1) Open the session. 2) Hover the completed assistant turn that has usage, then focus its Context inspector trigger with the keyboard. 3) Inspect the panel's remaining-token header, used percentage and meter, the pi-ai-resolved model context-window size, exact input/output/cache/reasoning breakdown, cache hit rate when cache-read metadata is reported, completed generation tokens/s value, source badges, and each unique tool type's aggregated call count and estimated argument/result token allocation. 4) Scroll the transcript until the trigger is close to the top, bottom, and right viewport edges, and resize the window while the panel is open. 5) Click Retry on that turn while idle. 6) Confirm a turn without usage still offers Retry and omits the inspector.
- Expected: Model badge and compact Context inspector appear under completed assistant answers when data exists; the trigger shows remaining capacity and low-space warning/error states, while hover and keyboard focus expose the same complete token panel. The panel's exact provider and estimated tool sources are visibly distinguished, and its cache hit rate uses cached prompt tokens divided by all reported prompt tokens (cached plus uncached); when cache-read metadata is absent, the rate is omitted rather than inferred. Its context-window total matches the model metadata used by the agent runtime; tool rows show each unique tool type in first-seen order, aggregate repeated calls, include call counts and cumulative duration, and explicitly mark their estimates. The generation rate remains a completed-turn value and does not update during streaming; Retry re-sends the nearest preceding user prompt and is disabled while a turn is running; the portaled panel remains fully visible within the viewport, never clipped by transcript scrolling, and follows the trigger after scrolling or resize; Copy still excludes thinking text.
- Specs linked:
04-ux/08-component-spec.md,04-ux/10-workbuddy-benchmark-ux.md,03-runtime/01-ipc-protocol.md - Acceptance: C (chat stream), Quality
- Milestone: M5
- Status: Unit-covered (
transcript-style.test.mjs,context-usage.test.mjs, runtime usage mapping); full scenario Draft
E2E-061a: Regenerate replaces the current turn in place
- Preconditions: A session has user A → assistant A → user B → assistant B.
- Steps: 1) Hover assistant A and click Regenerate. 2) Wait for the new turn to complete. 3) Reload the session.
- Expected: Transcript truncates away assistant A / user B / assistant B before the redo starts; only user A plus the new assistant/tool tail remain. The regenerated answer does not leave the old branch above it. Reload keeps the truncated branch only.
- Specs linked:
04-ux/08-component-spec.md,03-runtime/01-ipc-protocol.md,03-runtime/04-data-storage.md - Acceptance: C (chat stream), F (persistence)
- Milestone: M5
- Status: Unit-covered (store/main truncate wiring tests); full scenario Draft
E2E-062: Regenerate history pager restores prior variants
- Preconditions: A session where an assistant answer was regenerated at least once.
- Steps: 1) Click Retry/Regenerate on a completed assistant turn. 2) Observe the visible root user bubble while the replacement turn starts and after it completes. 3) Switch to a previous variant. 4) Switch forward again. 5) Reload the session.
- Expected: The root user bubble remains visible and shows the
current / totalpager inside its action toolbar once the row is hovered or focused; the toolbar, including the pager, is hidden by default. Retry does not move or detach the selector from that bubble. Switching restores the archived assistant/tool branch in place. Reload preserves the active variant and the full revision set. - Specs linked:
04-ux/08-component-spec.md,03-runtime/04-data-storage.md - Acceptance: C (chat stream), F (persistence)
- Milestone: M5
- Status: Unit-covered (
sessions::tests::save_and_activate_message_revision, schema v4 migration); full scenario Draft
E2E-063: Empty home keeps the primary task surface focused
- Preconditions: App running on empty chat home (no transcript) in light and dark themes; window can be resized to ~1200×690 and ~900×640.
- Steps: 1) Open empty home. 2) Confirm the hero contains the quiet logo, localized title, and short supporting line. 3) Confirm no developer starter cards or contextual quick-action row is rendered. 4) Dismiss onboarding and inspect again. 5) Repeat in the other theme. 6) Resize to a short height and scroll the content region if needed.
- Expected: The default empty state keeps the hero and bottom composer as the visual anchors, with optional onboarding as the only additional content. Dismissing the checklist leaves no empty spacer. The composer remains at the bottom without covering the hero or checklist, and short windows keep every content block reachable via scroll.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,08-meta/decisions-log.md(D111/D131/D204/D206) - Acceptance: Quality (layout integrity)
- Milestone: M5
- Status: Unit-covered (
home-empty-layout.test.mjs); full UI scenario Draft
E2E-094: Active turns keep the lower transcript surface clear
- Preconditions: A deterministic agent can emit thinking, tool start/end, streamed answer, permission, and terminal events for a durable session; a second session can run in the background.
- Steps: 1) Start a turn in the visible session. 2) Observe the transcript while the agent is thinking, using tools, and streaming an answer. 3) Trigger a permission request and inspect the approval card. 4) Switch to a second session while the first continues. 5) Return to the first session after completion.
- Expected: No generic Understanding, Working, Checking, or completion card appears below the transcript while the turn is active. Assistant and tool rows remain inline; only an actual permission request renders an actionable card. Background activity never changes the visible session, transcript, composer focus, or project.
- Specs linked:
04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md,03-runtime/10-session-state-machine.md - Acceptance: C (chat stream), Quality (interaction and accessibility)
- Milestone: M5
- Status: Unit-covered (
active-turn-surface.test.mjs); full UI scenario Draft
E2E-095: Terminal failures expose recovery without a success card
- Preconditions: A deterministic provider can recover from a failed directory
ReadwithGlob, complete a turn with a workspace edit, and fail another turn with a retriable error; the session has a visible composer. - Steps: 1) Run the failed-Read then successful-Glob recovery turn and inspect its activity group. 2) Complete the workspace-edit turn. 3) Confirm that no success outcome card appears and inspect the inline review card immediately after the change tool row. 4) Expand the inline card and verify its hunks. 5) Commit the edited file and confirm the recorded card remains, then use rollback once. 6) Trigger the retriable failure. 7) Inspect the failure card, then choose Retry. 8) Start another new prompt and inspect the old card.
- Expected: The recovered turn keeps the failed Read visible on its own row, labels the containing group as processed, completes its session outcome, and shows no failure card. Completion uses the transcript and inline review card as its evidence without adding a "Task complete" card. File status, counts, and hunks remain on the adjacent card after commit, and guarded rollback restores the pre-tool state. Failure shows that existing work remains, exposes Retry and Continue, and retry preserves the latest prompt. A new turn clears the previous failure card; an abort creates no failure outcome copy.
- Specs linked:
04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md,03-runtime/10-session-state-machine.md, ADR 0069 - Acceptance: C (chat stream), Quality (completion and recovery)
- Milestone: M5
- Status: Unit-covered (
assistant-turns.test.mjs,interaction-performance.test.mjs,turn-outcome-card.test.mjs); full UI scenario Draft
E2E-064: Durable notification inbox records terminal task outcomes
- Preconditions: Two durable sessions exist; a deterministic provider can complete one turn, fail one turn with a stable error code, and abort one turn; notification inbox starts empty.
- Steps: 1) Focus and view session A, then complete a turn in A. 2) While still focused on A, fail a turn in background session B. 3) Unfocus the window and complete another turn in A. 4) Abort a fourth turn. 5) Repeat each terminal RPC. 6) Confirm the main titlebar has no bell, then open the bell in the expanded sidebar footer and switch between All and Unread. 7) Mark one row read and confirm its session has no terminal sidebar mark, then close/reopen the popover and restart the app. 8) Select the other session from its terminal-marked sidebar row. 9) Generate a host fixture with 205 eligible terminal turns. 10) Use Mark all read, then Clear.
- Expected: A's visible-current completion creates no row. Exactly two rows exist, newest first: the unfocused A completion and background B failure, with localized labels, snapshotted session titles, and B's stable code. Abort/repeated terminal calls create no row. The former footer Help shortcut is absent; the 32px footer bell and its upward-opening popover replace it. Badge and Unread show the exact unread count without opening implicitly reading rows. Read state and both records survive restart. Row selection marks it read and activates its bound project/session. The fixture retains exactly the newest 200 rows. Mark all preserves rows with zero unread. Selecting the other session clears its terminal sidebar mark and marks its task notification read; neither mark returns after refresh or restart. Clear empties only the inbox and leaves sessions, turns, and transcripts intact.
- Specs linked:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,03-runtime/01-ipc-protocol.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,08-meta/decisions-log.md(D117/D130) - Acceptance: C (turn completion), F (persistence), Quality
- Milestone: M5
- Status: Draft
E2E-065: Native task notifications are unfocused-only and activate sessions
- Preconditions: Native notifications are supported; sessions A and B exist; the main window can be focused, unfocused, hidden, and minimized. A Windows run uses the NSIS-installed app or the standard development command.
- Steps: 1) Keep the app focused on A and complete a turn in A. 2) While still focused on A, complete a turn in B. 3) Unfocus the app while A remains current and complete another turn in A. 4) Click A's native notification. 5) Minimize the app, fail another turn, and click its native notification. 6) Unfocus the app and abort a turn. 7) Repeat with native delivery suppressed by the OS. 8) On Windows, inspect the native notification attribution, notification-settings entry, taskbar group, installed executable, and Start menu shortcut.
- Expected: Focused-current A creates neither inbox row nor native banner. Focused-background B creates an inbox row without a native banner. Unfocused current A and the minimized failure each create one durable row and one localized native notification. Clicking restores, shows, and focuses the main window before activating the matching session; no event opens the wrong currently selected session. Abort shows neither surface. OS suppression does not lose the durable row or surface a misleading app error. Every inspected Windows system surface identifies
PI-Desktop; no stock Electron application name or identity is exposed. - Specs linked:
03-runtime/01-ipc-protocol.md,04-ux/07-ui-design-system.md,04-ux/09-interaction-patterns.md,08-meta/decisions-log.md(D117/D141) - Acceptance: C (turn completion), Quality
- Milestone: M5
- Status: Draft
E2E-066: Provider model catalog survives restart and offline refresh
- Preconditions: A saved provider has returned at least two models from its discovery endpoint and the resulting catalog is stored in
models. - Steps: 1) Quit and restart the app. 2) Disconnect the provider endpoint. 3) Open the top-bar model picker. 4) Wait for background refresh to fail. 5) Reconnect the endpoint with one renamed model and one additional model, then update the provider configuration and reopen the picker.
- Expected: The first picker open renders the prior catalog without starting from an empty list. Offline refresh preserves every cached entry and the configured-model fallback. After reconnection, live results update the renderer and persist to Rust-owned SQLite. User-defined model rows remain unchanged, and the newly discovered model remains available after another restart.
- Specs linked:
03-runtime/04-data-storage.md,03-runtime/12-provider-config-schema.md,03-runtime/13-model-catalog-and-selection.md,04-ux/08-component-spec.md - Acceptance: B (model config), F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
providers::tests,model-cache.test.mjs); full restart/offline UI scenario Draft
E2E-080: Claude Opus 5 resolves from the pinned pi-ai catalog
- Preconditions: Desktop is on
@earendil-works/pi-ai/pi-agent-core^0.82.1+; a custom or Anthropic-compatible provider uses apiStyleanthropic_messages(or OpenAI-compatiblechat_completionswithanthropic/claude-opus-5) and model idclaude-opus-5. - Steps: 1) Select
claude-opus-5in the top-bar model picker. 2) Open the Thinking selector. 3) Start a short turn and inspect the sidecar model snapshot / request metadata (or the unit-equivalent resolution path). - Expected: The id maps to the pinned pi catalog record (1M
contextWindow, adaptive-thinking compat, published thinking levels includingxhigh/max). The Thinking control appears and lists only those levels. Provider settings cannot override the limits. An id still absent from the pin stays on the generic non-reasoning fallback. - Specs linked:
02-architecture/02-tech-stack.md,03-runtime/11-provider-model-system.md,03-runtime/13-model-catalog-and-selection.md, ADR 0027, D136 - Acceptance: B (model config)
- Milestone: M5
- Status: Unit-covered (
model-capabilities.test.ts); full UI scenario Draft
E2E-067: Platform application menus and window chrome
- Preconditions: Native macOS, Windows, and Linux runners; built desktop app; English and zh-CN locales available. The Windows/Linux harness can set
PI_DESKTOP_START_MAXIMIZED=1before launch so Main maximizes the hidden native window before renderer mount. - Steps: 1) On macOS, launch both
pnpm devand a packaged build. Confirm the application-menu title is PI-Desktop, open About PI-Desktop, and inspect its name, version, and icon. Then open every system menu and invoke New Task, Open Project, Settings, global search, sidebar toggle, editing, zoom/fullscreen, Window, Help, Logs, and Check for Updates actions. Verify the update status reports that the current fixture version is up to date. 2) On Windows/Linux, confirm no File/Edit/View/Window/Help menubar appears inside the window and the left-side navigation occupies the reclaimed titlebar space. Verify F10 and Shift+F10 are not consumed by shell chrome; exercise New Task, Open Project, Settings, close-window, zoom, fullscreen, global search (Cmd/Ctrl+K and Cmd/Ctrl+Shift+P), sidebar, and standard editing shortcuts. Invoke Check for Updates from Settings -> Info with the same status result. 3) Close the macOS window, immediately invoke two native menu commands, and acknowledge renderer readiness after the replacement loads. Verify one window and one delivery per command. 4) On Windows/Linux, repeat from the main chat, Settings, and an open work panel. With the work panel open, confirm the panel collapse button is flush with the main-pane right divider and does not retain the 112px outer-window control clearance. In the main chat, send a first user message and confirm its full bubble starts below the 46px titlebar control band. Open the Extensions page and confirm its header actions, then the detail sheet's close button, also start below that band and take their own clicks instead of moving the window. Click the center plus the top, bottom, and titlebar-facing edges of each right-side control to minimize, maximize, restore, and close the window. 5) Start the renderer while its native window is already maximized and inspect the initial queried glyph/state. 6) Attempt unknown menu/window IPC actions while a window exists and after it closes. 7) Build each target on its native runner from a clean release-host directory. - Expected: macOS development and packaged launches show PI-Desktop as the native application identity, and the About panel uses the canonical PI-Desktop icon; neither surface exposes the stock Electron name or icon. macOS follows native menu conventions and accelerators. Windows/Linux show no application menu inside the window; navigation and right-side controls do not collide with drag regions, keyboard shortcuts remain operational, and no work-panel launcher is present. The open-panel collapse button touches the main-pane right divider without an inset or a duplicate native-control gap. Check for Updates invokes the allowlisted update command from the macOS system menu and the Settings surface and shows the resulting up-to-date state. Replacement-window commands wait for renderer readiness without creating duplicate windows or losing events. No Main, Settings, or work-panel drag rectangle overlaps the reserved control zone. Window controls remain clickable across their full 46px-high hit targets, match native state, and have accessible names; the first user or assistant transcript row never paints beneath them, and neither do the Extensions page header actions or the plugin detail sheet close button. Unknown actions fail closed. Each package contains the target-native host binary (
.exeonly on Windows). Passing this scenario on Windows/Linux proves shell readiness, not first-release qualification. - Specs linked:
03-runtime/01-ipc-protocol.md,04-ux/01-ui-ia.md,04-ux/02-i18n-english-first.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md,06-delivery/06-release-runbook.md,08-meta/decisions-log.md(D118, D121, D129) - Acceptance: A (app startup), Quality
- Milestone: M5 on macOS; post-MVP release qualification on Windows/Linux
- Status: Unit-covered (
window-menu.test.mjs,development-branding.test.mjs); Electron boot probe covers platform bridge, native menu installation, and the pre-render maximize fixture on Windows/Linux; native visual scenario Draft
E2E-067A: Prerelease install discovers newer stable release (D120)
- Preconditions: Packaged build whose embedded version is a prerelease such as
0.2.0-rc.6; GitHub Releases latest stable tag is newer (for example0.2.2) with publishedlatest*.ymlfeeds. - Steps: 1) Launch the packaged prerelease install. 2) Wait for the automatic check or invoke Check for Updates from the application menu / Settings → Info.
- Expected: Update state reports
available(manual platforms) or advances through in-app download for Windows NSIS / Linux AppImage withavailableVersionequal to the newer stable tag. The client must not report up-to-date merely because no newer release shares the samercprerelease channel. - Specs linked:
04-ux/09-interaction-patterns.md,05-security/01-security.md,08-meta/decisions-log.md(D120), ADR 0022 - Acceptance: A (app startup), Quality
- Milestone: M5
- Status: Unit-covered (
auto-update.test.mjsassertsallowPrerelease = false); packaged discovery scenario Draft
E2E-067B: Dual-locale update notes and full changelog dialog (D164)
- Preconditions: The shipped
packages/sharedCHANGELOG contains alignedenandzh-CNstable history; product language can be switched. For the compact update path, use a packaged or fixture updater state with a cataloguedavailableVersion. - Steps: 1) With no available update, open Settings → Info and open Release notes. 2) Inspect the complete history, current-version marker, scrolling, and close behavior by close control, Escape, and backdrop. 3) Force or wait for update discovery so status is manual
available, in-appdownloading, ordownloaded; inspect the ambient banner and Settings Updates row, then reopen Release notes. 4) Switch UI language to zh-CN and re-inspect without invoking a new check. 5) Repeat the compact update path with a version absent from the catalog. - Expected:
UpdateState.releaseNotesis plain multi-line product highlights selected by Main from the dual-locale catalog — never a renderer-supplied URL. Both surfaces show a localized "What's new" block when notes exist and hide it when they do not. Locale change refreshes notes for the same version. The Release notes action remains available in every updater state and opens a localized, newest-first modal containing every shipped stable entry, with current and available versions identified when present. The modal traps focus, restores it after close, and does not expose a new IPC domain or feed configuration. - Specs linked:
04-ux/06-settings-ia.md,04-ux/09-interaction-patterns.md,05-security/01-security.md,06-delivery/06-release-runbook.md,08-meta/decisions-log.md(D164), ADR 0022 - Acceptance: A (app startup), Quality
- Milestone: M5
- Status: Unit-covered (
auto-update.test.mjs,changelog.test.ts); packaged UI scenario Draft
E2E-068: Fork a conversation into an independent session
- Preconditions: An idle project conversation has user, assistant, thinking, and tool history plus at least one regenerate variant. A second source conversation is running. A Temporary conversation and two retained project workspaces contain distinct same-named marker files. The idle source has a session-scoped tool grant.
- Steps: 1) Open the idle conversation overflow menu with keyboard. 2) Choose Create branch. 3) Append a prompt and change model/mode on the child. 4) Switch the visible workspace, return to the child, and read the marker. 5) Trigger the previously granted tool and verify confirmation is requested. 6) Reopen the source. 7) Restart the app and inspect both sessions. 8) Open the running conversation overflow menu. 9) Fork the Temporary conversation and invoke a workspace-required tool.
- Expected: A localized branch title appears in the same project group and is activated with composer focus. Its visible active transcript and durable project/provider/model/mode/thinking/permission configuration match the source snapshot, but regenerate pager history is absent. Child messages and later configuration changes do not affect the source; both survive restart. The running source action is disabled. No turns, notifications, artifacts, permission grants, revisions, or scratch files are copied. The marker resolves under the child's inherited project; the Temporary child remains path-less and returns
WORKSPACE_REQUIRED. - Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,04-ux/01-ui-ia.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (sessions), D (workspace), F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
sessions::tests::fork_session_clones_active_transcript_and_configuration,session-fork.test.mjs); full restart UI scenario Draft
E2E-071: Fork an assistant response without changing its source
- Preconditions: An idle conversation contains two completed user/assistant exchanges and the second assistant response has cache-token usage metadata.
- Steps: 1) Hover the first assistant response and inspect its toolbar. 2) Click Fork. 3) Confirm the activated child ends at that response and append a prompt. 4) Reopen the source and inspect it. 5) Append a prompt to the child, restart, and inspect source and child. 6) Repeat while the source is running.
- Expected: The completed-assistant toolbar contains Copy, Fork, and Regenerate only — no Delete and no Edit (D137 moved Edit to user turns). Fork is disabled during a source turn. It activates a separately titled session whose history stops at the selected response; later source turns are absent. Source text, version history, token metadata, later turns, runtime, and cache state remain unchanged. Continuing the child affects only that child and reseeds from its own remapped transcript.
- Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,04-ux/08-component-spec.md,08-meta/decisions-log.md(D134, D137) - Acceptance: C (chat stream/sessions), F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
sessions::tests::message_scoped_fork_stops_at_selected_assistant_response,session-fork.test.mjs,transcript-style.test.mjs); full restart UI scenario Draft
E2E-073: Icon-only message toolbars and editing a user prompt
- Preconditions: An idle conversation contains two completed user/assistant exchanges; one user turn was sent as a slash-template invocation.
- Steps: 1) Hover a completed assistant row and a user row, then hover and keyboard-focus each action chip. 2) Choose Edit on the first user prompt. 3) Press Escape, reopen Edit, save the prompt unchanged. 4) Reopen Edit, change the text, and save with Cmd/Ctrl+Enter. 5) After the new answer completes, use the
current / totalpager to return to the original exchange and forward again. 6) Reload the session. 7) Choose Edit on the slash-command turn and inspect the seeded text. 8) Try Edit while a turn is running. - Expected: Every toolbar chip shows its glyph only, with the label appearing as a tooltip on hover and on keyboard focus; no chip renders caption text. The assistant toolbar offers Copy, Fork, Regenerate; the user toolbar offers the pager (when variants exist), Copy, Edit, Delete. Edit replaces the prompt bubble with a wider inline textarea; Escape restores the bubble unchanged, and an unchanged save closes the editor without starting a turn. A changed save truncates the transcript from that prompt, streams a new answer, and leaves a
current / totalpager on the user turn that restores the original prompt with its full answer tail in place — surviving reload. The slash turn seeds the typed/commandform and re-expands the template on save. Edit is disabled while a turn is running. - Specs linked:
04-ux/08-component-spec.md,03-runtime/01-ipc-protocol.md,03-runtime/04-data-storage.md,08-meta/decisions-log.md(D137) - Acceptance: C (chat stream), F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
transcript-style.test.mjs); full UI scenario Draft
E2E-069: Platform-specific sidebar header behavior
- Preconditions: PI-Desktop is open with the expanded sidebar and a chat session is active.
- Steps: 1) Open Extensions on macOS windowed mode. 2) Inspect the expanded sidebar titlebar. 3) Confirm no PI-Desktop logo/title is visible and Search then Collapse sidebar appear at the right of the traffic lights. 4) Enter fullscreen and inspect the same row. 5) On Windows/Linux, confirm the brand remains visible; activate it with a pointer, then with keyboard focus and Enter/Space.
- Expected: macOS uses one 46px row with native lights at left, a usable drag region, and separate accessible Search and Collapse buttons at right; the Logo/Home brand is absent in both windowed and fullscreen modes. Windows/Linux render the canonical 20px logo beside the 15px shell name; the complete brand has a localized Home accessible name, visible hover/focus feedback, and returns the main pane to chat without clearing the active conversation or workspace. Collapse remains immediately after Search. The logo itself is theme-aware: light mode shows
build/icon_1024.png, dark mode showsbuild/logo_dark.png, swapping live withdata-theme(no reload). - Specs linked:
04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md,04-ux/08-component-spec.md - Acceptance: Quality
- Milestone: M5
- Status: Unit-covered (
renderer-branding.test.mjs,sidebar-navigation.test.mjs); rendered interaction scenario Draft
E2E-098: Sidebar collapse and expand animate as a docked transition
- Preconditions: PI-Desktop is open with the expanded sidebar and an active chat session;
prefers-reduced-motionis off. - Steps: 1) Click Collapse sidebar in the expanded sidebar header (or press the sidebar toggle shortcut). 2) Watch the sidebar during collapse. 3) Confirm the main pane expands and the collapsed titlebar now shows an Expand control. 4) Press the sidebar toggle shortcut again from the collapsed state: each press must strictly alternate collapse and expand, so the second press re-expands the sidebar (regression: it must never re-collapse). 5) Collapse and re-expand once more via the shortcut, then repeat the full round trip with the pointer controls. 6) Repeat on Windows/Linux.
- Expected: Collapse plays the
sidebar-outkeyframe (opacity + ≤8px slide plus width/flex allocation) while the aside stays in the tree, then unmounts once the animation ends; the main pane fills the freed space continuously. Expand plays thesidebar-inkeyframe and the controls return to the expanded header. On Windows the dock stays opaque during exit (sidebar-out-windows), matching the work-panel dock behavior. No layout jump precedes the animation, and focus returns to the sidebar/Expand control predictably. - Specs linked:
04-ux/08-component-spec.md,04-ux/07-ui-design-system.md - Acceptance: Quality
- Milestone: M5
- Status: Unit-covered (
sidebar-collapse-animation.test.mjs); rendered interaction scenario Draft
E2E-070: Native select menus follow the Windows theme across the app
- Preconditions: PI-Desktop is running on Windows with light and dark themes available.
- Steps: 1) In light theme, open native selects in Settings → Basics, Settings → Model configuration, Settings → Import, and one scheduled-task form. 2) Repeat every surface in dark theme. 3) Open each list after switching themes without restarting the app.
- Expected: Every closed trigger and opened native option list uses the active theme's readable foreground/background pairing. No dark-theme list falls back to a light Windows surface with light text, no light-theme list uses dark-theme ink, and changing theme updates subsequent openings. The same result holds for native selects outside Settings.
- Specs linked:
04-ux/06-settings-ia.md,04-ux/07-ui-design-system.md - Acceptance: Quality (cross-platform theme readability)
- Milestone: M5
- Status: Unit-covered (
settings-general.test.mjs); Windows rendered scenario Draft
E2E-072: Keyboard shortcut mappings persist and stay conflict-safe
- Preconditions: App running on macOS and on one Windows/Linux target with Settings open; no custom shortcut overrides are stored.
- Steps: 1) Open Settings → Shortcuts and inspect Keyboard shortcuts. 2) Change Search to an unused modifier chord. 3) invoke the new chord and then the old chord. 4) Attempt to assign that chord to the command shortcut (now opened via global search). 5) Attempt a bare letter and a reserved editing chord. 6) Restart the app and invoke the custom Search chord again. 7) Restore Search, then choose Restore defaults. 8) On macOS inspect the corresponding native application-menu accelerator after each save/reset. 9) Press and release Ctrl/Command alone, confirm an IME candidate, and hold the back/forward chord long enough to generate repeats.
- Expected: Actions are grouped as Navigation, Agent, and Window with platform-native key labels; recording has visible focus and
Escapecancels; the custom Search chord takes effect immediately, replaces the old chord, survives restart, and updates the macOS menu; duplicate, modifier-free, and reserved assignments show an inline error without changing either action; individual and global reset restore the shared defaults; Keyboard shortcuts is its own Settings destination (the eight-item rail remains unchanged). Modifier-only and IME keydowns dispatch nothing, and a held history chord traverses only once per physical press. - Specs linked:
04-ux/06-settings-ia.md,04-ux/07-ui-design-system.md,03-runtime/01-ipc-protocol.md - Acceptance: F (settings persistence), Quality (keyboard accessibility)
- Milestone: M5
- Status: Unit-covered (
keyboard-shortcuts.test.ts,settings-keyboard-shortcuts.test.mjs); rendered scenario Draft
E2E-073a: Developer mode gates the developer-tools console
- Preconditions: App running on macOS and on one Windows/Linux target; developer mode is absent or false in persisted settings and Settings -> Info is open.
- Steps: 1) Find the Developer card through Settings search. 2) Confirm the Open console action is disabled and invoke F12 plus the platform secondary shortcut. 3) Enable developer mode and open the console from Settings. 4) Close it and reopen it with F12; on Windows/Linux repeat with Ctrl+Shift+I, and on macOS inspect and invoke the View-menu developer-tools item. 5) Restart the app and invoke an enabled entry point. 6) Disable developer mode while the console is open. 7) Attempt the console IPC directly while disabled.
- Expected: No disabled entry point opens developer tools, and macOS omits the View-menu item. Enabling the persisted switch unlocks the localized Settings action and applicable platform shortcuts; each toggles the same window console after restart. Disabling the switch closes the console, disables the Settings action, removes the macOS menu item, and makes direct IPC requests fail closed.
- Specs linked:
03-runtime/01-ipc-protocol.md,04-ux/06-settings-ia.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: F (settings persistence), Quality
- Milestone: M5
- Status: Unit-covered (
settings-general.test.mjs,window-menu.test.mjs); native interaction scenario Draft
E2E-074: Concurrent session events and permissions never steal focus
- Preconditions: Sessions A and B exist in Agent mode and can run concurrently; A is visible with a draft in its composer.
- Steps: 1) Start turns in A and B, then return to A. 2) Let B emit streamed messages, tool activity, completion, and a permission request. 3) Confirm A remains visible and continue editing its draft. 4) Trigger a permission request in A as well. 5) Open B explicitly, resolve only B's request, then return to A and resolve A's request. 6) Rapidly select A then B while session details load in opposite completion order. 7) While B is loading, resolve A's Write/Edit request so its tool completion creates Review, then let B emit a BrowserPreview artifact while A is visible. Switch back to each session.
- Expected: B's background events update only B's row and retained state; they do not change A's active session/project/page, transcript, draft, scroll, or keyboard focus, and no global modal appears. Opening B reveals only B's inline card with its original countdown. Both requests remain independently actionable, and resolving B does not clear A. The final rapid selection stays on B even when A's older load finishes later. Only explicit notification or session activation may navigate. A's post-approval Review is retained only in A without a transient open/close flash in B; B's BrowserPreview carries B's session identity, updates only B's retained Browser resource, and never opens, navigates, focuses, or resizes A's panel. Explicitly returning to either session restores its own open state, tabs, active tab, and Browser resource.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/03-permission-ux.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (session isolation), E (permission isolation), Quality
- Milestone: M5
- Status: Unit-covered (
permission-inline.test.mjsfor scoped state, inline rendering contract, absolute countdown, and latest-selection guard;work-panel.test.mjsandbrowser-preview-tool.test.mjsfor session-scoped artifact retention and routing); full UI scenario Draft
E2E-075: Sidebar section context menus create sessions and projects
- Status: Manual
- Priority: P1
- Covers: A, C, D / US-UI-57
- Preconditions: App running with the expanded home sidebar visible.
- Steps:
- Right-click the
Sessionsheading label (not a session row). - Choose the single create item.
- Right-click empty chrome in the standalone session list.
- Right-click the
Projectsheading label. - Choose the single create item and cancel or complete the project picker.
- Right-click empty chrome in the project list (outside any project group).
- Right-click the
- Expected:
- Sessions context menus create/reuse a path-less temporary session and focus the composer.
- Projects context menus open the same folder picker as the heading folder-plus control.
- Existing row context menus and heading glyph buttons remain available; the section menus stay one-item and theme-matched with other sidebar menus.
- Section, session-row, and project-row right-click menus open to the right of the pointer when space permits, and remain fully within the viewport at the right edge.
- Escape and outside click dismiss the menu without creating anything.
E2E-076: Startup splash appears then yields to the main shell
- Status: Partially automated (
startup-splash-motion.test.mjscovers splash markup, motion tokens, reduced-motion, and catalog keys; full window timing remains Draft) - Priority: P1
- Covers: A, Quality / US-UI shell polish
- Preconditions: App launch path available (dev or packaged).
- Steps:
- Launch PI-Desktop.
- Observe the first painted renderer surface before bootstrap completes.
- Wait until sessions/settings bootstrap finishes.
- Repeat with OS
prefers-reduced-motion: reducewhen available.
- Expected:
- Before ready: full-window splash with brand mark, shell name, tagline, and accessible starting status (
data-testid="startup-splash"). - After ready: splash exits with a short fade (or instantly under reduced motion) and the main shell (or settings page) is interactive underneath.
- No plain unbranded “Starting…” centered text as the only boot UI.
- Overlay/dialog enter motion uses shared tokens; reduced motion keeps state changes without decorative duration.
- Before ready: full-window splash with brand mark, shell name, tagline, and accessible starting status (
- Specs linked:
04-ux/07-ui-design-system.md§8,04-ux/02-i18n-english-first.md, decisions-log D146 - Acceptance: A (app startup), Quality
- Milestone: M5
E2E-099: Brand logo follows the active theme
- Status: Draft
- Priority: P3
- Covers: Quality / US-UI shell polish
- Preconditions: App running; theme can switch between light and dark (and system) without restart.
- Steps:
- In light mode, open the app shell, an empty chat home, and the expanded sidebar (Windows/Linux) or startup splash.
- Inspect the rendered
BrandLogosource in the sidebar and startup splash, and inspect the randomly selectedHomeMascotLogopose-group animation in the empty-home hero. Hover the mascot and verify that it advances continuously while hovered. - Switch the theme to dark (Settings → Basics → Appearance, or system appearance change).
- Re-inspect the same surfaces without reloading.
- Switch back to light and re-inspect.
- Expected:
- Light and dark mode render
build/icon_1024.png/build/logo_dark.pnglive in the sidebar and startup splash without a window reload. - The empty-home hero renders the 100px mascot sprite, chooses one of nine remaining pose groups on mount, swaps discrete frames within the fixed viewport, and chooses a different group after each playback. Single-frame groups hold longer, and completed groups rest for several seconds before the next selection. Pointer hover bypasses those idle rests and continuously advances the pose groups; under reduced motion the current group's first frame remains visible.
- Sizes stay stable across theme changes (sidebar 20px, hero 100px, splash 64px), and the marks stay decorative with no click, keyboard, or focus behavior.
- Light and dark mode render
- Specs linked:
04-ux/08-component-spec.md§3.7,04-ux/07-ui-design-system.md - Acceptance: Quality
- Milestone: M5
E2E-077: Theme-aware selection and CJK section labels
- Status: Partially automated (
user-select.test.mjs,interaction-polish.test.mjs) - Priority: P2
- Covers: A, Quality / US-UI shell polish
- Preconditions: App running with at least one selectable transcript or input; language can be switched to
zh-CN. - Steps:
- Select text inside a transcript message or the composer.
- Inspect sidebar Sessions/Projects section labels in English.
- Switch the app language to
zh-CNand re-check the same labels. - Hover jump-latest (when visible), stop, search rows, and profile menu items.
- Expected:
- Selection highlight uses a neutral text-primary wash (not browser-default blue).
- Caret/form accent colors stay on the monochrome token ramp.
- English section labels may use uppercase + wide tracking;
zh-CNlabels use normal tracking without forced uppercase. - Listed chrome controls ease background/color changes via shared motion tokens.
- Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md - Acceptance: D147
- Milestone: M5
- Status detail: Source-level coverage for CSS contracts; visual selection paint remains manual.
E2E-078: Work panel and settings light-surface polish
- Status: Partially automated (
surface-polish.test.mjs) - Priority: P2
- Covers: D, Quality / US-UI shell polish
- Preconditions: App running; theme can switch to light; a work-panel tab can be opened.
- Steps:
- Switch to light theme.
- Open Settings and inspect form fields, toggles, segment controls, and shortcut keycaps.
- Open the work panel (Review / Files / Browser) beside a chat session.
- Hover file-tree rows or diff headers; focus the browser URL field.
- Open a confirmation/provider dialog and inspect the scrim.
- Expected:
- Work panel body reads as quiet
#fafafainset paper with a white header band. - Settings fields, browser URL, segment tracks, and shortcut keycaps use light inset fills; focused fields lift with a neutral ring.
- Toggle on-state keeps a white knob on the near-black track.
- Hover fills on file-tree/diff/resize ease with shared motion tokens.
- Light dialog scrim is softer than the dark 45% veil (~28% ink).
- Work panel body reads as quiet
- Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md - Acceptance: D148
- Milestone: M5
- Status detail: Source-level coverage for CSS contracts; visual surface checks remain manual.
E2E-079: User-facing catalog copy in English and Chinese
- Status: Partially automated (
packages/i18n/test/user-facing-copy.test.mjs,catalogs.test.mjs) - Priority: P2
- Covers: A, Quality / US-UI copy
- Preconditions: App running; language can switch between English and zh-CN.
- Steps:
- Inspect empty-home hint, sidebar temporary-chat section, and status/connection toasts.
- Open Settings → AI providers and the marketplace refresh action.
- Switch the app language to zh-CN and re-check the same surfaces.
- Expected:
- Copy explains outcomes in plain product language (AI provider, project, marketplace, connected/limited) rather than host/backend/repo jargon.
- English and zh-CN catalogs keep identical keys and interpolation variables.
- Crash chrome and empty-home titles remain catalog-backed in both locales.
- Specs linked:
04-ux/02-i18n-english-first.md - Acceptance: D149
- Milestone: M5
- Status detail: Source-level coverage for catalog contracts; visual wording review remains manual.
E2E-081: Send re-pins transcript and jumps to bottom
- Preconditions: Long transcript that overflows one viewport; provider configured.
- Steps:
- Start at the pinned bottom and use a trackpad to scroll upward with a small initial movement; observe the first movement and the jump-to-latest control.
- Type a new prompt in the composer and send it.
- Observe transcript position while the turn starts and streams.
- Scroll upward again with a small trackpad movement while streaming, wait for more content, then click jump-to-latest.
- Expected:
- The first upward movement immediately releases follow mode and remains stable; it does not snap back, reverse direction, or oscillate while a pending stream or resize follow frame completes.
- New streamed content does not move the manually positioned viewport, and jump-to-latest appears as soon as follow mode is released.
- On send, the transcript re-pins, hides jump-to-latest, and jumps to the bottom so the new user message (and following stream) is visible.
- Streaming continues to follow while pinned.
- Manual scroll mid-stream pauses follow and shows jump-to-latest again; clicking it resumes follow.
- Specs linked:
04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (chat stream), Quality / D151
- Milestone: M5
- Status: Partially automated (
apps/desktop/test/transcript-scroll.test.mjs); full trackpad interaction remains Draft
E2E-082: New reasoning session defaults to maximum thinking
- Preconditions: The app default provider/model resolves through pi-ai as reasoning-capable and publishes a sparse thinking-level set; a second default model is non-reasoning.
- Steps:
- Set the reasoning-capable model as the app default and create a new session.
- Inspect the Thinking trigger and the session configuration sent to the host.
- Select a lower level or Off, leave the session, and reopen it.
- Set the non-reasoning model as default and create another new session.
- Expected:
- The first new session persists and displays the highest canonical level published for the inherited reasoning model, even when the provider returns its sparse levels out of order.
- Reopening the first session preserves the user's later explicit selection.
- The non-reasoning session starts at
offand renders no Thinking trigger; missing capability metadata also falls back tooff.
- Specs linked:
03-runtime/13-model-catalog-and-selection.md,04-ux/08-component-spec.md, ADR 0018, D153 - Acceptance: B (model config), F (persistence), Quality
- Milestone: M5
- Status: Partially automated (
thinking-levels.test.ts,thinking-ui.test.mjs); full UI scenario Draft
E2E-083: Long streaming turns keep shell interaction responsive
- Preconditions: Provider configured; an active session has enough user, assistant, and tool rows to overflow several viewports; work panel can be opened; both normal and reduced-motion preferences are available.
- Steps:
- Start a long assistant response that emits frequent streamed updates.
- While it streams, hover and focus sidebar rows, type in the composer when enabled, open/collapse the work panel, and scroll the transcript away from and back to latest.
- Observe the minimap, completed history rows, composer surface, and shell chrome throughout the stream.
- Navigate to Plugins or Settings and back, then repeat with reduced motion.
- Expected:
- The current assistant row reveals content progressively and pinned follow stays at latest without visible oscillation.
- Replaceable message/tool partials are coalesced to the next paint, while terminal, permission, planning, and error states remain immediate.
- A failed tool row remains error-hued and locally expandable, but never marks the containing activity group as terminally failed. The group reports only processing duration; terminal turn styling comes from the terminal agent outcome surfaces.
- Sidebar, composer, completed message/activity rows, work panel, titlebar, and global overlays do not visibly repaint or lose pointer/keyboard responsiveness for each token update.
- Completed history remains in its stable render boundary while the active tail changes; history stays selectable, copyable, and anchored in the minimap without being rebuilt as a React subtree for every token.
- Pressing and releasing standard, icon, sidebar, send, stop, and message action controls uses one eased transform rather than a snapped scale; active streaming labels and loading skeletons keep their shimmer/pulse loop at or below 1 second.
- Minimap overflow and active-marker state remain correct without marker jitter while streamed content changes height.
- Destination, panel, focus, pressed, jump, and error feedback use one short bounded transition; no composer blur trails the transcript.
- The initial shell does not eagerly evaluate secondary destination modules; first navigation may show a compact localized loading indicator, then preserves normal page interaction after the local chunk resolves.
- Reduced motion preserves every state change and uses instant programmatic scrolling with near-zero transition duration.
- Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md - Acceptance: C (chat stream), Quality
- Milestone: M5
- Status: Unit-covered (
interaction-performance.test.mjs); rendered streaming scenario Draft
E2E-084: Long tool loop compacts before the provider context limit
- Preconditions: Provider configured with known pi-ai context/output limits; a fixture can produce repeated tool turns and large capped tool results without finishing the agent run. Automatic protection is always on and has no settings.
- Steps:
- Start one agent task whose tool loop grows past the hard budget.
- Let at least three
turn_endevents occur beforeagent_end; observe the composer/session controls, processing rows, transcript, and toasts. - Continue until a checkpoint is installed, then allow the task to finish.
- Send further prompts until a second checkpoint is installed.
- Repeat the hard-boundary turn with multiple parallel capped tool results in the compacted range.
- Restart the app, reopen the session, and send a follow-up that depends on both summarized old work and the retained recent user messages.
- Repeat with a provider fixture that returns Bedrock's
prompt is too long: N tokens > M maximumonce. - Run a turn where the model calls
new_contextwell below the hard budget. - Invoke
/compactmanually while idle.
- Expected:
- Each
turn_endis evaluated before another provider request and never marks the overall task idle; composer/config controls remain blocked untilagent_end,error, or manual-onlycompaction_end. - Every successful compaction adds exactly one divider row to the transcript, positioned immediately after the last message that checkpoint covers, and raises exactly one warning toast. Two checkpoints produce two rows, in order, and neither row replaces or hides a message.
- The
new_contextcall appears as a normal tool activity row, returns immediately, and the checkpoint is created at the following turn boundary rather than mid-turn. - Opening the context usage inspector after a checkpoint shows one line with the compaction count and the newest summary's token estimate; before any checkpoint that line is absent.
- At the hard boundary a durable checkpoint is created before the next model request. The complete visible transcript is unchanged, and the continued task stays below the model-aware safe budget.
- After a checkpoint the next provider request contains no assistant or tool message from before the boundary — only the summary and recent user messages, the oldest of which may carry the checkpoint-truncation marker — and contains no tool call without its result. Expanding the original transcript rows still shows their complete persisted results.
- Restart restores summary + retained user messages, and every earlier compaction row is still drawn. A regenerate/fork before a checkpoint boundary drops that record specifically; records anchored on surviving messages are preserved/remapped.
- The exact provider overflow removes only the failed assistant from model context, retries once after compaction, and does not loop on a second overflow.
- If automatic summary generation fails, a durable retained-tail fallback checkpoint is appended, the run stays active, and one warning explains that older model context was reduced; if fallback persistence or the safe budget guard fails,
CONTEXT_COMPACTION_FAILEDis emitted once. - If the newest checkpoint is already the transcript leaf when a follow-up prompt crosses the hard budget, the runtime rebuilds a smaller tail from the full transcript and carries the existing summary forward instead of reporting that there is no new context to compact.
- The budget reminders appear at most once each per checkpoint window, never in the transcript, and never in a persisted system prompt.
- Idle
/compactsucceeds and shows its own informational toast on top of the compaction warning, because the user asked for it. Compaction failures surface once throughCONTEXT_COMPACTION_FAILEDwithout duplicate error toasts. - Settings contains no context-management card and Settings search returns no compaction rows.
- Each
- Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md,03-runtime/03-tools-and-permissions.md,03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,04-ux/06-settings-ia.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md, ADR 0030, ADR 0049, ADR 0061, ADR 0064, D158, D203 - Acceptance: C (chat/stream), F (persistence), Quality
- Milestone: M5
- Status: Partially automated (
runtime.test.ts,context-compaction.test.mjs,assistant-turns.test.mjs, host-core transcript/session unit tests); full provider/UI journey Draft
E2E-AGENTS-001: Project instruction chain configures an agent session
- Preconditions: A project contains root
AGENTS.md, nestedpackages/api/AGENTS.md, and a provider is configured. - Steps:
- Start an Agent-mode conversation and submit a task covered by the root instruction.
- Let the agent read or edit
packages/api/handler.ts. - Add
packages/api/AGENTS.override.md, then have the agent access another file in that directory. - Edit the root instruction while the session is idle, then submit a follow-up task.
- Expected: The initial runtime receives the root chain. Before the file tool executes, the nested instruction is appended after its root source and therefore takes precedence. In one directory,
AGENTS.override.mdwins overAGENTS.md;CLAUDE.mdand.claude/CLAUDE.mdare fallback names. The idle follow-up uses changed root content rather than reusing the prior runtime. Empty, unreadable, oversized, and out-of-root instruction files do not block the turn; combined UTF-8 content is capped at 32 KiB. If path-specific resolution exceeds its two-second deadline or the host is unavailable, the file tool continues with the base chain and does not retain a sibling directory's rules. Repeated file tools in the same directory during one prompt reuse one path-resolution claim; the next prompt resolves again so changed instruction files are observed. The resolver uses the session-bound project root passed at runtime launch and does not issue a per-filesession.getRPC. - Specs linked:
03-runtime/02-agent-runtime.md - Acceptance: C (chat/stream), F (persistence)
- Milestone: M5
- Status: Partially automated (
project-instructions.test.ts,runtime.test.ts); full provider/UI journey Draft
E2E-AGENTS-002: Global settings and project menus manage instruction files
- Preconditions: PI-Desktop is running; a project can be opened.
- Steps:
- Open Settings -> Instructions without an active project and save global content.
- Start a new agent session and verify its instruction context includes the global source.
- Open the Projects view and use a project's more menu to edit and save its displayed
AGENTS.md. - Submit a prompt in a new or idle session.
- Expected: The global editor targets only
~/.pi/agent/AGENTS.md. The project editor is available only from a known project's Projects-view more menu and targets only that project's rootAGENTS.md. Both editors show their resolved paths, preserve the typed text, and save through the dedicated IPC rather than a general file write API. Global content precedes project content in the next runtime; the saved project content follows it and takes precedence on conflicts. The project editor is a viewport-level dialog. - Specs linked:
03-runtime/02-agent-runtime.md, ADR 0037 - Acceptance: C (chat/stream), D (workspace), F (persistence)
- Milestone: M5
- Status: Unit-covered (
project-instructions.test.ts); UI journey Draft
E2E-085: Expanded sidebar typography keeps list content compact
- Preconditions: The expanded sidebar contains at least one standalone session, one retained project with a session, and one empty project group; light and dark themes are available.
- Steps:
- Open the app at the default window width and inspect session titles, project/group titles, empty-state copy, section labels, and the footer's Settings, Plugins, and notification icons.
- Switch between light and dark themes, then narrow the window to the minimum supported expanded-sidebar width.
- Compare the sidebar hierarchy with 14px chat body text and inspect long session/project names.
- Expected:
- Footer action icons use the shared 32px hit target and compact 14px icon sizing; Plugins sits immediately to the right of Settings.
- Session titles, project/group titles, and empty-state copy use
--text-md(13px); section labels and secondary metadata remain at--text-sm(12px). - The hierarchy remains readable in both themes, row pitch stays compact at approximately 28–32px, and long labels truncate without shell reflow.
- Specs linked:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md, D161 - Acceptance: Quality
- Milestone: M5
- Status: Unit-covered (
sidebar-navigation.test.mjs); rendered visual scenario Draft
E2E-086: Assistant Mermaid fences render safely without blocking streams
- Preconditions: A provider can stream an assistant answer containing a valid Mermaid flowchart, an invalid Mermaid fence, and ordinary fenced code; light and dark themes are available.
- Steps:
- Stream a valid
mermaidfence slowly and observe it before and after the closing fence arrives. - Scroll the completed diagram into view, toggle source, copy the source, and switch between light and dark themes.
- Scroll upward so transcript follow is paused, then bring another completed diagram near the viewport.
- Render invalid and over-20,000-character Mermaid sources plus a payload attempting an HTML label, external image, link, or Mermaid config override.
- Expand thinking content containing a
mermaidfence.
- Stream a valid
- Expected:
- The partial stream remains a normal source code block; only the complete answer fence starts rendering, and only near the viewport. Ordinary code fences and thinking Mermaid fences retain their source presentation.
- The diagram uses bounded card chrome, remains within the transcript width, switches theme without stale colors, and exposes keyboard-accessible diagram/source and copy controls. Copy returns the original fence source.
- Diagram height changes keep a pinned transcript at the bottom but never resume follow after the user scrolls upward.
- Invalid, oversized, or unsafe input cannot fail the assistant turn, execute a link, load embedded media, add foreign HTML, or weaken strict settings; it falls back to readable copyable source when rendering is unavailable.
- Specs linked:
04-ux/08-component-spec.md§8.7,04-ux/09-interaction-patterns.md§2,05-security/01-security.md§2, D165 - Acceptance: C (chat & stream), Security, Quality
- Milestone: M5
- Status: Unit-covered (
mermaid-rendering.test.mjs); rendered security and visual scenario Draft
E2E-092: Packaged runtime is self-contained without duplicate dependencies
- Preconditions: Native macOS arm64, Windows x64, and Linux x64 packages built from clean release-host directories; a clean application profile; English and zh-CN available; external network access can be disabled while loopback remains available; a deterministic loopback OpenAI-compatible fixture provider returns code, KaTeX, Mermaid, and a terminal command.
- Steps:
- Record every compressed artifact format plus the unpacked application, ASAR, Electron runtime, locale, and unpacked-native sizes on each native runner.
- Inspect ASAR and resource inventories for sidecar, host, production modules, source maps, tests/examples/declarations, Chromium locales, and native prebuild targets.
- Configure the loopback fixture provider, disable external egress, and launch from a clean profile. Switch between English and Simplified Chinese, request the deterministic response, render common JavaScript/TypeScript, Python, Rust, shell, Mermaid, and unknown-language fences plus KaTeX and a Mermaid diagram, open Terminal, and verify host and agent-sidecar health.
- Expected: The package contains exactly one bundled agent sidecar, the target-native Rust host and
node-pty, and only configured Chromium locale packs. Renderer dependencies exist through Vite output rather than duplicate rawnode_modules; dependency source maps, tests, examples, declarations, a second agent-runtime tree, and reliably excludable non-target native assets are absent. Curated Shiki grammars highlight locally while an unknown fence stays readable as plain text. The offline shell starts and all fixture capabilities use local packaged assets; provider/update network failures do not block startup. - Specs linked:
02-architecture/01-architecture.md,02-architecture/02-tech-stack.md,03-runtime/07-process-model.md,04-ux/02-i18n-english-first.md,05-security/01-security.md,06-delivery/06-release-runbook.md, D008 - Acceptance: A (app startup), Quality
- Milestone: M5
- Status: Unit-covered (
packaging-footprint.test.mjsvalidates static dependency and builder configuration); native inventory and packaged offline launch Draft
E2E-093: Mutating tools serialize and recover from stale edit context
- Preconditions: A project-bound Agent session has a writable workspace; the provider fixture can emit two same-session
Write/Editcalls in one tool batch; a second edit can be given stale or ambiguousold_stringcontext; a Bash command can return a non-zero exit code with diagnostics. - Steps:
- Start a task that emits two mutations for the same session while also emitting independent read/search calls.
- Inspect tool timing and the transcript while the first mutation runs.
- Force the second
Editto use a context that is no longer present, then allow the agent to re-read the file and retry from the current contents. - Run a Bash command that exits non-zero and inspect its tool result and inline state.
- Repeat with an
old_stringthat appears in two locations. - If the task uses a dedicated worktree outside the advertised workspace, verify its guarded Bash edit and resulting
git diff.
- Expected:
- Read/search calls may overlap, but only one
Write/Editexecutes for a session at a time; queued mutations do not consume another global mutation slot while waiting. - The stale and ambiguous edits fail without changing the file and return a
TOOL_FAILEDmessage that directs the agent to re-read and provide a unique current context. - The non-zero Bash command is marked failed while retaining its exit code, stdout, and stderr for the agent and diagnostics.
- The retry performs one fresh read and operates on the current file; a second same-path mismatch or failed shell patch command returns a terminating tool result, stops the mutation workflow, and does not repeatedly modify an old patch artifact or its hunk headers.
- The final file contains exactly the intended change, and diff/review data contains no partial or interleaved mutation.
- Read/search calls may overlap, but only one
- Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md,03-runtime/08-error-codes.md - Acceptance: E (tools & permissions), Quality
- Milestone: M5
- Status: Unit-covered (
tool_budget.rs,tools/mod.rs,agent-runtime/runtime.test.ts); full provider/UI journey Draft
E2E-096: Recover one transient provider stream termination in place
- Preconditions: A project-bound Agent session uses a deterministic provider fixture that emits a partial assistant stream, terminates once, then succeeds on the next request; a second fixture run can terminate twice; timing logs are enabled.
- Steps:
- Start an Agent turn with the one-termination fixture and observe the partial assistant response.
- Wait for the bounded retry and inspect the transcript, session state, and model timing log after recovery.
- Repeat with the two-termination fixture and inspect the terminal error message/event and its diagnostic details.
- Reload the session and verify that only the completed response or the single terminal failed assistant remains durable.
- Expected:
terminatedis classified asSTREAM_FAILED.- The first post-stream transient failure waits for one abortable bounded backoff, removes the failed assistant from model context, and retries once without a duplicate assistant bubble or terminal error notification.
- The recovered turn emits one terminal lifecycle and keeps the same visible assistant message id. The timing log records
outcome=retryand the final outcome. - The second failure emits one terminal
STREAM_FAILEDassistant error and lifecycle event. Available details include phase, stream timing, provider status, andretryAttempt, without credentials or an unrestricted provider body. - Authentication, model-selection, context, and malformed-request failures do not enter this same-turn stream replay path.
- Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md,03-runtime/08-error-codes.md,08-meta/decisions-log.md(D186), ADR 0050 - Acceptance: C (chat & stream), F (persistence), H (diagnostics), Quality
- Milestone: M5
- Status: Unit-covered (
agent-errors.test.ts,runtime.test.ts); full provider/UI journey Draft
7A. M6 Plan and shell scenarios
E2E-104: Legacy contract values migrate to schema v11
- Preconditions: Schema-v8 fixtures contain sessions, app defaults, and scheduled records with legacy
chatvalues plus transcripts and permissions; schema-v7 and schema-v9 fixtures cover both guarded entry paths. - Steps: 1) Start host-core and allow the guarded migration (v7 first reaches v8). 2) Inspect sessions, settings, scheduled modes,
plan_approvalsfields/indexes, and the exact readable v8/v9 backup. 3) Restart and inspect the same records. 4) Repeat with malformed app settings, malformed scheduled config, invalid top-level operating modes, and an unknown or wrong-platform default shell. 5) Repeat with a platform-valid persisted shell marked temporarily unavailable and nested extensionmodefields. - Expected: Every legacy mode is
plan, Agent remains the new-session and new-task default, transcripts/permissions survive,plan_approvalsretains approval data and has artifact/execution fields, v8→v11 is one atomic transaction after its WAL checkpoint and v8 backup, v9 and v10 create readable backups, and migration failure leaves the source schema authoritative. Every malformed or invalid fixture fails closed before schema promotion. The temporarily unavailable platform-valid shell remains persisted for runtime fallback, and nested extension modes remain unchanged. - Specs linked:
00-baseline.md,03-runtime/04-data-storage.md,03-runtime/01-ipc-protocol.md,04-ux/06-settings-ia.md, ADR 0053 - Acceptance: F (persistence), H (diagnostics)
- Milestone: M6
- Status: Automated (passed 2026-08-05): host-core 139/139, including 15 focused DB tests, covers schema-v7→v8→v11, v8→v11, v9→v11, and v10→v11 guarded paths, exact readable backups, fail-closed rollback, restart, transcript, settings, scheduled-mode, approval-field, and index tests
E2E-105: Plan policy remains host-authoritative
- Preconditions: A project-bound session is idle in Plan with BrowserPreview, a plugin tool, and a forged
requestedMode = "agent"fixture. - Steps: 1) Inspect visible Plan tools. 2) Use Read/Glob/Grep and BrowserPreview. 3) Attempt Write, Edit, plugin, and unknown tools through the host with every permission mode. 4) Run Bash under Ask, Accept edits, and Auto.
- Expected: Plan denies Write/Edit/plugin/unknown tools regardless of the forged mode, grants, or Auto; Bash follows the selected permission mode. The runtime remains one pi Agent and all denials are audited.
- Specs linked:
03-runtime/02-agent-runtime.md,03-runtime/03-tools-and-permissions.md,03-runtime/05-host-core-rust.md,03-runtime/06-host-rpc-protocol.md,05-security/01-security.md, ADR 0053 - Acceptance: E (tools and permissions), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planplus host-core permission/policy and agent-runtime tool-composition tests
E2E-106: SubmitPlan rejects into editable planning and resubmits a new artifact
- Preconditions: A project-bound session is idle in Plan with a provider;
.pi/plan/is absent or empty and the workspace permits host artifact creation. - Steps: 1) Let the Agent call
SubmitPlanwith fixed title, Markdown, and question. 2) Inspect the new.pi/plan/*.mdfile byte-for-byte and theplan_approvalsrow. 3) Inspect the card's title and artifact opener; confirm the question/description, validity/deadline, and status are absent and only Approve and Reject are offered. 4) Open the approval mode menu, choose Auto, and verify the next approval defaults to Auto. 5) Reject the proposal. 6) Confirm durable mode is Plan, live state is editableplanning, the approval gate is cleared, and a later prompt is accepted. 7) Let the Agent revise and callSubmitPlanonce in that new turn with a complete snapshot. 8) Approve the second proposal with the remembered Auto mode. - Expected: Host preserves the exact submitted Markdown bytes in a new unique artifact, records its relative path/hash/size with structured title/question, and never lets the renderer or sidecar write or replace it. The title-derived artifact filename is recognizable from the title, including non-ASCII title characters. The card shows the title and opens the artifact; it does not require inline question/Markdown/hash/size or a validity/deadline indicator. The selected approval mode is remembered locally for the next approval. Rejection is terminal for the first row, leaves durable mode Plan, and returns live state to editable planning. The later prompt/resubmission creates a second complete Markdown snapshot and a different
.pi/plan/*.mdartifact; the first artifact bytes remain unchanged. Approving the second proposal with the remembered Auto mode still changes the same Agent to Agent and queues execution. - Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md,03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,04-ux/03-permission-ux.md,04-ux/08-component-spec.md,05-security/01-security.md, ADR 0053 - Acceptance: C (conversation/stream), E (permissions), F (persistence)
- Milestone: M6
- Status: Automated (passed 2026-08-05):
test:e2e:planverifies the Host artifact/approval lifecycle. The optional livetest:e2e:plan-uicase requires an env-provided OpenAI-compatible provider; the authorized run with modelgpt-5.6-lunapassed 6/6 with zero console diagnostics. It used the real controlled Composer and Send, the live Agent calledEnterPlanModethenSubmitPlan, normal rendered Ask approval resolved through preload/Main, approved execution emitted the exact durable marker, and a private env-gated WeakMap check proved the sameDesktopAgentRuntimeobject before and after approval. Main/Host/sidecar PIDs remained stable; credentials never entered CDP or output. The default no-key run remains 5/5 with the live case explicitly skipped.
E2E-107: Plan approval uses one absolute 30-minute expiry
- Preconditions: A pending Plan request exists with a controllable clock.
- Steps: 1) Record
createdAtandexpiresAt. 2) Reload the renderer and reopen the request. 3) Advance time to the deadline without resolving. 4) Attempt approval after expiry. - Expected: Renderer reload rehydrates only the still-pending row while the host remains alive, and the displayed countdown retains the original absolute deadline; rejected, expired, approved/completed, and interrupted terminal cards are not part of reload hydration. Expiry records
expired, leaves the session Plan, returnsPLAN_APPROVAL_TIMEOUT, and rejects the late response without changing mode or permission. - Specs linked:
03-runtime/06-host-rpc-protocol.md,03-runtime/08-error-codes.md,03-runtime/10-session-state-machine.md,04-ux/03-permission-ux.md, ADR 0053 - Acceptance: E (permissions), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:plan-uicovers pending renderer reload;test:e2e:planand the deterministic host-core late-expiry test cover the absolute deadline, timeout persistence, and fail-closed resolution. No terminal-card reload hydration is claimed.
E2E-108: Startup fence interrupts pending Plan work
- Preconditions: A Plan request is pending with a live approval waiter and running planning turn; host and renderer can restart independently.
- Steps: 1) Reload the renderer and list the live request. 2) Restart the host/app before resolution. 3) Inspect the
plan_approvalsrow, turn, and session after startup. 4) Submit the pre-restart response. - Expected: Renderer reload while the host remains alive preserves the still-pending row and original deadline. Full Host/app restart transactionally marks the pending row and turn interrupted/aborted before RPC service, leaves the session Plan, and returns
PLAN_APPROVAL_STALEfor the old response. No actionable stale card or execution is restored, and the UI is not required to present the interrupted terminal snapshot after restart. No process epoch field is persisted or sent. - Specs linked:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,03-runtime/07-process-model.md,03-runtime/10-session-state-machine.md,04-ux/08-component-spec.md, ADR 0053 - Acceptance: F (persistence), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planperforms a real Host restart and host-core recovery tests verify interrupted durable state; the pending renderer-reload assertion is covered by the E2E-107 UI lane.
E2E-109: Approved Plan execution is not replayed after restart
- Preconditions: A Plan request has been approved with Ask and is captured once in
queuedand once inrunningstate. - Steps: 1) Restart the host during each state. 2) Inspect the
plan_approvals.execution_stateand turn records after startup. 3) Observe provider/tool invocations and session mode. 4) Start a new user turn explicitly. - Expected: Queued/running execution fields become
interrupted, associated turns abort, no provider/tool call is replayed, and the session remains Agent because approval already committed. A new turn is accepted only after the user starts it; no interrupted terminal card or stale action is required after restart. - Specs linked:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,03-runtime/07-process-model.md,03-runtime/10-session-state-machine.md, ADR 0053 - Acceptance: C (conversation/stream), F (persistence), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planrestarts real queued and claimed executions and verifies no replay plus Agent retention
E2E-110: Scheduled Plan is rejected before any work
- Preconditions: A scheduled task is Plan and an unattended runner is available; provider, artifact, and queue writes can be observed.
- Steps: 1) Trigger the task through the unattended path. 2) Inspect the provider trace,
.pi/plan/, andplan_approvalstable. 3) Switch the task/session explicitly to Agent and run it again. - Expected: Plan is rejected before provider, artifact, approval, or queue work with
PLAN_REQUIRES_INTERACTIVE_SESSION; no background auto-approval occurs. Explicit Agent selection permits normal unattended policy. - Specs linked:
03-runtime/04-data-storage.md,03-runtime/08-error-codes.md,04-ux/01-ui-ia.md, ADR 0053 - Acceptance: F (persistence), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planverifies Plan rejection before side effects and explicit Agent execution independent of the global default
E2E-111: Active-turn, pending-approval, and configuration boundaries are enforced
- Preconditions: A session has one active Agent turn and another session is idle; a Plan run can be made pending/queued/running.
- Steps: 1) Attempt a second prompt, mode/provider/model/permission/shell configuration change, and second Plan submission during the active turn. 2) Let the turn become pending approval and repeat the prompt and configuration attempts. 3) Reject the approval. 4) Submit a later prompt and let the Agent create a revised Plan snapshot. 5) Repeat configuration after the session is editable planning.
- Expected: Active-turn/configuration changes, prompts, and a second Plan submission fail with
AGENT_BUSY/CONFLICTwhile the turn or active pending approval exists; only the originating session is blocked. Reject returns the durable session to Plan and the live state to planning, clears the gate, and permits the later prompt/new artifact. Terminal proposal snapshots do not disable input, the Composer mode chip, or model selection during the current renderer lifetime. Idle/planning configuration succeeds and no cross-session event or workspace root leaks. - Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md,03-runtime/06-host-rpc-protocol.md,03-runtime/10-session-state-machine.md,04-ux/08-component-spec.md, ADR 0053 - Acceptance: C (conversation/stream), E (permissions), Quality
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planverifies Host boundaries andtest:e2e:plan-uiverifies pending-only gating plus editable rejected/terminal states during the current renderer lifetime
E2E-112: Selectable shell catalog persists the default
- Preconditions: Host has an available platform catalog entry, a fixture can make a persisted choice unavailable, and a project-bound Agent session is idle. The Windows lane exercises the multi-choice ordering.
- Steps: 1) Inspect the catalog for the platform-valid IDs
windows-powershell,cmd,git-bash, andbash. 2) Verify settings rejects an unavailable or wrong-platform ID. 3) Select an available shell and persistdefaultCommandShell. 4) Make that persisted choice unavailable, restart, and verify the catalog selects the first available platform shell withfallback: true. 5) Execute the unchangedBashtool. - Expected: Settings persists only a valid stable shell ID; unavailable entries remain unavailable with guidance and a later unavailable persisted choice uses the intentional first-available fallback. The host invokes the effective shell while the tool/protocol name stays
Bash, and shell selection follows the idle configuration boundary. - Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md,04-ux/06-settings-ia.md,04-ux/08-component-spec.md, ADR 0054 - Acceptance: B (model/config), E (tools/permissions), F (persistence)
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planverifies catalog, validation, persistence, and restart; the deterministic host-core catalog test verifies first-available fallback when a stored shell becomes unavailable
E2E-113: Stale shell identity fails closed
- Preconditions: A Bash turn has a pinned effective shell ID/dialect; the fixture can change the effective catalog selection before spawn.
- Steps: 1) Change the effective shell ID or dialect. 2) Execute Bash with the old expected ID. 3) Inspect process creation, fallback attempts, audit, and UI error. 4) launch a fresh turn and retry.
- Expected: The first call returns
COMMAND_SHELL_CHANGED, starts no process, and does not change shell after the turn pin. The audit records the selected ID and dialect. A fresh turn snapshot is required for a later run. - Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/05-host-core-rust.md,03-runtime/06-host-rpc-protocol.md,03-runtime/08-error-codes.md,05-security/01-security.md, ADR 0054 - Acceptance: E (tools/permissions), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planverifies stale dialect rejection before marker creation plus host-core stale ID/dialect tests
E2E-114: Bash streams stdout and stderr independently
- Preconditions: A selected shell is available and a deterministic command writes interleaved stdout and stderr chunks.
- Steps: 1) Execute the command through
Bash. 2) Observe host/RPC/UI output events. 3) Inspect the final bounded result and transcript row. - Expected: stdout and stderr remain separate, ordered per tool call, and visible while the process runs. Final output preserves truncation metadata; no chunks cross sessions or turns and the Bash protocol name is unchanged.
- Specs linked:
03-runtime/06-host-rpc-protocol.md,03-runtime/09-logging-and-observability.md,03-runtime/16-tool-result-limits.md,04-ux/09-interaction-patterns.md, ADR 0054 - Acceptance: C (stream), E (tools), Quality
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planverifies distinct stdout/stderr notifications and final tool identity; host/runtime stream tests cover bounded accumulation and session isolation
E2E-115: Bash timeout uses 60 seconds and a bounded override
- Preconditions: A selected shell can run a command longer than 60 seconds; host clock is observable.
- Steps: 1) Run without a timeout override. 2) Observe the 60-second deadline. 3) Run with an in-range override. 4) Submit zero, negative, and over-300-second overrides.
- Expected: Missing timeout uses exactly 60 seconds and returns
TOOL_TIMEOUTafter process-tree shutdown. In-range values work within 1–300 seconds; out-of-range values fail validation and never spawn. - Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md,03-runtime/08-error-codes.md,03-runtime/16-tool-result-limits.md,05-security/01-security.md, ADR 0054 - Acceptance: E (tools), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04): long-timeout
test:e2e:planmeasured the no-override timeout at 60,024 ms and verified in-range plus invalid bounds without delayed marker writes
E2E-116: Bash abort shuts down the complete process tree
- Preconditions: A Bash command starts a child and grandchild that emit delayed output; the originating session is running.
- Steps: 1) Start the command. 2) Abort the active turn. 3) Inspect process descendants, output events, audit, and turn state after the shutdown grace.
- Expected: The process group/job tree is terminated, no descendant remains, no later output arrives, the turn returns
TURN_ABORTED, and the workspace is not rolled back automatically. - Specs linked:
03-runtime/03-tools-and-permissions.md,03-runtime/07-process-model.md,03-runtime/08-error-codes.md,03-runtime/16-tool-result-limits.md,05-security/01-security.md, ADR 0054 - Acceptance: C (abort), E (tools), H (diagnostics), Security
- Milestone: M6
- Status: Automated (passed 2026-08-04):
test:e2e:planaborts a real descendant process tree and verifies no late marker/output; host-core tests verify cancellation registry cleanup
E2E-117: Agent/Plan/Goal UX and locales contain no Chat controls
- Preconditions: App can run in English and zh-CN with an idle session, Plan artifact fixture, shell settings, and command palette available.
- Steps: 1) Inspect Agent/Plan/Goal, permission, artifact approval, and shell controls in English. 2) Enter Plan and inspect planning/approval/queue/ terminal states while the renderer remains alive. 3) Approve and reject a proposal, confirming that the approval surface disappears after host confirmation. 4) Use the command palette and
/plan-mode//agent-modeslash aliases to switch the active idle session, confirming the Composer chip changes immediately. Type a prompt after either alias in the same draft and send it; confirm the mode changes and the prompt remains as a visible user turn. Send an alias alone and confirm it remains a local mode switch without creating an empty transcript turn. If prompt dispatch fails, confirm the complete draft remains editable. 5) Reload after a terminal proposal and inspect the session while asserting that Electron Main and Host process identities did not change. 6) Repeat in zh-CN. 7) Search visible commands for the removed Chat mode and request-changes controls. Host/app restart recovery is exercised separately by E2E-108 and E2E-109. - Expected: Agent is the default; the left-of-input Composer chip is the sole active-session Agent/Plan/Goal control; Plan and Goal show Ask/Accept edits/Auto, the submitted title, an artifact opener, remembered approval mode, approve/reject only, shell catalog/fallback status, and localized failed-closed states. No Chat mode,
/chat-mode, request-changes action, inline Markdown/hash/size requirement, or stale actionable queue is exposed; terminal checkpoint metadata may remain non-actionable only for the current renderer lifetime, while the composer approval surface is removed after host-confirmed resolution. Renderer reload does not rehydrate rejected, expired, approved/completed, or interrupted terminal cards. Host/app restart does not replay work or restore stale actions, and the UI is not required to present the interrupted terminal snapshot;page = "chat"remains an internal route. - Specs linked:
01-product/01-product-scope.md,04-ux/01-ui-ia.md,04-ux/04-builtin-commands.md,04-ux/03-permission-ux.md,04-ux/06-settings-ia.md,04-ux/08-component-spec.md,04-ux/02-i18n-english-first.md, ADR 0053, ADR 0054 - Acceptance: C (conversation), Quality
- Milestone: M6
- Status: Automated (passed 2026-08-04): raw-CDP
test:e2e:plan-uiuses an env-gated Electron Main probe against the existing Host, asserts stable Electron/Host PIDs, covers pending restore, live terminal controls, rejected and approved/completed terminal-card absence after renderer reload, EN/zh-CN, and 1280×800 / 900×700 rendering. E2E-108/E2E-109 cover Host restart interruption, stale-action rejection, and no replay.
E2E-118: A regenerated turn keeps its final answer after the branch archive
- Preconditions: A project-bound Agent session whose transcript already has one completed exchange, a provider that streams a multi-tool turn long enough for the final assistant message to land through the persistence outbox, and read access to
<data_dir>/sessions/<id>.jsonl,<id>.revisions.jsonl, and themessagesindex. - Steps: 1) Regenerate the assistant answer so the root user turn carries
revisionCount/activeRevisionand revision 1 is archived. 2) Let the re-run finish a turn that ends with tool calls followed by a final assistant message. 3) Immediately afteragent_end, inspect the transcript file, themessagesrows, and the archived revision payload. 4) Reload the session. 5) Page the root bubble back to revision 1 and forward again. - Expected: The final assistant message is present in the transcript file, in the index, and as the last message of the archived branch. Every message of the turn keeps its owning
turn_id. The root carriesrevisionCount = 2andactiveRevision = 2, reload shows the complete turn, and paging restores each branch whole. Nosession.replaceMessagescall is made on the turn-completion path. - Specs linked:
03-runtime/04-data-storage.md§4.9/§7,03-runtime/06-host-rpc-protocol.md§4, ADR 0041, ADR 0060 - Acceptance: C (conversation), F (persistence), H (diagnostics), Quality
- Milestone: M6
- Status: Covered by host-core unit tests (2026-08-06):
save_active_branch_revision_keeps_a_message_appended_after_its_readarchives with a message appended after the read and asserts the transcript, the archived payload, and the index(seq, turn_id)rows;replace_messages_preserves_owning_turn_idscovers the remaining rewrite callers. UI paging remains manual.
E2E-119: Parallel subagents report back without entering the parent's context
- Preconditions: A project-bound Agent session with a workspace containing
.pi/agents/scout.md(read-only, notoolskey),.pi/agents/fixer.md(tools: Read, Edit),.pi/agents/pinned.md(model:naming a second configured provider) and.pi/agents/broken.md(missingname); a provider whose stream can be driven to emit twoTaskcalls in one assistant message; permission modeaskso a delegate'sEditis gated; read access to<data_dir>/sessions/<id>.jsonland themessagesindex. - Steps:
- Prompt a turn in which the assistant emits two
Taskcalls —scoutandpinned— in one message. Observe the delegation card while both run and after each one settles; collapse it, then expand each node. - Prompt a turn in which two
fixerdelegates each edit a different file, and answer only the first permission card. - Answer the second card, then prompt a third turn where two
fixerdelegates edit the same file. - Start a fan-out and press Stop while one card is on screen and another is queued.
- Prompt a
Taskcall namingbroken, then one naming an agent that does not exist, then one whose definition pins an unconfigured provider. - Switch the session to Plan, then to Goal, and inspect the tool catalog.
- Reload the session and re-expand the delegation card and every
Tasknode.
- Prompt a turn in which the assistant emits two
- Expected:
- Both delegates in step 1 run concurrently, and
pinnedstreams on its own provider/model while the parent keeps the session's. - The two
Taskcalls in step 1 form one full-width delegation card. While active it opens once and its header updates the subagent and settled counts; after settlement it keeps the user's expansion choice and reports aggregate success, warning, or issue state plus elapsed time. A singleTaskstill uses the compact one-line row. - The expanded card shows one main-agent root connected to
scoutandpinnedin parent-row order, with no invented edge between delegates. Each node shows its agent, short description, explicit outcome, duration and step count. Expanding a node shows the brief, report exactly once, andstatus/turns/toolCalls. Delegate rows appear only inside that node, never in the turn stream or the minimap. - The parent's next request contains the reports and no delegate message or tool row; the rows are nonetheless present in the transcript file and the index with
meta.parentToolCallIdandmeta.agentName. - Only the head permission card is rendered; it names the asking delegate and the number waiting behind it. Answering it reveals the next card, and neither answer resolves the other request.
scoutcannot callEditorWriteat all;fixercan. Same-file edits in step 3 apply in a defined order and neither loses the other's write.- Stop denies both the shown and the queued request, and both delegates end
abortedin text and icon inside their ownTasknodes — the parent turn ends once and the aggregate card settles with a warning. brokenis absent from the catalog with a launch diagnostic and the session keeps its other three delegates; an unknown agent and an unresolvable model pin each fail as aTasktool error naming the cause, with no fallback to the session provider and no turn failure.Taskis absent from the catalog in Plan and Goal.- After reload the card is collapsed by default; re-expanding preserves node order, attribution, outcome and nested content exactly as they appeared live.
- Both delegates in step 1 run concurrently, and
- Specs linked:
03-runtime/02-agent-runtime.md§5f/§7.2b/§8,03-runtime/03-tools-and-permissions.md§10.2,03-runtime/04-data-storage.md§4.7a,04-ux/03-permission-ux.md§6a,04-ux/08-component-spec.md§9.9, ADR 0062, decisions-log D201 - Acceptance: C (conversation), E (tools & permissions), F (persistence), Security, Quality
- Milestone: M6
- Status: Covered by unit tests (2026-08-06):
packages/sharedsubagent-definition.test.tsandpackages/agent-runtimesubagent-definitions.test.ts(frontmatter, tool filtering, caps, malformed documents, project-shadows-builtin);subagent.test.ts(report bounding, turn cap, abort, event attribution, prompt framing) andpath-lock.test.ts(same-path ordering, concurrency cap); desktoppermission-inline.test.mjs(queue order, id-matched removal, tool-call removal, abort denying the queue, card copy),subagent-wiring.test.mjs(main-process discovery and model pins) andsubagent-transcript.test.mjs+assistant-turns.test.mjs(nesting, single report print, memoization), plussubagent-topology.test.mjs(delegate detection, structured outcomes and aggregate counts). Full multi-provider fan-out and rendered topology interaction remain manual.
E2E-097: Tool results read as structured blocks, never JSON
- Preconditions: A project-bound Agent session with permissions allowed for the turn; a plugin tool whose result is an arbitrary record is installed; the workspace contains a file large enough to trip host truncation.
- Steps:
- Run one turn that reads a source file, globs a directory, greps a token, greps again with
outputMode: filesWithMatchesandcount, edits a workspace file, edits a scratch-root file, runs a failing shell command, and calls the plugin tool. - Inspect each collapsed activity row, then expand every row in light and dark.
- Click a Glob path and a Grep hit heading.
- Trigger a high-risk tool so the inline permission card appears.
- Read a truncated file and copy each block.
- Run one turn that reads a source file, globs a directory, greps a token, greps again with
- Expected:
- No expanded row shows escaped JSON, and no payload appears twice.
- Read/Write show highlighted content; Bash shows command, output, and error-hued stderr as separate blocks with empty channels omitted; Glob shows a path list; Grep shows hits grouped per file with line numbers in
contentmode, a path list infilesWithMatches, and per-file totals incount; the failing command carries anexit 1chip. - The workspace edit shows no inline diff (its ReviewChangeCard owns it); the scratch edit shows a compact diff and a
scratchchip. - The plugin result renders label/value fields and labeled blocks, not a blob.
- Clicking a path opens it in the work panel; paths outside the workspace root are not clickable.
- The permission card's args preview uses the same blocks.
- Host truncation markers stay visible, a
truncatedchip appears, a hostnoticerenders as a neutral note under the block it qualifies, capped lists report the hidden remainder, and copy yields the full payload.
- Specs linked:
04-ux/08-component-spec.md§9, §10.2,08-meta/decisions-log.md(D192) - Acceptance: C (chat & stream), E (tools & permissions), Quality
- Milestone: M5
- Status: Unit-covered (
tool-presentation.test.mjs,transcript-style.test.mjs); full UI journey Draft
E2E-098: A turn that produces no visible text re-runs once
- Preconditions: A project-bound Agent session uses a deterministic provider fixture that ends one turn with no tool call and no text — once with reasoning content present, once with nothing at all; a second fixture run ends both the first turn and the re-run that way; timing logs are enabled.
- Steps:
- Start an Agent turn with the reasoning-only fixture and watch the transcript while the runtime recovers.
- Inspect the transcript, session state, and model timing log afterwards.
- Repeat with the nothing-at-all fixture.
- Repeat with the twice-silent fixture and inspect the terminal error message, its details disclosure, and its action button.
- Click the error's retry action.
- Reload the session and verify what stayed durable.
- Expected:
- The recovered turn keeps the same visible assistant message id, emits one terminal lifecycle, and shows no error. The user sees only the answer.
- The empty assistant is removed from model context before the re-run, so the provider never receives two assistant messages in a row, and it is never appended to the durable transcript.
- The timing log records
outcome=silentwiththinkingOnlytrue for the reasoning-only fixture and false for the nothing-at-all fixture, then the re-run's own outcome. - The second silence emits one terminal retriable
EMPTY_MODEL_RESPONSEassistant error and lifecycle event; the message names both attempts, and the retry action re-sends the last prompt. - A turn whose text is empty because it requested tools is untouched, and so is an aborted or already-failed turn.
- Only one re-run happens per prompt, including after context-overflow recovery within the same prompt.
- Specs linked:
03-runtime/02-agent-runtime.md§5e, §7,03-runtime/08-error-codes.md§3.2,08-meta/decisions-log.md(D193) - Acceptance: C (chat & stream), F (persistence), H (diagnostics), Quality
- Milestone: M5
- Status: Unit-covered (
runtime.test.ts); full provider/UI journey Draft
E2E-099: Scoped search stays inside its budget and the agent narrates
- Preconditions: A project-bound Agent session; the workspace contains a multi-megabyte source file, a minified bundle with a
.mapsibling (one line, megabytes long), a binary file, and a dependency tree excluded by.gitignore. - Steps:
- Inspect
tools.listforRead,Glob, andGrep. - Read the multi-megabyte file, then read it again from the reported next offset.
- Grep a token that hits the minified bundle and its
.map. - Grep the same token with
pathpointing into the ignored dependency tree, then withincludenarrowing to one extension, then withoutputMode: filesWithMatchesandcount. - Glob a broad pattern, and Glob with
pathandlimit. - Read the binary file.
- Run a shell command that prints far past the shell budget on stdout, then one that fails after printing progress noise on stderr, and open the spill file named in each marker.
- Ask a question that needs several tool batches, and watch the transcript between batches.
- Inspect
- Expected:
- Every description carries its parameters and the real limit numbers.
- No single tool result exceeds its budget: 48 KB for Read/Glob/Grep, 96 KB for Bash. Read reports
offset,lineCount,fileBytes, and a next-offsetnotice; the second read continues without overlap and reportstotalLinesonce the end is reached. - No file size is ever refused. Lines from the bundle and the
.maparrive clipped at 2000 chars and the clip count appears innotice, so one line cannot consume the result. - An explicit
pathreaches into the ignored tree; without it the same search returns nothing from there.include,outputMode, andheadLimiteach shrink the payload, and results order newest-modified first. - The binary read fails with
TOOL_BINARY_CONTENTand no binary reaches the model; Grep skips it silently. - Bash stdout keeps its head, stderr keeps its tail, both markers name which end survived and the spill path, and each spill file opens with the fuller output.
- The agent answers in the language the user wrote in, precedes each tool batch with a sentence about what it is doing, never leaves more than one batch without new visible text, and ends with a self-contained result.
- Specs linked:
03-runtime/16-tool-result-limits.md,03-runtime/02-agent-runtime.md§7,08-meta/decisions-log.md(D194) - Acceptance: C (chat & stream), E (tools & permissions), Quality
- Milestone: M5
- Status: Unit-covered (host-core
toolstests,runtime.test.tsprompt assertions); full provider/UI journey Draft
E2E-100: A pasted MCP server runs, and only where it is scoped
- Preconditions: Two projects on disk,
~/work/apiand~/personal/site. A local stdio MCP server available on PATH. An Agent session per project. - Steps:
- Extensions → MCP → Import from JSON. Paste a
mcpServersdocument holding three servers: one valid stdio entry, one remote entry withurland notype, and one stdio entry with nocommand. - Confirm the import, then open the imported stdio server and press Test connection.
- Leave the server at Everywhere and ask the agent in each project to list its available tools.
- Set the server to These projects, with only
~/work/apipicked. - Ask again in each project.
- In the already-open
~/personal/sitesession — assembled while the server was global — ask the agent to call one of the server's tools by name. - Edit the server's
envand save; ask in~/work/apiagain. - Rename the server and re-scope it; ask once more.
- Point the server's command at a binary that does not exist, save, and open a new session.
- Extensions → MCP → Import from JSON. Paste a
- Expected:
- Two servers import; the third is listed as skipped with "a stdio server requires command". The remote entry lands as
httpwith its url intact. - Test reports connected with the tool names it found, and the row's glyph turns from connecting to ready.
- While global, both sessions see
mcp_<serverId>_<tool>names. - After narrowing, only the
~/work/apisession sees them; the summary chip reads "1 project" and names it. - The stale call from step 6 fails with
TOOL_NOT_FOUNDand "not active for this session" — scope holds at dispatch, not only in the catalog. - The
envedit drops the connection: the next assembly re-handshakes, and the tool's behaviour reflects the new value. The rename in step 8 does not reconnect anything. - The broken command records
failedwith a message, contributes no tools, and is not re-dialled on the following session assembly; pressing Test retries it.
- Two servers import; the third is listed as skipped with "a stdio server requires command". The remote entry lands as
- Specs linked:
07-plugins/01-plugin-system.md§12,03-runtime/01-ipc-protocol.md§12a,08-meta/decisions-log.md(D192, D193) - Acceptance: E (tools & permissions), Quality
- Milestone: M5
- Status: Unit-covered (
apps/desktop/test/user-mcp.test.mjs,packages/shared/src/mcp-import.test.ts, host-coremcp_serverstests); full UI journey Draft
E2E-101: A user skill is written once and scoped per project
- Preconditions: Two projects on disk. An Agent session in each.
- Steps:
- Extensions → Skills → New. Save with an empty description.
- Fill in a description, write a body, and save.
- Ask the agent in each project to use the skill by name.
- Set the skill to These projects with only the first project picked, then switch it Off and back to These projects.
- Ask again in each project.
- In the first project's session, narrow the skill to the second project and immediately ask the agent to invoke it.
- Paste a body over 128 KB.
- Switch the app language to 中文 and revisit every surface above.
- Expected:
- Saving without a description is refused with a message naming the field: the description is the only part that enters the prompt.
- The base prompt carries the skill's id, name and trimmed description and not its body; the body arrives only through the
Skilltool. - Toggling Off and back restores the picked project without re-picking it.
- After narrowing, only the scoped project's session can invoke it; the other gets "not enabled for this project".
- Step 6 fails in the already-open session too — the scope is re-read when the body is loaded, not trusted from the catalog that listed it.
- The byte counter warns before the 128 KB cap and the save is refused past it.
- Every label, empty state, error and count renders in Chinese, with counts reading naturally at 0, 1 and many.
- Specs linked:
07-plugins/01-plugin-system.md§12.3,03-runtime/01-ipc-protocol.md§12b,08-meta/decisions-log.md(D174, D192, D194) - Acceptance: E (tools & permissions), Quality
- Milestone: M5
- Status: Unit-covered (host-core
user_skillstests,apps/desktop/test/extensions-page.test.mjs); full UI journey Draft
E2E-102: Composer file and image paste becomes a session-scratch reference
- Preconditions: The app is running with an Agent session in a project and a home composer available. The OS clipboard contains a text snippet, one or more local files (including a filename with whitespace), and an image in separate paste attempts. Record the app data directory and the session id.
- Steps:
- Paste text-only into the composer and confirm the text is inserted by the native textarea path.
- Paste one local file, then paste multiple files including a spaced name.
- Paste an image from the OS screenshot/clipboard provider.
- Inspect the draft before sending: confirm each materialized item is a removable leaf-name chip and no scratch absolute path occupies the textarea. Hover/focus chips to inspect their full paths, remove one, then send the prompt and inspect the session message.
- Inspect
<data_dir>/scratch/<sessionId>/pasted/and compare the saved bytes with the source files/image. Check the projectgit status. - Delete the session, then confirm its scratch directory and pasted files are removed.
- Expected:
- Text-only paste remains native and is not routed through the file bridge.
- Each file/image is saved with a sanitized, UUID-backed unique name under the session scratch root, while its chip shows only the sanitized original leaf name. Duplicate leaf names remain separate references.
- The dispatched prompt and persisted user message contain the remaining complete paths with whitespace quoting, not labels or binary bytes, and the agent can use its normal file tools to read the materialized files.
- A home paste creates or reuses a durable session before writing. The workspace remains clean and no workspace artifact row is created.
- Deleting the session removes the pasted files with the rest of scratch.
- Specs linked:
04-ux/08-component-spec.md§11.7–11.8,03-runtime/01-ipc-protocol.md§13c,03-runtime/03-tools-and-permissions.md§4b,03-runtime/04-data-storage.md,08-meta/decisions-log.md(D197, D209), ADR 0059, ADR 0070 - Acceptance: C (conversation & stream), E (tools & permissions), F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
apps/desktop/test/composer-paste-files.test.mjs); full UI journey Draft (do not run E2E locally unless explicitly requested)
E2E-102a: Composer file reference results use compact leaf names
- Preconditions: The app is running with an Agent session in a workspace containing nested files, duplicate leaf names in different directories, and a directory whose name contains whitespace.
- Steps: 1) Type
@and filter to the nested and duplicate entries. 2) Inspect the visible rows, then hover for full-path tooltips and inspect their accessible names. 3) Accept a file and confirm a leaf-name chip appears while the textarea omits its path. Accept a directory result and continue to a child file. 4) Send the completed references and inspect the persisted user message. - Expected:
- Each result persistently renders only its leaf name; directories retain a trailing
/, and no parent path consumes horizontal row space. - The tooltip and accessible name retain the complete relative path so duplicate leaf names remain distinguishable.
- File acceptance retains the original complete
entry.pathbehind the chip; directory acceptance retains literal path continuation. At dispatch the sent and persisted prompt contains each complete path with existing whitespace quoting, and the agent can read both selected files normally.
- Each result persistently renders only its leaf name; directories retain a trailing
- Specs linked:
04-ux/08-component-spec.md§11.8,04-ux/09-interaction-patterns.md§8a,03-runtime/01-ipc-protocol.md§13c,08-meta/decisions-log.md(D124, D209), ADR 0024, ADR 0070 - Acceptance: C (conversation & stream), Quality
- Milestone: M5
- Status: Unit-covered (
apps/desktop/test/composer-file-reference-display.test.mjs); full UI journey Draft (do not run E2E locally unless explicitly requested)
E2E-102b: Unanswered Stop restores compact file-reference drafts
- Preconditions: An Agent session can delay its first assistant event. The draft contains ordinary text, one workspace reference, two pasted references with duplicate leaf names, and a canonical path containing whitespace.
- Steps: 1) Send the mixed draft and stop before assistant text, thinking, or any tool row begins. 2) Inspect the restored composer and transcript. 3) Send the restored draft again and inspect the persisted user message. 4) Repeat, allow partial assistant output to begin, then stop.
- Expected:
- Unanswered Stop removes the just-sent user row and restores the original ordinary text plus leaf-name chips in stable order.
- Relative and scratch absolute paths never appear in the restored textarea; duplicate labels remain distinct references.
- Resending serializes each exact canonical path once with existing whitespace quoting.
- Stop after reply start preserves the partial aborted transcript and does not restore or duplicate text or chips.
- Scratch bytes remain under the existing session lifecycle.
- Specs linked:
04-ux/08-component-spec.md§11.5/§11.8,04-ux/09-interaction-patterns.md§3.2/§8a.2,03-runtime/10-session-state-machine.md,08-meta/decisions-log.md(D209), ADR 0070 - Acceptance: C (conversation & stream), F (persistence), Quality
- Milestone: M5
- Status: Unit-covered (
composer-file-reference-display.test.mjs,transcript-style.test.mjs); full UI journey Draft (do not run E2E locally unless explicitly requested)
E2E-103: A subagent written in the UI reaches Task, scoped and shadowed
- Preconditions: The app is running with two projects registered, A and B, and an Agent session available in each. Neither project has a
.pi/agents/directory. Record the app data directory. - Steps:
- Extensions → Subagents → New. Name it
log-reader, write a description and a body, grantRead, Grep, Bash, and save. Confirm the row shows theTask(log-reader)handle and tintsBashas a mutating grant. - In a session in project A — without restarting the app — ask the agent to delegate to
log-readerand confirmTaskaccepts the name. - Set the row's scope to project B only. Start a new turn in project A and confirm
Taskno longer offerslog-readerand the row reports it is not active here; repeat in project B and confirm it is offered there. - Return the scope to global. In the read-only list, copy
exploreras your own definition, save it unchanged, and confirm the registry copy appears whileexplorerno longer appears as a builtin — the copy is whatTaskwould run. Rename the copy and confirm both entries are then listed. - Write
<project A>/.pi/agents/log-reader.mdwith a different description. Reopen the tab and confirm the registry row reports the project document as the winner, and that the read-only list shows the project definition. - Delete
log-readerfrom the registry and confirm<data>/agents/no longer holds its document while the project document still loads.
- Extensions → Subagents → New. Name it
- Expected:
- Creating, editing, scoping and deleting a definition never writes into a project;
git statusin both projects stays clean except for the file written by hand in step 5. - A saved edit takes effect on the next prompt with no restart, because the catalog is re-read per launch.
- A duplicate name is refused with
SUBAGENT_INVALIDrather than silently suffixed, and the registry cannot exceed 16 definitions. - Builtin and project rows offer no enable switch and no scope control; their only actions are reveal (project only) and copy.
- Precedence is project > user registry > builtin everywhere it is reported.
- Creating, editing, scoping and deleting a definition never writes into a project;
- Specs linked:
03-runtime/01-ipc-protocol.md§12c–§12d,03-runtime/02-agent-runtime.md§5f,04-ux/01-ui-ia.md§3.5,08-meta/decisions-log.md(D192, D201, D202), ADR 0062, ADR 0063 - Acceptance: E (tools & permissions), F (persistence), Quality
- Milestone: M6
- Status: Partly automated (
scripts/e2e-subagents.mjs— the registry over real host-core RPC, then its documents through the real loader: scope filtering both ways, the 16 cap, project > user > builtin precedence, and a malformed document degrading to a diagnostic) plus unit coverage (host-coreuser_subagentstests,packages/agent-runtimesubagent definition tests,apps/desktop/test/extensions-page.test.mjs,apps/desktop/test/subagent-wiring.test.mjs); the UI journey throughTaskstays Draft (do not run E2E locally unless explicitly requested)
E2E-120: Global plugin launch, next-turn editing, and stopped throughput
- Preconditions: Install and enable a panel plugin whose Chinese display name is
无限画布. Configure a provider that streams slowly enough to stop a partial answer. Run once on macOS and once on Windows. - Steps:
- Immediately after PI-Desktop finishes booting, leave it unfocused and press Option+Space on macOS or Alt+Space on Windows while another application owns the foreground window. Confirm the first invocation promptly reveals a fully rendered, centered launcher on the pointer's display without a blank initialization frame, native close, minimize, maximize, resize, or taskbar controls. Confirm Windows does not show the active application's system menu.
- Search separately for
无限,wuxianhuabu, andwxhb. Use Up/Down and Enter for one run and click for another; confirm the existing plugin panel opens. Confirm Chinese IME candidate Enter does not open a result. - Start an Agent answer. While it streams, type the next draft and change Thinking, permission mode, and Agent/Plan/Goal. Confirm every selection is editable, Stop remains present, and Send cannot dispatch.
- Stop after partial output. Confirm the partial answer remains, the queued configuration becomes durable only after termination, and the next turn uses the final selection rather than any intermediate selection.
- Inspect the stopped answer's conversation statistics, reload the session, and inspect again.
- Expected:
- Post-boot warm-up removes BrowserWindow and renderer loading from the first shortcut's visible path. Launcher search returns only enabled, ready panel plugins and every invocation starts with an empty, focused query. Escape and focus loss hide it without closing the main application.
- No running turn observes the staged mode/model/thinking/permission change, and a second prompt cannot be sent concurrently.
- Stopped throughput is present before and after reload. It uses exact output usage when the provider supplied it; otherwise the UI labels the persisted four-code-point estimate as approximate.
- Specs linked:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md§5b/§9,04-ux/07-ui-design-system.md§8.2–8.3,04-ux/08-component-spec.md§11,04-ux/09-interaction-patterns.md§1/§3, D211, D212, ADR 0072, ADR 0073 - Acceptance: C (conversation & stream), F (persistence), G (plugins), Quality
- Milestone: M6
- Status: Unit/source-contract covered; full cross-platform UI journey Draft (do not run E2E locally unless explicitly requested)
E2E-121: Goal approval resumes autonomous acceptance-criteria execution
- Preconditions: A project-bound session has a configured provider and is idle in Agent mode; the workspace permits host artifact creation and has no prior test goal artifact.
- Steps: 1) Switch the session to Goal and let the Agent call
EnterGoalMode, thenSubmitGoal(title, markdown, question). 2) Inspect the exact Markdown bytes in the new.pi/goal/*.mdartifact and the matchingplan_approvalsrow. 3) Confirm the shared approval card exposes only Approve/Reject and that Goal denies Write/Edit/plugin tools while Bash follows the selected permission mode. 4) Approve with Ask and observe the same Agent resume in Agent mode. 5) Inspect the final response for criterion-by-criterion verification or an explicit boundary, then reload the session. - Expected: Goal uses the Plan approval pipeline without a second planner; the artifact is immutable and the row records
kind = goal, path, hash, size, and execution state. Approval is a separate user decision, transitions the session to Agent, and starts autonomous work only after approval. The transcript remains reviewable after reload and no scheduled/unattended Goal run can bypass the approval boundary. - Specs linked:
03-runtime/02-agent-runtime.md,03-runtime/03-tools-and-permissions.md,03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md,03-runtime/10-session-state-machine.md,04-ux/01-ui-ia.md,04-ux/08-component-spec.md, D198 - Acceptance: C (conversation & stream), E (tools & permissions), F (persistence), H (diagnostics), Security
- Milestone: M6+
- Status: Unit/source-contract covered (
packages/agent-runtimeand host-core Goal tests); full UI journey Draft (do not run E2E locally unless explicitly requested)
E2E-122: Plugins request and deliver native notifications
- Preconditions: An installed plugin declares and is granted
notify; the desktop platform supports Electron native notifications; the OS notification permission is in its initial or previously denied state. - Steps: 1) From the plugin process, call
getNotificationPermission(). 2) CallrequestNotificationPermission()and observe the native permission/probe result. 3) CallshowNativeNotification({ title, body }). 4) Repeat with a plugin that lacksnotify, and on a platform where native notifications are unsupported. - Expected: The first status is
unknown,denied, orunsupported; the request returns a best-effortgranted,denied, orunsupportedresult; a granted plugin receives{ shown: true, permission: "granted" }for native delivery, while denied/unsupported delivery returnsshown: falsewithout crashing the plugin. Missingnotifyfails withPERMISSION_DENIED. Native plugin notifications do not add durable task inbox rows or activate a chat session. - Specs linked:
07-plugins/01-plugin-system.md,07-plugins/03-plugin-api.md,07-plugins/13-plugin-permissions-matrix.md, ADR 0074 - Acceptance: E (tools & permissions), G (plugins), Security, Quality
- Milestone: M6+
- Status: Unit/source-contract covered; full cross-platform OS permission journey Draft (do not run E2E locally unless explicitly requested)
8. Traceability Matrix
| Acceptance | Scenarios |
|---|---|
| A — App startup | E2E-001, E2E-002, E2E-003, E2E-004, E2E-067, E2E-076, E2E-079, E2E-092, E2E-097 |
| B — Model config | E2E-005, E2E-006, E2E-007, E2E-038, E2E-050, E2E-052, E2E-055, E2E-066, E2E-080, E2E-082 |
| C — Conversation & stream | E2E-008, E2E-008a, E2E-009, E2E-010, E2E-011, E2E-011a, E2E-011b, E2E-031, E2E-040, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-052, E2E-053, E2E-054, E2E-055, E2E-059, E2E-059a, E2E-060c, E2E-060d, E2E-061, E2E-061a, E2E-062, E2E-064, E2E-065, E2E-068, E2E-071, E2E-073, E2E-074, E2E-075, E2E-081, E2E-083, E2E-084, E2E-086, E2E-087, E2E-088, E2E-089, E2E-090, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-102, E2E-102a, E2E-102b, E2E-106, E2E-109, E2E-111, E2E-114, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-121, E2E-AGENTS-001 |
| D — Workspace | E2E-012, E2E-013, E2E-022B, E2E-024I, E2E-047, E2E-049, E2E-057, E2E-058, E2E-060, E2E-068, E2E-075, E2E-078 |
| E — Tools & permissions | E2E-008a, E2E-014, E2E-015, E2E-016, E2E-017, E2E-018, E2E-019, E2E-024I, E2E-024K, E2E-040, E2E-049, E2E-074, E2E-093, E2E-097, E2E-099, E2E-100, E2E-101, E2E-102, E2E-103, E2E-105, E2E-106, E2E-107, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-119, E2E-121, E2E-122 |
| F — Persistence | E2E-020, E2E-021, E2E-036, E2E-037, E2E-038, E2E-040, E2E-042, E2E-047, E2E-048, E2E-051, E2E-054, E2E-056, E2E-061, E2E-062, E2E-064, E2E-066, E2E-068, E2E-071, E2E-072, E2E-073, E2E-082, E2E-084, E2E-096, E2E-098, E2E-102, E2E-102b, E2E-103, E2E-AGENTS-001, E2E-061a, E2E-073a, E2E-104, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-118, E2E-119, E2E-120, E2E-121 |
| G — Plugins | E2E-022, E2E-022A, E2E-022B, E2E-022C, E2E-023, E2E-024, E2E-024B, E2E-024C, E2E-024D, E2E-024E, E2E-024F, E2E-024G, E2E-024H, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M, E2E-024N, E2E-024O, E2E-025, E2E-026, E2E-105, E2E-117, E2E-120, E2E-122 |
| H — Diagnostics | E2E-027, E2E-031, E2E-034, E2E-042, E2E-096, E2E-098, E2E-104, E2E-107, E2E-108, E2E-109, E2E-110, E2E-113, E2E-115, E2E-116, E2E-118, E2E-121 |
| Security | E2E-028, E2E-029, E2E-030, E2E-024J, E2E-024K, E2E-024M, E2E-049, E2E-068, E2E-086, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-113, E2E-115, E2E-116, E2E-117, E2E-119, E2E-121, E2E-122 |
| Quality | E2E-032, E2E-033, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-053, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-103, E2E-AGENTS-001, E2E-024N, E2E-024O, E2E-059a, E2E-060b, E2E-060c, E2E-060d, E2E-061a, E2E-073a, E2E-111, E2E-114, E2E-117, E2E-118, E2E-119, E2E-120, E2E-122 |
| Milestone | Scenarios |
|---|---|
| M1 | E2E-001, E2E-002, E2E-003, E2E-028, E2E-029 |
| M2 | E2E-004, E2E-005, E2E-006, E2E-007, E2E-008, E2E-009, E2E-010, E2E-011, E2E-011a, E2E-011b, E2E-020, E2E-021, E2E-027, E2E-031, E2E-036, E2E-037, E2E-042, E2E-087, E2E-088, E2E-089, E2E-090 |
| M3 | E2E-012, E2E-013, E2E-014, E2E-015, E2E-016, E2E-017, E2E-018, E2E-019, E2E-040 |
| M4 | E2E-022, E2E-023, E2E-024, E2E-025, E2E-026, E2E-030, E2E-038 |
| M5 | E2E-008a, E2E-032, E2E-033, E2E-034, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-051, E2E-052, E2E-053, E2E-054, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-AGENTS-001, E2E-059a, E2E-060b, E2E-060c, E2E-061a, E2E-073a, E2E-094, E2E-095 |
| M6 | E2E-104, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-103 |
| M6+ | E2E-121, E2E-122 |
| Post-MVP | E2E-022A, E2E-022B, E2E-022C, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M (plugin roadmap R2/R3/R6) |
The US-UI-* visual scenarios (§UI shell visual scenarios) trace to the Codex parity decisions in decisions-log §D rather than the A–H criteria; their gold source is the capture suite.
9. How AI Must Update This Doc
When adding or changing a feature that affects user-visible or protocol-visible behavior:
- Add a new scenario using the template in §6. Assign the next available ID (
E2E-<N>). - Link it to the relevant acceptance criterion (A–H) and milestone (M1–M6 or M6+ for the current product increment).
- Set status to
Draftunless an automated test already exists. - Update the traceability matrix in §8.
- Commit the update as part of the change (per ai-development-workflow R3).
10. Future Automation Mapping
When E2E automation is implemented (post-M5):
- Each
Draftscenario → Playwright test file. - Scenario ID becomes test case name:
e2e-001-app-launches. - Fixtures and test data paths defined in a
tests/e2e/fixtures/directory. - CI gate: all E2E scenarios must pass before release.
Automation section will be expanded in a future ADR when the tooling decision is finalized.
11. Acceptance Criteria
This test plan spec is accepted when:
- [ ] All MVP acceptance criteria (A–H) have at least one E2E scenario.
- [ ] All security acceptance items have at least one E2E scenario.
- [ ] Every scenario links to at least one spec document.
- [ ] Traceability matrix is complete (scenarios ↔ acceptance ↔ milestones).
- [ ] Scenario template is defined and all entries follow it.
- [ ] AI update rules are documented and cross-linked to workflow spec.
- [ ] Environment requirements match baseline (macOS arm64, clean profile).
UI shell visual scenarios
US-UI-01 Codex-aligned shell chrome
- Open the desktop app on macOS dark theme.
- Expect charcoal main surface (
#181818), left sidebar with current-project and Temporary session groups, and a floating bottom composer with mode/model controls and no workspace rail. - Expect no blue-slate marketing chrome; primary send control is a circular inverted button.
US-UI-02 Empty thread hero
- Open or create a thread with zero messages.
- Expect the centered hero copy "What can I help you build?", a short muted supporting line, with no developer starter cards. The optional project name remains a dotted-underline action when a workspace is open.
US-UI-03 Sidebar destinations
- Expect the expanded home sidebar to show Sessions and Projects without standalone Plugins, Pull requests, or Scheduled rows.
- Click the Plugins icon in the sidebar footer, immediately to the right of Settings, and expect it to replace the main pane with a dedicated page.
- Open Settings → Project archive and use it to open, switch, and close a local folder workspace.
US-UI-04 Composer without workspace context
- With a git workspace open, composer does not show project, Local, or branch labels above the prompt surface.
- Operating-mode selector switches between Agent, Plan, and Goal; both contract modes keep the permission-mode chip and explain their Bash tradeoff.
US-UI-05 Locale chrome
- On a zh-CN system locale, sidebar labels render in Chinese (项目 / 临时会话), without 拉取请求 or 已安排 entries. The footer Plugins icon exposes the localized accessible name 插件.
- Empty-thread hero and supporting line are localized Chinese copy; project name remains a dotted-underline action when a workspace is open.
- Composer omits the 本地 workspace label and shows Agent/Plan/Goal plus the active model ID; both locales expose the Plan and Goal approval copy.
US-UI-06 Session auto-title
- Create a new task and send a first prompt such as "同步代码".
- Expect its project or temporary session row title to become a truncated form of that prompt instead of remaining "New task".
US-UI-08 Shortcut-only destination history
- Navigate Settings → Project archive → a project session → Plugins.
- Expect no back/forward buttons in the expanded sidebar or main titlebar.
- Press
Cmd/Ctrl+[andCmd/Ctrl+]; expect them to traverse that history.
US-UI-09 Grouped session title backfill
- Open an older session that previously showed "New task"/"New chat" but has a first user message.
- Expect its scoped sidebar row to display a truncated first-user-message title after session list load.
US-UI-11 Empty draft reuse
- Click New task twice.
- Expect only one empty "New task" draft in the current project or Temporary group and the home hero remains visible. Empty drafts in another scope are not reused.
US-UI-12 Composer without workspace rail
- On empty home, project home, and in a thread, expect no project / Local / branch context rail above the composer.
- The prompt shell remains one uninterrupted rounded surface with no reserved rail height, attached top lip, rail shadow, bottom seam, or separators.
US-UI-13 Light theme shell parity
- Set theme to system/light on a light macOS appearance.
- Expect sidebar
#f3f3f3, main#ffffff, text#1a1c1f, white floating composer, and home hero with project underline. - Sidebar project/session labels, footer Settings/Plugins/notification icons, current-project identity, thread titles, and composer controls must remain readable dark-on-light (≥4.5:1). Never white/translucent text on the light sidebar.
- The macOS traffic-light row keeps Search and Collapse sidebar readable at the right on light chrome without rendering the Logo/Home brand.
US-UI-14 Semantic chrome tokens
- Toggle theme system → light → dark without restart.
- Shell chrome (sidebar items, composer runtime controls, icon buttons) follows semantic
--ds-text-*/--ds-bg-*tokens in both themes; no hard-coded white (gray-0) text on light surfaces.
US-UI-15 Codex density + elevation
- Sidebar rows use a compact ~28–32px pitch with the 12–14px hierarchy from US-UI-69 and 8px horizontal padding (Codex
radius-token-row10px). - Floating composer uses Codex elevation-prominent: 0.5px stroke + soft 3px/20px shadow (not heavy 10–30px drop).
- Empty hero title is 28px / 34px line-height, weight 400.
- Window restores ≥1000×700 (target 1200×800) if Stage Manager collapses it.
US-UI-16 Sidebar footer utility layout
- On the light/dark home shell, the sidebar footer is a transparent utility band with no separator. Settings, Plugins, and notification actions are grouped on the left, while the build/version chip is right-aligned.
- The notification Bell remains visible in the left action group with its unread badge and opens the inbox above the footer; the main titlebar has no duplicate Bell.
- Clicking the build/version chip checks for updates when current, or opens Settings → Info when an actionable update is available.
- Traffic lights sit at Codex
{x:16,y:16}with a 46px toolbar; the expanded macOS sidebar places Search plus Collapse sidebar at the right in that same row, with no Logo/Home brand or back/forward buttons.
US-UI-17 PI-Desktop home hero logo
- On empty chat home, the 100px
HomeMascotLogorenders above the title using the nine remaining pose groups (50 transparent mascot frames) compiled from the supplieddocs/ipsheets. - Each empty-home mount randomly chooses one group and plays only its visible atlas cells. When the group finishes, another group is randomized without an immediate repeat after a several-second idle rest; single-frame groups rest longer. Hovering the mascot bypasses the idle rest and continuously advances through the pose groups. The sprite keeps its native pixel-art colors and reduced motion holds the current group's first frame.
- Title is 28px / weight 400; active project name uses dotted underline (1px, offset 4px).
- Composer does not render attachment or appshot controls before their payload reaches pi end to end.
US-UI-18 Composer has no inert actions
- On chat home and a docked thread, inspect every composer control.
- Expect no file, photo, or appshot controls while those payloads are unsupported by the pi runtime. Exact reasoning-capable models expose the current Thinking level immediately to the right of Agent / Plan / Goal; unsupported models show no trigger. Unknown compatible models can explicitly enable thinking from the model menu, and changes update the durable session.
- Expect no project, Local, or branch context labels in the composer.
- Every visible composer control changes the active session, opens its menu, or submits/aborts the current turn.
US-UI-19 Permanent Stage Manager bounds restore
- On macOS with Stage Manager, shrink or unfocus the PI window until width < 1040 or height < 700.
- Expect the shell to re-assert a Codex-like footprint (~1200×800, min 1040×700) and keep restoring while still collapsed (not only during the first 20s after launch).
US-UI-20 Dark floating composer box
- Switch to dark theme on chat home.
- Expect main
#181818, sidebar#000000, and the floating composer plate at elevated-primary (#212121f5/ gray-800 96%) with elevation-prominent stroke + soft lift so the box reads against the main surface.
US-UI-21 Top-bar model menu configures pi
- Create a session with provider A/model A, then open the top-bar model menu.
- Expect enabled, runnable provider/default-model pairs and an Agent entry. The model trigger shows only the model ID; a capability-gated Thinking trigger is placed beside Agent / Plan / Goal instead of being nested in the model menu.
- Select provider B/model B, send a prompt, and expect the main-to-sidecar
agent.promptpayload and pi runtime to use B for that session. - Switch away and back; expect B to remain selected. While a turn runs, expect the model control to be disabled.
US-UI-22 Profile footer menu
- On the sidebar footer, click the
Custom/Local profiletrigger. - Expect a 280px opaque elevated menu 8px above the footer. It repeats the local identity in a non-interactive header, then shows a divider and Settings, Logs, and Theme actions in that order.
- Arrow keys wrap through the three actions; Home/End jump to the boundary. Escape closes the menu and restores trigger focus. An outside pointer press closes it without stealing target focus.
- Settings navigates to the settings page, Logs opens local logs, and Theme cycles the current theme after closing the menu.
US-UI-23 Project archive index
- Open Settings → Project archive.
- Expect the Settings title "Project archive" plus three stacked bands (D168): an overview banner with one intent sentence, the primary "Add project", and four counters (projects, open, archived, sessions); a toolbar with a search field, clear affordance, live match count, and a Recent / Name sort segmented control; and a grouped index in the order Pinned, All projects, Archived where each present section shows its label and row count. With no project at all the grouped index is replaced by one empty-state card with its own primary action.
- Expect each row to carry a colored glyph, the project name with its Active / Open / pinned / Archived tags, one meta line with the shortened monospace path, branch, and session count, a relative last-active time, and the hover-revealed New task and row-menu actions. Archived rows stay listed and softened rather than hidden.
- Expand a non-active project and open one of its sessions; expect the app to activate that project before selecting the session, so workspace tools and session scope use the same project.
- Switch the sort to Name and expect rows to reorder inside every section with no row hidden; clear the search and expect the complete index back.
US-UI-24 Settings full-page shell
- Open Settings (footer profile → Settings).
- Expect full-page Codex settings (no app sidebar/nav). Left rail has Back to app, search, and exactly Basics / 全局 AI / Shortcuts / Model configuration / Import / Project archive / Info in that order; content pane shows section title and the destination's settings or archive content.
- Return to the app shell and expect Plugins to remain an independent sidebar-footer destination.
- Drag the empty 46px top band over either the rail or content pane; the native window moves while Back, search, and navigation remain clickable.
US-UI-27 Dark destination pages
- Force dark theme and open Plugins and Settings → Project archive.
- Expect black sidebar, main
#181818, and destination cards/rows readable on elevated dark plates (not flat same-gray).
US-UI-28 Home empty composer association
- On empty chat home (light + dark), expect the hero, optional onboarding checklist, and home composer in one scrollable vertical flow (D111/D204/D206), without a large empty gap or starter-card layer.
- The composer remains a standalone plate without an attached workspace rail.
- Starting a transcript restores the bottom-docked composer with fade veil.
US-UI-29 Light composer plate legibility
- On light theme empty home, the white composer shell uses one uniform solid fill with no internal gradient or background image.
- The shell still reads as an elevated box through a hairline stroke and restrained soft shadow against the
#ffffffmain surface. - Toolbar controls and placeholder remain legible (not pure white-on-white).
US-UI-30 Composer placeholder copy
- Empty composer shows PI-Desktop placeholder copy: EN
Ask PI-Desktop to do anything, zh-CN向 PI-Desktop 下达任意指令. - Placeholder ink is legible on light and dark floating plates.
US-UI-31 Home empty vertical stack (D111/D204/D206)
- Given empty chat home, when the window is ~1200×690, the hero and optional onboarding checklist render in a centered scrollable content stack above a bottom-reserved home composer (not dual-grow absolute portal regions).
- No starter cards or absolute overlay are present; the onboarding checklist remains actionable and the composer is directly available.
US-UI-32 Dark floating box elevation
- Given dark theme empty home, when the composer shell is painted, it uses elevated-primary
#212121on#181818with elevation-prominent stroke+lift identical to light (no heavier custom dark shadow).
US-UI-33 Scoped sidebar session groups
- The home sidebar has no Recents aggregate.
- It shows one independently collapsible header per retained project path with nested sessions and one
Temporary sessions/临时会话header for path-less sessions. - Project and Temporary headers expose compact scope-specific
+controls; project/session overflow menus expose pin/archive actions; nav row pitch remains ~32px and session row pitch ~28–31px.
US-UI-34 Home has no developer starter cards (D206)
- On empty chat home (light + dark), no developer starter grid, card, or contextual quick-action row renders between the hero and composer.
- Task entry starts directly in the bottom composer, while the optional onboarding checklist remains actionable when present.
US-UI-35 Empty composer plate density
- Empty-home composer is compact and content-driven with an empty or one-line draft; it does not reserve the former fixed ~148px empty plate.
US-UI-36 Hero Y + night box elevation
- At ~1200×690 light home, the hero forms one centered block; the home scroller does not clip its top or overlap the bottom composer, and no starter grid is rendered.
- Dark home composer plate reads as elevated-primary
#212121f5with elevation-prominent against#181818(not flat same-surface). - Light composer renders as one uninterrupted solid surface with no context rail or independent top elevation.
- Model chip shows the active model ID; its menu contains only runnable provider/model choices and Agent.
- Placeholder and approval chip remain legible on light and dark plates.
US-UI-39 Home mark + hero title optical
- Empty-home PI-Desktop mark is visible (not near-invisible); stroke density remains readable without a decorative ghost effect.
- Empty-home title with a project uses a readable project label span (short basenames may display as
PI-Desktopfor optical parity).
US-UI-40 Home content width vs rem root
- At 1200×690 light empty home, composer plate outer width is ~744–760px (not ~640px).
- Home suggestion grid spans the same content column as the composer plate.
US-UI-41 Dark hero + night box readability
- Dark empty home hero title ink is light-on-dark (
--ds-text-primary/ near white), not hardcoded#1a1c1f. - Night composer plate is elevated-primary
#212121f5on main#181818with elevation-prominent; light theme is not forced to the night plate fill.
US-UI-42 Light scoped session creation chrome
- On the light sidebar, the Sessions and Projects scoped create controls remain icon-only with semantic hover wash; no standalone New task row is rendered.
US-UI-43 Empty home plate Y + night elevated-primary
- Open empty home at ~1200×690 light theme.
- Composer plate is bottom-aligned and content-driven: an empty or one-line draft uses the compact shell rather than a fixed ~140px minimum; the surface remains uniformly solid with no decorative wash.
- Switch dark theme: night plate is elevated-primary (
#212121f5/ gray-800 96%) with the same restrained elevation and no internal gradient.
US-UI-44 Settings compact directory + merged sections
- Open Settings light theme at ~1200×690.
- Full-page shell: rail ~260px on
#f3f3f3, main#fff; Back to app; search pill; Basics active pill with icon. - Rail order is exactly Basics, 全局 AI/AI, Shortcuts, Instructions, Model configuration, Import, Project archive, and Info; there are no Personal/Integrations/Coding group headings, plugin duplicate, or placeholder destinations.
- Basics content: large title and an Appearance card with working system/light/dark controls and a Defaults card. 全局 AI holds Permissions and Context management; Shortcuts holds the Keyboard shortcuts card. File-open target, language override, menu-bar behavior, and bottom-panel behavior are absent until host-backed implementations exist.
- Model configuration contains the provider studio hero, default mode/model, Enter-to-send switch, and card-based Providers management with an add-provider dialog.
- Plugin load/enable/disable/uninstall remains available from the app shell's independent Extensions destination.
- Dark: rail
#000, main#181818, cards elevated#212121.
US-UI-38 Composer workspace context omitted
- On empty home, project home, and after starting a transcript, the composer never renders a project / Local / branch capsule.
- Workspace identity remains visible through the home hero or sidebar rather than being duplicated above the prompt.
US-UI-37 Empty draft row + resize
- Empty composer prompt rows show no leading brand icon and retain visible placeholder ink (not a blank white/night hole).
- Auto-resize never collapses empty textarea below ~28px.
- Disabled send control is a solid gray chip on light (
#8e8e90), full opacity with white arrow. - Dark night plate remains elevated-primary
#212121f5with readable elevation-prominent on#181818.
US-UI-31b (superseded)
- Superseded by US-UI-31 home empty vertical stack (D111).
US-UI-46 Home-with-project composer chrome
- Open a project on empty home (no transcript).
- Expect no workspace controls attached to the plate; there is no legacy draft mark, and the placeholder uses the PI-Desktop copy.
- Model chip shows the active model ID; the footer uses the circular local-user glyph, two-line Custom / Local profile identity, disclosure chevron, and separate Help → Settings Info control.
US-UI-47 Projects index parity
- Open Settings → Project archive.
- Expect the Settings section title, search pill, Add project button, and the complete durable project list including archived rows.
- Rows expand for recent tasks; activating a project or one of its sessions uses
setProjectwithout re-picking via dialog and keeps session/workspace context synchronized. Sidebar pin/archive/close metadata remains local to the renderer and never hides or deletes a durable Project-archive row.
US-UI-48 Home starter glyphs and labels are absent (D206)
- On empty home, no developer starter icon plate, title/description, or starter-card glyph renders in light or dark themes.
- The hero, optional onboarding checklist, and bottom composer remain the only empty-home task-entry surfaces.
US-UI-49 Scoped sidebar row chrome
- Hover or select a project or temporary session row.
- Expect restrained title rows with active/hover background and compact overflow actions for pin/archive (not a Recents aggregate).
- Multiple retained project groups may be visible at once; sessions remain under exact-path groups, while closed-project sessions remain available in Settings → Project archive.
US-UI-50 Destination title scale
- Open Settings → Project archive and Plugins.
- Expect large section titles (~28px) consistent with Codex destination/index pages.
- Dark home scoped session-creation controls remain quiet icon actions without a standalone New task row.
US-UI-52 Settings gold chrome metrics (D070)
- Open Settings light Basics at ~1200×690.
- Expect ~275px
#f4f4f4rail, single active Basics pill, Back + search. - Expect the working theme selector without inert toggle or open-target rows.
- Expect Permissions + Basics + Appearance elevated cards; Agent, Import, and Info remain the only other destinations.
- Resize between 1040px, 1200px, and 1600px widths; the content cards fill the available right pane at each size without changing the rail or introducing horizontal scrolling.
US-UI-53 Settings dark shell (D070)
- Dark theme Settings Basics: black rail, elevated cards, blue on-toggles, Back returns to chat.
- Row descriptions use theme-aware secondary text and remain clearly readable on the
#212121card surface; they must not fall back to low-contrast muted ink.
US-UI-54 Toast variants + lifecycle (D085)
- Trigger a success (save provider), an error (run with an invalid key), and an info toast from a test plugin.
- Expect a top-center stack on an elevated plate with a tinted variant icon (green ✓ / red ! / info) and an X dismiss per card; newest enters at the top-center anchor and pushes older cards down.
- Success/info auto-dismiss ~4s, error lingers ~8s; hovering a card pauses its countdown; X removes it immediately.
- With the stack overlapping the frameless titlebar band, hover still pauses the countdown and every X remains clickable instead of dragging the window.
- Repeating the same action restarts the existing toast instead of stacking a duplicate; stack never exceeds 4.
- Capture rig scenes
pi-toasts-light/pi-toasts-darkshow the stack in both themes.
US-UI-55 Composer textarea growth (D089)
- In both home and thread-docked composers, an empty or single-line draft displays one visible text line.
- Enter or paste two through seven visual lines; the textarea grows with the wrapped content without manual resizing.
- Add an eighth visual line; the textarea stays at seven visible lines and scrolls internally instead of growing the composer further.
- Delete back to one line or submit the draft; the textarea contracts to its one-line default.
US-UI-56 Codex transcript tool activity
- In light and dark themes, tool calls use transparent compact activity rows, not elevated cards or colored success rails.
- Consecutive calls appear inside one default-collapsed processing group. Its active header shows
Processing · {elapsed}and its completed header showsProcessed for {elapsed}, plus a localized step count. - The row shows a semantic 15–16px icon, progressive/past-tense action, ellipsized monospace argument hint, quiet disclosure chevron, and localized running/error/denied state.
- Fork-family tools show the GitFork branch icon instead of the generic tool glyph.
- Expanding a completed call reveals output before input. Both sections are independently copyable and capped with internal scrolling.
- Reloading the session preserves the action label and argument hint instead of degrading the row to a generic
Tool. - Run a turn that emits assistant text, calls multiple tools, and resumes with more assistant text. During streaming and after session reload, expect one assistant article for the whole user turn, with fragments and activity in original order but only one trailing model/usage row and one Copy/Fork/Retry toolbar. Copy includes all assistant text fragments in order.
US-UI-57 Multi-project sidebar groups
- Open projects A and B without closing either.
- Expect a
Sessionsheading aboveProjects, containing path-less conversations plus new-session and sort actions. With more than five standalone conversations, expect a five-row-high list that scrolls to every remaining row without growing further. - Right-click the
Sessionsheading or empty standalone list chrome and expect a one-item create menu that creates/reuses a path-less temporary session. - Expect the following
Projectsheading to retain its new-project folder action, one path-keyed group per retained project, and an active-state marker on exactly one group. Its list consumes the remaining height and scrolls independently. Adjacent project groups read as a compact continuous tree without detached card spacing. - Right-click the
Projectsheading or empty project-list chrome and expect a one-item create menu that opens the same project picker as the folder-plus action. - Expect project and session lists to scroll inside the sidebar body without clipping behind the footer; sidebar Search/Collapse remain in the sidebar header. When the work panel is open, expect its sole collapse control in the session pane top-right rather than the work-panel content header, flush against the divider at the main pane's right edge.
- Collapse A by clicking its directory label, expand it from the chevron area, then activate B and return to A. Only A's child rows collapse; project
+and overflow actions do not toggle it; the active project, topbar path, and transcript switch together; the composer remains free of workspace identity chrome. - Close B and reopen it from Settings → Project archive. Closing removes only the sidebar tab; durable project/session rows remain available.
US-UI-58 Sidebar organization actions
- Open a project and conversation overflow menu.
- Expect localized Pin/Unpin, Archive/Restore, and (for conversations) Delete actions with keyboard-reachable menu semantics.
- Open the sort menu from the standalone
Sessionsheading, pin one project/session, and choose each user-facing sort mode (Recently updated, Created date, Oldest first, Name). Pinned rows remain first. - Archive a row, verify it is absent by default, enable Show archived, and restore it. The transcript and project binding remain unchanged.
- A legacy
manualpreference loads without presenting a drag-reorder affordance.
US-UI-59 Session-rooted background tools
- Start a visible turn in project A, switch to project B while it runs, and inspect both sidebar status indicators.
- Expect A's turn to continue in the background, B's composer/context to show only B, and tool output/artifacts from A to remain rooted in A without opening or activating a work-panel tab over B.
- Open a Temporary session and invoke a workspace-required tool; expect the normal
WORKSPACE_REQUIREDresult rather than inheritance from B.
US-UI-60 WorkBuddy transcript plates (D101)
- Open a mixed transcript in light and dark themes.
- Expect right-aligned compact user plates, transparent full-width assistant prose, denser row spacing, and hover-only copy chips under each turn.
- While an assistant answer streams, expect a thin accent left rule rather than a boxed frame.
US-UI-60b Assistant markdown prose redesign
- Open an assistant answer with headings, table, code fence, blockquote, and task list.
- Expect the refined
.prose-chathierarchy and inset code chrome in both themes. - Expand thinking markdown and confirm it stays visually subordinate to the answer.
US-UI-61 Assistant context inspector + retry (D103, D184)
- Complete an assistant turn that reports usage.
- Expect a model badge and compact Context inspector under the answer. The trigger shows the remaining context percentage; hover or keyboard focus shows used/remaining/window tokens, exact input/output/cache/reasoning usage, generation tokens/s, and each unique tool type's aggregated call count, argument/result footprint, share, and duration. The panel labels provider totals as reported and tool rows as estimates.
- Move the trigger near each viewport edge and scroll or resize while the panel is open; expect the body-level overlay to flip, clamp, and remain fully visible instead of being clipped by the transcript scroll container.
- Hover the action row and click Retry; the nearest preceding user prompt is re-sent.
US-UI-62 In-place regenerate (D105)
- On a multi-turn transcript, regenerate an earlier assistant answer.
- Expect the later turns to disappear and the chosen user prompt to re-run in place, without stacking a second copy of the prompt.
US-UI-63 Regenerate history pager (D109)
- Regenerate an assistant answer twice.
- After each retry, hover or focus the root user bubble and expect its action toolbar to expose a
1/Npager for restoring earlier variants.
US-UI-64 Empty home no composer overlap (D111/D204/D206)
- Open empty home at ~1200×690 and at a shorter height (~900×640).
- Expect the hero and optional onboarding checklist in a scrollable content region, with the home composer visibly reserved at the bottom and no starter cards.
- Short windows scroll the content region rather than stacking the composer over the checklist; when the checklist is absent, no empty spacer remains.
US-UI-65 Durable notification inbox (D117/D130)
- Verify a focused-current completion leaves the inbox unchanged, then populate it through background/unfocused completed and failed task rows, including one long session title. Inspect the expanded sidebar footer and popover in light/dark themes at default and narrow supported widths.
- Expect no titlebar bell, a stable 32px footer bell in the former Help position, a non-overlapping
1–99/99+badge, dense 360px-or-narrower list, localized kind/session/time/error content, and distinct text/icon/unread-dot semantics without nested cards or clipped text. - Switch All/Unread; use Tab, arrow keys, Home/End, Enter/Space, Escape, and outside click. Focus order remains predictable, row activation opens the correct session, and Escape restores focus to the bell.
- Mark all read and Clear expose icon tooltips/accessible names, disabled and empty states remain understandable, and reduced-motion mode changes the popover instantly without suppressing focus or unread state.
US-UI-66 Application update notice layout
- In a conversation with the docked composer visible, exercise manual
available, in-appdownloading, anddownloadedupdate fixtures in light and dark themes at default and minimum supported window sizes. Grow the composer draft to its maximum visible height. - Expect one compact update notice below the titlebar in the main pane's top-right safe area. It never intersects the composer, including while the draft grows, and it does not cover an open work panel.
- Expect a stable update icon/title/message hierarchy, determinate progress for
downloading, the applicable View release or Restart to update action, and a 24px dismiss control with an accessible name. Dismissing one status stage does not suppress a later stage for the same version. - When the fixture includes
releaseNotes, expect a "What's new" section under the status message on both the banner and Settings → Info Updates row, using the product UI locale (EN or zh-CN). A fixture without notes omits the section. Switching language re-resolves the same version's notes without a new check.
US-UI-67 Distinct sidebar task status indicators (D135)
- In light and dark themes, keep session B selected while session A progresses through in-progress, completed, a new in-progress turn, failed, and aborted states. Repeat with reduced motion enabled and inspect keyboard focus.
- Expect A to show an orange breathing dot while in progress, a green check on completion, and a red circled alert on failure. Starting a new turn clears A's earlier terminal mark; abort leaves no completed or failed mark.
- Expect selected idle B to show a static accent-blue outlined ring and active row background. If selected B starts work, its orange in-progress dot takes precedence until the turn settles; its latest terminal result remains hidden behind the selected ring while selected.
- Every indicator exposes localized In progress / Selected / Completed / Failed text through its accessible name and tooltip. Reduced motion makes the orange dot static without changing its color or meaning. Row height, title truncation, pin icon, hover actions, and focus ring remain stable in both themes.
- Open a conversation with a completed or failed mark and expect that terminal mark to clear immediately while its durable task notification becomes read. Refresh notifications and restart the app; the acknowledged mark must not return. A terminal notification marked read from the inbox likewise produces no sidebar terminal mark.
US-UI-68 Session-scoped inline permissions and artifacts (D138/D142)
- Run two sessions concurrently and keep A visible while B reaches a tool approval request. Inspect light/dark themes at default and narrow widths.
- Expect no backdrop, modal, page/session switch, work-panel hide, transcript replacement, or composer-focus change in A. B retains its pending state.
- Open B explicitly and expect one inline permission card after B's latest activity, with readable risk, args, workspace, countdown, and wrapping action controls. Switching away and back preserves the absolute deadline.
- Make A and B pending together, resolve each independently, and confirm neither action removes or changes the other card.
- Resolve A's Write/Edit permission and switch to B before completion. Expect no transient Review panel in B and no panel/window flash; returning to A restores A's resulting Review tab and prior panel selection, while B's tabs and Browser resource remain unchanged.
US-UI-69 Sidebar type balance (D144/D161)
- Open the expanded sidebar in light and dark themes at default and minimum supported widths with at least one session, one project group, and the local profile footer visible.
- Expect Plugins, footer profile name, and profile menu actions to render at the body chrome size (
--text-base/ 14px). - Expect session/thread titles, project/group titles, and empty-state copy at
--text-md/ 13px, with uppercase section labels (SESSIONS/PROJECTS) at--text-sm/ 12px — never below--text-mdfor primary list content. - Confirm row pitch remains compact (≈28–32px), titles still truncate cleanly, and collapsed icon-rail controls stay legible without reflowing the shell.
US-UI-70 Disable text correction on editable fields (D145)
- Open empty home, a docked transcript, Settings search, Plugins market search, Projects archive search, global search (which now includes commands), provider model combo, message-edit textarea, and the work-panel browser URL bar in light and dark.
- Expect every text
input/textareato exposespellcheck="false"(ReactspellCheck={false}) plusautocorrect="off"andautocapitalize="off". - Expect no red spelling underlines while typing code-like tokens, paths, model ids, or URLs; checkboxes and non-text controls remain unchanged.
US-UI-71 Composer runtime chip descenders (D150)
- Open empty home and a docked thread with a model ID that contains descenders (for example
gpt,gemini, or any id withg/y/p/q/j). - Inspect Agent/Plan/Goal, Thinking (when present), permission mode, and the model chip in light and dark.
- Expect every chip label to show full glyph ink — bottoms of
g/y/pare not clipped by the 28px capsule — while long model IDs still ellipsize horizontally. - Specs linked:
04-ux/07-ui-design-system.md§8.2,04-ux/08-component-spec.md§11.5, decisions-log D150 - Milestone: M5
- Status: Partially automated (renderer source test: chip line-height + no leading-none)
US-UI-72 Apple-inspired global corner hierarchy (D210)
- Open the empty home, a populated transcript, Settings, Plugins, Project archive, a menu, and a dialog at the default supported desktop size in light and dark themes.
- Expect fixed corners to follow the global 4/6/8/10/12/14/16/18/20/24px ladder, with visually larger or more elevated surfaces receiving the larger radii.
- Standard compact and medium buttons and fields remain rounded rectangles, not capsules. Pills, segmented selections, status labels, progress tracks, switches, equal-width circular icon controls, and dots retain their explicit capsule or circle shape.
- Where a rounded child sits against a rounded parent corner, expect the radii to read concentrically with the intervening inset. Full-width sidebar, titlebar, and work-panel edges remain square rather than becoming floating cards.
- Resize to the minimum supported window and inspect menus/dialogs near each edge. Rounded surfaces must not clip text, focus rings, actions, or scrollable content.
- Specs linked:
04-ux/07-ui-design-system.md§6.2, ADR 0071 - Milestone: M5
- Status: Partially automated (radius token and shared-control source test)
US-UI-73 Composer mode selector has stable width
- Open the empty home and a docked thread in both English and zh-CN.
- Switch the Composer mode chip through Agent/Plan/Goal several times.
- Expect the mode chip to keep one fixed width sized for the longest built-in label (English "Agent" / zh-CN "智能体"); the adjacent Thinking and permission controls, send button, and composer shell do not move or resize.
- In Goal, expect the permission chip to remain visible with the same geometry, show Full auto / 全自动, and stay disabled without opening a permission menu. The Plan/Goal approval card remains the separate execution-policy control.
- Specs linked:
04-ux/08-component-spec.md§11.3 - Milestone: M5
- Status: Partially automated (renderer style/source contract)
E2E-123: asktool collects multiple answers and returns skipped placeholders
- Preconditions: Agent, Plan, or Goal mode; a configured provider; a session with an active transcript.
- Steps: 1) Ask the agent to call
asktoolwith a single-select question, a multi-select question, and an option list for each. 2) Confirm each card shows the fixed custom-input choice. 3) Answer the first question, click Next, and select two answers on the multi-select question. 4) Skip the final question without entering text. 5) Inspect the completed tool row and the next model response. - Expected: One question is visible at a time; the small indicators show answered, current, and skipped states in the composer approval area, at the same dock position used by Plan and Goal approval. The request has no countdown. The tool output is ordered as
question:answer, uses、between multiple answers and\n---\nbetween questions, and keepsquestion:for the skipped question. Decline all produces empty placeholders for every question and still completes the tool call. - Specs linked:
03-runtime/17-asktool-questions.md,04-ux/11-asktool-question-card.md, ADR 0077 - Acceptance: E (interactive tool output), C (inline card)
- Milestone: M5
- Status: Draft (unit coverage active; desktop journey pending)
E2E-124: Minimize hides the window in the cross-platform tray
- Preconditions: Built desktop app on macOS, Windows, and Linux; English and zh-CN locales are available; a normal main window is open.
- Steps: 1) Minimize from the platform's window control (macOS traffic light or Windows/Linux renderer control). 2) Confirm the main window is hidden while the app process remains resident. 3) Click or double-click the tray icon and confirm the same window is restored and focused. 4) Open the tray menu and choose Show, then repeat with Quit. 5) Repeat in zh-CN and invoke macOS app activation while the window is hidden.
- Expected: All minimize paths hide to one tray icon instead of quitting or leaving a taskbar-minimized window. Show/click/double-click/app activation restores the existing window; the localized menu contains Show PI-Desktop and Quit PI-Desktop. Quit runs the normal shutdown sequence and leaves no orphan host, sidecar, or tray process. Closing the window remains an explicit quit action.
- Specs linked:
03-runtime/07-process-model.md,04-ux/09-interaction-patterns.md,08-meta/decisions-log.md(D216), ADR 0078 - Acceptance: A (app lifecycle), Quality
- Milestone: M6+
- Status: Unit/source-contract covered; native cross-platform tray journey Draft (do not run local E2E unless explicitly requested)
E2E-125: Complete bilingual VitePress documentation remains usable
- Preconditions: Docs dependencies are installed and the VitePress preview server is running from the repository.
- Steps: 1) Open
/at 1440×900 and verify the English landing page, system map, read-by-intent journeys, reference shelf, global search, Guide/Specs/ADRs navigation, and language selector. 2) Switch to/zh-CN/and verify the translated hero, mirrored topic map, and Chinese specification links. 3) Open/spec/03-runtime/01-ipc-protocol, switch languages, and verify that/zh-CN/spec/03-runtime/01-ipc-protocolcontains translated prose, preserved code identifiers, and a link back to the English source. 4) Search in each locale and open a matching result. 5) Repeat the homepage and a long table-heavy specification at 390×844 in light and dark mode. - Expected: Both locale entry points and every English/Chinese specification pair render without broken links or page-level horizontal overflow. Landing and reading columns are visually centered within their available layout; the mobile hero presents text before the system visual. Search returns local results. The mobile navigation opens and closes without shifting or obscuring the page. Code blocks and tables remain readable through contained scrolling, theme contrast stays clear, and every Chinese spec identifies the English page as its canonical source. Directly refresh
/zh-CN/spec/README,/zh-CN/spec/03-runtime/01-ipc-protocol,/zh-CN/adr/,/spec/README, and/adr/READMEon Vercel; each route resolves through the documentedcleanUrlsconfiguration instead of returning 404. - Specs linked:
02-architecture/04-documentation-site.md, ADR 0079 - Acceptance: Quality, documentation discoverability, responsive layout
- Milestone: M6+
- Status: Browser-rendered desktop/mobile verification is authorized for this documentation redesign; remote deployment refresh checks remain Draft.