V0.1.3/m2/webview UI#7
Open
Vedansi18 wants to merge 51 commits into
Open
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt sets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ofile Adds selectAbsenceMap helper (hardcore_pro → formal, else → casual) and updates resolveDecisionContent to use it for non-vibe profiles. Updates 6 no-profile tests and 2 priority-override tests to assert casual variants, consistent with selectNonBeginnerVariant's undefined → casual behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 3 hardcore_pro routing tests for remaining formal absence variants, add all 8 non-beginner absence sets to allContent and per-set count tests, add 4 beginner absence sets to C-02 structural validation block with missing imports for ABSENCE_REGRESSION_CHECK_BEGINNER and ABSENCE_SPEC_ACCEPTANCE_BEGINNER. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes to buildMjsScript() in TtySelectFn.ts: - Fix _lineCount to split labels by \n and compute per-line visual rows instead of treating the whole label as one long string; this correctly estimates height for multi-line (numbered-steps) option labels - Add wrapping guard: when all options fit within budget (no-overflow) but their total visual lines exceed the option count, recompute _maxItems using the option count as a tight budget ceiling so visually dense option sets always get a viewport rather than a flat list - Pass maxItems to select() so @clack/prompts k() uses _maxItems as the viewport window instead of rows-4; without this, maxItems computation had no effect on the actual rendered UI Applies to Mac, Windows, and Linux new-window paths (all share buildMjsScript). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…word lists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ded vibeKeyword sets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds formal, casual, and beginner DecisionContent objects for idea_scoping, idea_constraint_check, and idea_user_definition. Includes content existence tests covering shape and non-empty strings. Map wiring deferred to Phase 4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds formal, casual, and beginner DecisionContent objects for task_ordering, task_sizing, and task_definition_of_done. Includes content existence tests covering shape and non-empty strings. Map wiring deferred to Phase 4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds formal, casual, and beginner DecisionContent objects for user_feedback_review and iteration_planning. Wires all 8 new signal keys (Groups A, B, C) into ABSENCE_CONTENT, ABSENCE_CONTENT_CASUAL, and ABSENCE_CONTENT_BEGINNER. Adds 24 routing tests covering all 3 registers for all 8 signals, plus Group C content existence tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…INNER Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cture test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This reverts commit 2793678.
… map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…NG_CASUAL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 8 new absence signal definitions in signals.ts (scope_creep, context_loss, api_design_review, accessibility, environment_and_secrets, data_validation, ci_pipeline, rate_limiting) - 24 new content set constants across 3 registers (formal, casual, beginner) in options.ts and options-beginner.ts - All 8 keys added to ABSENCE_CONTENT, ABSENCE_CONTENT_CASUAL, ABSENCE_CONTENT_BEGINNER maps - relevantProjectTypes filter on api_design_review, accessibility, rate_limiting signals - AbsenceDetector.ts: Gate 3 now uses per-signal absenceThreshold with profile multiplier; project-type gate added - types.ts: relevantProjectTypes field added to SignalDefinition - auto.ts: projectType extracted from getProject() and passed to detectAbsenceFlags - Tests: routing, content existence, structure, and buildOptionList coverage for all 8 new signals Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Branch 1 of Milestone M2 (v0.1.3/m2/extension-skeleton). Establishes the
src/ext-vscode/ sub-package with an esbuild-driven build pipeline
(ESM source -> CJS bundle for the VS Code host), activates on
onStartupFinished, and ships the four scoped modules:
M1 - Skeleton: package.json (activationEvents, activity-bar container +
placeholder view backed by viewsWelcome so the icon actually renders),
tsconfig.json, esbuild.config.mjs, src/extension.ts entrypoint.
M5 - IPC stub: src/ipc.ts. spawnAuto(prompt, sessionId) and
spawnStop(sessionId) spawn the nexpath CLI as subprocesses and parse
the decision-session JSON payload from stdout, with typed errors
(NexpathBinaryNotFoundError, NexpathMalformedPayloadError) and
configurable binary-path resolution
(opts.binaryPath -> NEXPATH_BIN env -> 'nexpath' on PATH).
The exact stdin envelope vs. Layer C input contract is intentionally
a stub here; Branch 4 (cursor-windsurf-adapters) finalises it.
M11 - Onboarding: src/onboarding.ts. First-launch consent toast persists
the user's choice to globalState; on macOS, additionally shows a
Full-Disk-Access guidance toast that deep-links to the System
Settings privacy pane.
M12 - Icon: media/icon.svg. Y-fork (branching path) representing
"next path" decision points; monochrome currentColor, scalable.
25 unit tests co-located alongside source (8 onboarding, 11 ipc, 6 extension),
runnable via root vitest with vi.mock('vscode') stubs. Sub-package has its
own tsconfig + package-lock; root tsconfig now excludes src/ext-vscode/ so
each side owns its TS build. Both root and sub-package tsc --noEmit are
clean. Full root test suite: 1851 passing + 18 pre-existing unrelated
TtySelectFn Windows-sim failures (carried forward from dev plan §3.0).
Deferred (flagged for follow-up, not blockers for this branch):
- 5 moderate npm-audit warnings in the esbuild -> vite -> vitest dev chain
(dev-only; will be addressed during M5 hardening).
- IPC stdin envelope contract: real wiring lands in Branch 4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Branch 3 of Milestone M2 (v0.1.3/m2/webview-ui). Stacked on M2 Branch 1 (commit 879ed5e) — does NOT depend on M2 B2's watcher, only on B1's skeleton + the DecisionSessionPayload type from ipc.ts. Delivers the three scoped modules: M6 — WebviewViewProvider: src/webview/view-provider.ts. NexpathDecisionSessionViewProvider implements vscode.WebviewViewProvider for the nexpath.status activity-bar view. resolveWebviewView wires webview.options (enableScripts + localResourceRoots), sets initial HTML, registers onDidReceiveMessage + onDidDispose. publishPayload stores the payload, updates the HTML, and calls webviewView.show(true) for the auto-reveal UX matching architecture rev 2 §4. Payload survives view dispose/re-show. Injectable onSelect dependency for tests. Exposes getCurrentPayload() + handleMessage() for direct message-routing tests. M7 — HTML template: src/webview/html.ts. renderDecisionSessionHtml(payload, opts) — pure function, no I/O. Returns the full self-contained HTML for the webview. Two modes: empty/watching state (no scripts, just "Nexpath is active…") and populated state (advisory + numbered option buttons + dismiss). CSP: default-src 'none' with nonce-scoped scripts. All user-controlled strings HTML-escaped. Theming via --vscode-* CSS variables so the UI inherits Cursor/Windsurf's theme. Tests verify both states, nonce handling, HTML escaping (incl. <script> + onerror= injection attempts), and empty-options array. M8 — Prompt injection: src/webview/prompt-injection.ts. handleOptionSelection writes the selected option text to the system clipboard via vscode.env.clipboard.writeText + shows a non-modal info toast directing the user to paste. This is the ONLY reliable path — VS Code text-editing APIs target editor documents, not the host's (Cursor's) chat input panel (dev plan §2.4). Branch 4 may discover a Cursor-specific command that lets us write directly; until then clipboard + toast is the primary path. Injectable deps for tests. extension.ts updates: - Registers the view provider on activate via vscode.window.registerWebviewViewProvider(VIEW_ID, instance). - Pushes the registration disposable onto context.subscriptions for cleanup on deactivate. - Holds the provider at module scope; exposes via getViewProvider() so Branch 4's adapter wiring can publish payloads. - View provider registration runs BEFORE onboarding so the icon shows immediately on activation, even while consent toasts are open. - Onboarding errors still swallowed (per existing B1 behaviour). package.json updates: - nexpath.status view now declares "type": "webview" (was implicit tree). - viewsWelcome entry removed — webview-type views render their own empty state from inside the webview HTML, not via viewsWelcome. The empty state in renderDecisionSessionHtml replaces it. 38 new unit tests: - html.test.ts: 13 (escapeHtml + empty state + populated state + nonce + HTML escaping in advisory/options + empty options array) - view-provider.test.ts: 14 (VIEW_ID + resolveWebviewView × 4 + publishPayload × 3 + clearPayload + handleMessage × 5) - prompt-injection.test.ts: 6 (clipboard write + toast + error paths + DI + empty string) - extension.test.ts: +5 (registration test + subscriptions push + getViewProvider + onboarding-rejects-but-view-still-registered + the deactivate clears viewProvider) Sub-package totals at branch HEAD: 63 tests across 6 files (was 25 in B1, +38 here). Root tsc + sub-package tsc clean. Full root test suite 1889 passing + 18 pre-existing TtySelectFn carry-forward (unrelated). Esbuild bundle grew from 3.4 KB → 11.0 KB (includes the new webview modules + their CSS template strings). Deferred (flagged, not blockers for this branch): - Pre-prompt blocking on Cursor/Windsurf — current architecture only shows guidance after the host sends the prompt. Pre-send blocking would need a keybinding hijack (architecture doc §11 open question 7). - Cursor-specific "write to chat input" command — discover in Branch 4 if it exists, otherwise clipboard + toast remains the only path. - E2E test against a real Cursor instance — Branch 5 (smoke-test) gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two refinements after cross-confirmation review against the dev plan + a read of the Layer C TTY UI in src/decision-session/TtySelectFn.ts. ## 1. injectFn contract — addresses Drift hi0001234d#3 (primary text-editing path) prompt-injection.ts now defines: - `OptionInjector = (text: string) => Promise<boolean>` — the contract for a direct-injection function (agent-specific, lives in B4). - `PromptInjectionDeps.injectFn?` — optional adapter-supplied injector. B3 default is absent → clipboard fallback always wins. handleOptionSelection now has two paths: 1. If `deps.injectFn` is provided AND `injectFn(text)` resolves true: skip clipboard. Text is in the chat input. Done. 2. Otherwise (injectFn absent, returned false, or threw): fall back to clipboard + info toast. B4 (cursor-windsurf-adapters / M9 + M10) will: - Discover Cursor / Windsurf command ids that write text to the AI chat input (via `vscode.commands.getCommands(true)`). - Implement `cursorChatInputInject` / `windsurfChatInputInject` of type OptionInjector. - Pass them through the view-provider constructor's onSelect arg as: const onSelect = (text) => handleOptionSelection(text, { injectFn: cursorChatInputInject }); Decision saved to memory at ~/.claude/projects/-home-emptyops-Documents-Vedanshi-NexPathMain-reviewduel/memory/project_b4_prompt_injection_contract.md — marked load-bearing (do not delete or rename the named symbols). This guarantees the deferred work doesn't get forgotten in a future session. 4 new unit tests in prompt-injection.test.ts: - injectFn returning true → clipboard NOT touched - injectFn returning false → falls back to clipboard - injectFn throwing → falls back to clipboard - injectFn absent → clipboard path (default B3 behaviour) ## 2. Keyboard shortcuts — addresses Drift hi0001234d#2 (Layer C UX consistency) After reading TtySelectFn.ts, the relevant UX patterns to mirror: - Ctrl+X = opt-out / dismiss (matches Layer C's `\\x18` keypress handler at TtySelectFn.ts:128 + the install disclosure copy: "press Ctrl+X during an advisory") - Esc = standard web cancel (TTY doesn't have this but it's expected web UX) Added to the webview HTML script: - keydown handler for Ctrl+X → dispatches `{type: 'dismiss'}` - keydown handler for Esc → dispatches `{type: 'dismiss'}` - keydown handler for digits 1-9 → dispatches `{type: 'select'}` against the Nth option (matches the visible numbering) - First option focused on render so keyboard users land on something actionable. - Visible kbd-hint text in the options header and on the dismiss button so the shortcuts are discoverable. Patterns NOT mirrored (intentional, rationale): - TTY's two-step "Send to Claude now" / "Copy to clipboard" sub-menu: redundant in the webview — until B4's injectFn lands, every path ends in clipboard anyway. The two-step adds friction without value. - 60s auto-dismiss timeout: the webview is non-modal; the user can let it sit indefinitely. Adds complexity without UX gain. - Arrow-key navigation (Tab already works natively in HTML; number keys are the faster path for our short option lists). 5 new unit tests in html.test.ts: - keyboard hint string visible in options header - hint range scoped to option count (capped at 9) - keydown handler dispatches select on digits 1-9 - Esc + Ctrl+X handlers dispatch dismiss - first option button focused on render ## Verification - Sub-package tsc --noEmit clean - Sub-package vitest: 72/72 pass (was 63, +9 new) - Root tsc --noEmit clean - Full root test suite: 1898 passing + 18 pre-existing TtySelectFn carry-forward - Esbuild bundle: 11.0 KB → 12.3 KB (the new keyboard handler script + injectFn branch) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-confirmation audit caught one real resilience gap + two missing
unit-test coverage points. All scoped to M2/B3 work.
## Resilience fix in view-provider.ts
NexpathDecisionSessionViewProvider.handleMessage previously did:
await this.onSelect(msg.optionLabel);
If onSelect rejected (which a real B4 injectFn can — e.g. when a Cursor
command is missing or throws), the rejection propagated up. The caller
chain is `webview.onDidReceiveMessage` → `void this.handleMessage(raw)`
in resolveWebviewView, which has no `await` to catch it — so it would
have surfaced as an unhandled promise rejection in the extension host.
Fixed by wrapping the onSelect call in try/catch + console.error. The
user-facing error path stays in handleOptionSelection (which already
shows a toast on clipboard failure); the catch here is a last-resort
guard so the extension host doesn't see unhandled rejections.
## 3 new unit tests covering previously-untested behaviour
view-provider.test.ts (+2):
- "a second publishPayload replaces the first (no stacking)" — verifies
the latest payload wins, both currentPayload and webview.html
reflect it, view.show is called per publish.
- "catches errors from onSelect so they never become unhandled
rejections" — proves the new try/catch works + the error is logged
to console.error with the right prefix.
html.test.ts (+1):
- "escapes attribute-breaking quote characters in option id and label"
— the existing escape test covered `<` `>` `&`. Quotes (`"`) inside
a data-option-id="..." attribute would close the attribute and
allow injection. Verifies escapeHtml correctly converts `"` to
`"` in both data-option-id and data-option-label.
## Verification
- Sub-package tsc --noEmit clean
- Sub-package vitest: 75/75 pass (was 72; +3)
- Root tsc --noEmit clean
- Full root test suite: 1901 passing + 18 pre-existing TtySelectFn
- Esbuild bundle: still builds clean (~12.3 KB)
Closes the M2/B3 unit-test audit gap. Per auto-commit rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the decision-session UI surface of the VS Code extension — modules
M6 (WebviewViewProvider), M7 (HTML template), and M8 (prompt
injection) per dev plan §3 M2 §2.2. The webview renders advisory +
options inside Cursor / Windsurf's activity-bar panel, auto-reveals when
a
DecisionSessionPayloadarrives, and round-trips the user's selectionback to the host's chat input (via clipboard + toast for now; B4 will
plug in a direct-injection primary path via the
injectFncontract thisbranch sets up). No Layer C file is touched.
Stacked on M2 Branch 1 (
v0.1.3/m2/extension-skeleton, commit879ed5e) per the dependency table in dev plan §3.0. Does NOT depend onM2/B2 — webview UI is parallelisable with chat-history capture.
Modules covered (per dev plan §3 M2 §2.2)
src/webview/view-provider.tsNexpathDecisionSessionViewProvider implements vscode.WebviewViewProvider.resolveWebviewViewwires enableScripts + localResourceRoots +publishPayloadstores the payload, replaces HTML, and callswebviewView.show(true)for the auto-reveal UX matching architecture rev 2 §4 /onSelecterrors so they never become unhandled rejections (added in audit follow-up).src/webview/html.tsrenderDecisionSessionHtml(payload, opts)— no I/O. Returns full self-contained HTML for the webview. Two modes: empty/watching statedefault-src 'none'with nonce-scoped scripts. All user-controlled strings HTML-escaped including attribute-breaking"(</>/&/"/'). Theming via--vscode-*CSS variables — inherits the host's theme automatically.src/webview/prompt-injection.tshandleOptionSelection(text, deps)with TWO paths: (1) primary —deps.injectFn(text)if provided and returns true; (2) fallback —vscode.env.clipboard.writeText+ info toast directing user to paste.injectFnis the load-bearing contract for Branch 4 to fill in per agent (see memoryproject_b4_prompt_injection_contract.md).Layer C UX consistency
After reading
src/decision-session/TtySelectFn.ts(Layer C — DO NOT MODIFY),the following UX patterns from the existing TTY decision-session were
mirrored into the webview:
Ctrl+Xopt-out (TtySelectFn.ts:128){type: 'dismiss'}Escalso dispatches{type: 'dismiss'}1.2.… prefixes + keys1–9dispatch selectoptionButtons[0].focus()on render(press 1–N, or Esc / Ctrl+X to dismiss)hint in the options headerNot mirrored (intentional): TTY's two-step "Send now / Copy to clipboard"
sub-menu — until B4's
injectFnlands, every path ends in clipboardanyway, so the two-step adds friction without value. TTY's 60s
auto-dismiss timeout — webview is non-modal, the user can let it sit.
Files
src/ext-vscode/src/webview/view-provider.tssrc/ext-vscode/src/webview/view-provider.test.tssrc/ext-vscode/src/webview/html.tssrc/ext-vscode/src/webview/html.test.tssrc/ext-vscode/src/webview/prompt-injection.tssrc/ext-vscode/src/webview/prompt-injection.test.tssrc/ext-vscode/src/extension.ts(modified)src/ext-vscode/src/extension.test.ts(modified)src/ext-vscode/package.json(modified)webview, droppedviewsWelcome(rendered inside the webview now)50 new unit tests added in this branch. Sub-package total: 75 tests
across 6 files (was 25 in B1).
Test plan
Automated