Skip to content

feat(agents-sidebar): Claudian-parity chat panel + session/vault plumbing#429

Merged
Luis85 merged 44 commits into
developfrom
feature/agents-sidebar-chat-panel
May 23, 2026
Merged

feat(agents-sidebar): Claudian-parity chat panel + session/vault plumbing#429
Luis85 merged 44 commits into
developfrom
feature/agents-sidebar-chat-panel

Conversation

@Luis85
Copy link
Copy Markdown
Owner

@Luis85 Luis85 commented May 23, 2026

Summary

Bundles 41 commits of work on the agents sidebar chat panel against develop.

Scope breakdown:

  • 30 × aux — auxiliary panel UI: Claudian-parity polish (G1–G5), composer chrome strip, header collapse, welcome state, brand-color splashes, status-panel hide-when-empty, header logo + model-selector chip, slash/mention popovers via SpDropdownPanel, floating nav + history menu, primitives (SpButton, SpIcon, SpIconButton, SpToggleSwitch, HoverActions), MessageItem extraction (REQ-AUX-014 avatars/model/timestamps), Enter-sends keybinding, optimistic composer clear, refreshed empty-state chips.
  • 9 × asm — agent session manager: --resume sessionId follow-ups (Q-F), readable session filenames + self-id row (Q-E), vault root as cwd (QW-A), system-prompt suffix with active note + selection (QW-B), vault-metadata greeting (QW-C), denylist limited to destructive tools (read/glob/grep unblocked), idle-based per-turn timeout, ignore stdin on Claude CLI spawn (kill TIMEOUT verdict).
  • 1 × asv3MarkdownBlock renders finalised messages on mount.
  • 1 × chore(aux) — drop bare todo from comments to satisfy no-warning-comments.

Test plan

  • npm audit --audit-level=high --omit=dev
  • npm run typecheck
  • npm run lint (0 errors after final commit)
  • npm run test
  • npm run build
  • npm run build:web
  • npm run docs:api
  • CI green on develop target
  • Manual smoke: open agents sidebar in Obsidian, send a turn, confirm vault metadata greeting + active-note context injection, confirm StatusPanel hides on empty thread
  • Manual smoke: npm run dev standalone UI parity

🤖 Generated with Claude Code

Symprowire and others added 30 commits May 22, 2026 12:50
Stages 1-6 artifacts for the UX/visual parity rebuild on top of MPS:
- idea.md (current-MPS + Claudian audits, delta table)
- requirements.md (21 REQ-AUX-* + 12 NFR-AUX-*)
- design.md (Part A UX + Part B UI + Part C Architecture)
- spec.md (interfaces, tokens, 9-workstream graph, 18 CQ-AUX)
- tasks.md (142 TDD-ordered tasks across 10 workstreams)
- dispatch-plan.md (per-WS subagent dispatch prompts)

ADRs filed:
- ADR-AUX-001 IconPort narrow port for obsidian.setIcon
- ADR-AUX-002 --sp-* design-token CSS layer
- ADR-AUX-003 HoverActions hover/focus-reveal primitive

Predecessor: multi-provider-agent-sidepanel (MPS WS-10 closed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands WS-AUX-1 of the agent-ux-parity feature: the additive --sp-*
design-token CSS layer plus the named-keyframes layer, wires the
[data-provider] attribute on the agent root, and captures the gzipped
bundle baseline for the NFR-AUX-001 5% budget check at WS-AUX-10.

Files added:
- src/ui/styles/tokens.css            — spec §4.1-§4.7 token contract
- src/ui/styles/animations.css        — 5 named keyframes + spin reduced-motion
- stories/styles/Tokens.stories.ts    — Tokens reference page (REQ-AUX-017)
- specs/agent-ux-parity/bundle-baseline.json — WS-AUX-1-tip gzipped sizes
- specs/agent-ux-parity/implementation-log.md — Stage 7 log scaffold
- tests/ui/styles/tokens.test.ts      — token-presence contract test
- tests/ui/styles/animations.test.ts  — keyframes-presence contract test
- tests/ui/agent/AgentSidepanelRoot.dataProvider.{test,po}.ts
- tests/ui/agent/AgentSidepanelRoot.providerSwap.test.ts

Files modified:
- decisions/ADR-AUX-002-sp-design-token-css-layer.md — accepted
- src/ui/main.ts                      — import tokens.css + animations.css
- src/ui/agent/AgentSidepanelRoot.vue — [data-provider] binding + class
- specs/agent-ux-parity/workflow-state.md — current_stage -> implementation
- specs/agent-ux-parity/tasks.md      — WS-AUX-1 ticked
- eslint.config.js                    — relax obsidianmd DOM rules for tests/**
- styles.css                          — plugin-build scoped-style hash refresh

Satisfies: REQ-AUX-006, REQ-AUX-007, REQ-AUX-008, REQ-AUX-009,
REQ-AUX-017, REQ-AUX-019, NFR-AUX-001, NFR-AUX-006.

Implements ADR-AUX-002. CQ-AUX-01 (Cursor brand colour) carried through
as the #6b7280 placeholder with inline comment per T-AUX-004 DoD.

Verify gate: green. 2276 unit tests across 232 files. Coverage
91.37/85.35/91.05/92.48 vs 80/70/80/80 thresholds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the narrow IconPort wrapper for obsidian.setIcon (per ADR-AUX-001),
bridge impls (Obsidian / Mock / LocalStorage), InjectionKey + useIconPort
composable, fakeModulePorts() extension, and the <SpIcon> primitive with
co-located PageObject + Storybook story.

Satisfies REQ-AUX-001 (icon system), REQ-AUX-018 (a11y aria-labels on
icon-only affordances).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pDropdownPanel, HoverActions)

Implements WS-AUX-3 of the agent-ux-parity feature (T-AUX-100..121).

New primitives under src/ui/components/primitives/ (each with co-located
PageObject, vitest suite, and Storybook coverage):
- SpButton.vue        — label button, 3 variants, loading/disabled wiring
- SpIconButton.vue    — icon-only button composing SpIcon; required ariaLabel
- SpToggleSwitch.vue  — pill toggle, role=switch + Space/Enter keyboard
- SpDropdownPanel.vue — backdrop-blur dropdown shell, Teleport-to-body
- HoverActions.vue    — canonical hover/focus-reveal slot wrapper (ADR-AUX-003)

ADR-AUX-003 marked Accepted (T-AUX-100). HoverActions ships the
accessibility-tree invariant (opacity-only reveal) plus reduced-motion
and coarse-pointer media-query branches; a dev-only console.warn fires
when consumers mount without the required .sp-hover-host ancestor.

49 new primitive tests green. Verify gate green: typecheck OK, lint OK,
coverage 91.05/85.37/90.92/92.14 (above 80/70/80/80). Plugin gzip total
737,229 B vs baseline 716,631 B → +2.87%, well inside the 5%
NFR-AUX-001 ceiling.

Satisfies: REQ-AUX-002, REQ-AUX-012, REQ-AUX-017, REQ-AUX-018.
References: spec §1.3.2, §1.3.12, §1.3.13, §1.3.14, §3.1.

CQ-AUX-04 (SpDropdownPanel cross-feature impact) remains escalated —
primitive ships scoped to the agent surface only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AgentSidepanelHeader collapses to a single 36px band; ProviderBadge
  and ModelSelector relocate to InputToolbar in WS-6 (placeholder
  comment only).
- ThreadTabBadge (24x24 data-state-driven border) renders inside every
  ThreadTab; mapping per spec §3.4 (active/streaming/attention/idle).
- WelcomeGreeting + WelcomeSuggestionChip with hour-banded serif
  copy + suggestion chips routed through the existing empty-tile
  pre-fill pipeline.
- useNarrowSidepanel composable + NARROW_SIDEPANEL_KEY inject key
  observe the sidepanel root via ResizeObserver and broadcast a
  reactive <360px boolean to descendants; AgentSidepanelRoot binds
  `data-narrow` for downstream CSS hooks.
- Microcopy keys `welcome.greeting.*`, `welcome.suggestion.*`.

Deferred to follow-ups (carry-forward documented in
implementation-log.md): CompactBoundary refresh (no file yet — moves
with WS-AUX-5), Storybook stories (no Storybook bootstrap yet — moves
with WS-AUX-10), Playwright assertion of the Copernicus font-family
(jsdom cannot resolve var() in scoped CSS).

Satisfies REQ-AUX-003, REQ-AUX-004, REQ-AUX-007, REQ-AUX-019.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds StreamingCursor (REQ-AUX-008), NestedDetailFrame (REQ-AUX-013),
MessageBubble (REQ-AUX-005/010), and a token-driven CompactBoundary
refresh (WS-AUX-4 deferred). Refactors ThinkingBlock + ToolCallBlock
onto NestedDetailFrame and migrates MessageActions to HoverActions +
SpIconButton with 1.5s Copied aria-label swap (REQ-AUX-002/016).
MessageList wires MessageBubble (role-aware shell) with MessageActions
in #actions slot, replaces literal U+258D cursor with <StreamingCursor>,
and renders compact boundaries through <CompactBoundary>.

Deviations:
- CQ-AUX-06 Fork action stays escalated; ships behind showFork prop
  defaulting to false. Microcopy + icon + emit are wired.
- SubagentBlock.vue does not exist yet; NestedDetailFrame ready to
  wrap it when it lands.
- Per-message avatar + timestamp gating deferred — MessageItem.vue is
  not a separate file (transcript rendered inline in MessageList).

Verify: typecheck GREEN; lint GREEN (0 errors); unit GREEN
(2430/2430); storybook GREEN (65/65); build GREEN main.js gzip
717.87 kB (+1.24 kB / +0.17% vs WS-4 baseline 716.63 kB);
build:web GREEN (95.83 kB gzip).

Refs REQ-AUX-001, REQ-AUX-002, REQ-AUX-005, REQ-AUX-008, REQ-AUX-010,
REQ-AUX-013, REQ-AUX-016, T-AUX-225..254.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
contextUsageStore + ContextMeter (SVG donut, brand->error >80%), McpIndicator
(zap + count + mcp-glow), InputToolbar in REQ-AUX-004 normative order
(model . mode . permission . thinking . mcp . context-meter . send),
ProviderBadge copy table ('Claude . CLI' not 'claude/cli'),
ProviderMenu + ModelSelector -> SpDropdownPanel, ModeIndicators ->
SpToggleSwitch, AttachmentStrip nested inside composer wrapper (CQ-AUX-18),
arrow-up-to-edit-last-user-message guard (CQ-AUX-10).

Satisfies REQ-AUX-004, REQ-AUX-012, REQ-AUX-016.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Group StatusPanel + AttachmentStrip + composer in shared .sp-composer-group
(max-height: min(40vh, 320px), own scroll). Surface dormant
ChatDegradedState via new TransportStatusPill in MessageList. Wire
↓-new-messages pill when streaming + scrolled-up.

Satisfies REQ-AUX-011, REQ-AUX-016.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds InlineApprovalCard Claudian-parity tabbed widget alongside existing
ApprovalCard. ApprovalCard remains the active widget; switch-over happens
in WS-8b. Sets up the single-resource case as the 1-tab default while
keeping the multi-resource tabbed shape forward-compatible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MessageList now renders InlineApprovalCard instead of legacy ApprovalCard
(legacy retained as dead code for WS-10 cleanup). HelpPopover refresh with
search input, arrow-key navigation, polite live region for result count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SlashCommandDropdown and MentionDropdown now compose SpDropdownPanel for
backdrop-blur chrome + drop-up positioning. Closes WS-AUX-8 alongside
8a (InlineApprovalCard) and 8b (MessageList switchover + HelpPopover).

Satisfies REQ-AUX-020, REQ-AUX-021 final pieces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FloatingNavSidebar (32px circular buttons, opacity 0.15->1 on hover,
hidden on narrow), NavSidebarButton primitive, ThreadHistoryMenu with
hover-reveal rename/delete actions. scripts/lint-style-tokens.mjs guards
against raw Obsidian-var leakage and physical-side CSS properties under
src/ui/agent + src/ui/components/agent; wired into npm run verify.
Source sweep to logical properties.

Satisfies REQ-AUX-009, REQ-AUX-010, REQ-AUX-016, NFR-AUX-006, NFR-AUX-010.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes implementation stage. Storybook coverage for all NEW components from
spec.md §5. parity-screenshots.md checklist for manual side-by-side capture
at 3 breakpoints. bundle-final.json delta vs baseline. traceability.md
regenerated. test-report.md + release-notes.md drafted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
InlineApprovalCard fully replaced ApprovalCard in WS-8b. Legacy file was
retained as dead code with WS-10 cleanup scheduled but missed. This commit
completes the deletion: removes the Vue component, its tests, story, and
any orphaned i18n keys.

Closes review finding R-AUX-01.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts MessageItem.vue from MessageList per-message template. Adds bot-icon
+ model name for assistant turns, optional relative-time timestamp gated by
messageTimestamps PluginSettings flag (default false). Closes deferred
REQ-AUX-014.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude CLI v2.x with `-p` holds the subprocess open until stdin EOF when
stdin is a pipe. From the Obsidian renderer this manifests as the 30 s
per-turn timeout firing — surfaced to users as "That took too long.
Please try again." even though the CLI is installed and operational.

We never write to stdin in this code path (prompt + flags are in argv),
so switching the inherited stdio shape from ['pipe','pipe','pipe'] to
['ignore','pipe','pipe'] lets the CLI exit immediately after `--print`
without waiting on a never-closing stdin.

Updates SubprocessLifecycle.spawn shape and its only assertion in
SubprocessLifecycle.test.ts. Same fix benefits the Cursor adapter that
shares this lifecycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cherry-picks the onMounted trigger from the dormant
fix/asv3-markdownblock-render-on-mount branch (commit 9a84f0d).

The `watch(..., { immediate: true, flush: 'post' })` can fire its
immediate run before the template ref binds inside Obsidian's view
lifecycle (Vue 3.5), so nativeContainer.value is still null and
rerenderNative returns early. Streaming bubbles get re-fired by
subsequent text deltas, but finalised user/assistant messages — whose
text never changes again after append — never paint. User sees empty
bubbles and thinks Ctrl+Enter didn't send when in fact the turn went
through end-to-end and only the rendering was lost.

The CSS-var reverts on that dormant branch are intentionally NOT
included: the agent-ux-parity work (WS-AUX-1) replaced direct Obsidian
vars with the --sp-* token layer, which the lint-style-tokens guard
enforces. The onMounted trigger is the only load-bearing change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two paths in ChatInput.handleKeydown could silently eat the Ctrl/Cmd+Enter
commit gesture:

1. `handlePaletteKeydown` selected a slash-command on plain Enter — and on
   Ctrl+Enter too — when the palette was open, never falling through to
   `tryHandleSendKey`. Mirror the mention-picker's `tryCommitFromKey`
   guard: bail when Ctrl/Cmd is held so the commit reaches the send path.
2. The IME composition guard returned unconditionally when
   `event.isComposing === true`. Obsidian on Windows can report a spurious
   `isComposing=true` outside of CJK input flows, blocking send for users
   with no IME stack. Restrict the guard to keystrokes without a command
   modifier — Ctrl/Cmd+Enter never composes a glyph and must always send.

Test updates: the old assertion "Ctrl+Enter on open palette emits
select-command (not send)" inverts to "send wins, palette is bypassed";
"Enter alone" now drives palette selection. The IME assertion splits:
plain Enter still suppressed during composition, Ctrl+Enter still commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 30 s absolute timeout fired mid-conversation whenever Claude CLI used
tools (Read / Grep / Bash / WebSearch …): tool execution easily exceeds
30 s for any non-trivial vault investigation, and the turn was aborted as
TIMEOUT even though the subprocess was actively streaming progress events.

Switch to an IDLE timer that resets on every NDJSON line written by the
CLI: a turn that keeps producing system / stream_event / tool deltas
never trips the timeout, regardless of total elapsed time. The timeout
only fires when the subprocess goes truly silent for the full window
(default 10 min, clamp 1 s – 1 h).

Implementation:

- DEFAULT_TIMEOUT_MS bumped 30_000 → 600_000 (10 min idle).
- MAX_TIMEOUT_MS bumped 300_000 → 3_600_000 (1 h hard ceiling).
- TurnProc carries a `resetIdleTimer` callback installed alongside the
  timer; `_handleNdjsonLine` calls it on every line.
- `_installStreamTimeout` returns a `cancel()` rather than a raw handle
  so the finally-block clears the *latest* armed timer (reset re-arms
  the timer onto a new handle the caller doesn't see).
- Error message updated to "idle for N ms" so logs are honest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…l+Enter

The Ctrl/Cmd+Enter send gesture proved fragile inside Obsidian on Windows
— two separate paths in ChatInput.handleKeydown silently swallowed it
(IME composition guard + slash-command palette consuming Enter regardless
of modifier), and even after both were fixed users still hit edge cases
where the keystroke never reached `emit('send')`. Switch to the modern
chat-UI convention used by Claudian, ChatGPT, and most peers:

  - Enter (no modifiers) sends.
  - Shift+Enter inserts a newline (default textarea behaviour, we don't
    preventDefault).
  - Ctrl/Cmd+Enter no longer fires send anywhere.
  - Picker and palette still consume plain Enter to commit a highlighted
    entry, so Enter only sends when both are closed.

IME guard reverts to unconditional: `event.isComposing === true` always
suppresses send so CJK candidate confirmation never races the turn.

`handlePaletteKeydown` complexity refactored: palette-commit logic
extracted to a pure helper to keep cyclomatic complexity under the
project's 10 ceiling. Page object exposes `triggerShiftEnter` for the new
newline gesture; `triggerSendKey` now triggers plain Enter (modifiers
argument retained for source-compat at call sites).

Test suite updated: 44 ChatInput tests pass (was 45 — the
"Ctrl+Enter+isComposing" assertion no longer applies and merged with the
plain-Enter IME case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After hitting Enter the user immediately gets an empty textarea so they
can start drafting the next turn while the agent is still streaming. The
in-flight text is restored only on failure (orchestrator returned
!result.ok OR the streaming reducer set messages.status to 'error') and
only when the textarea is still empty — anything the user has typed for
the next turn wins over the restore.

Mirrors Claudian's send-and-keep-typing UX and resolves the perceived
"my message stayed in the box" rough edge where the user couldn't tell
whether the send had registered.

Test coverage: ChatSidebar TEST-CCS-016 (timeout retains userText) still
passes — the restore-on-error branch fires when status==='error'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ools

The agent inside the side panel was functionally inert for vault
investigation tasks: hard-coded denylist passed every built-in tool name
(including Read, Glob, Grep) to --disallowedTools, so the CLI refused to
read a note, list a folder, or pattern-search the vault. Users hit this
as "ask agent to review the vault" failing with no actionable output.

Narrow the denylist to tools that genuinely need plugin mediation:

  - Edit / Write — route through FileWriteProposal so every mutation
    surfaces the accept dialog (trust-first, NFR-ASM-011).
  - Bash         — host shell execution out of scope; would bypass the
    proposal model.
  - WebFetch / WebSearch — privacy; never silent network egress.

Read, Glob, Grep, LS, NotebookEdit, and the rest of the built-in CLI
toolbelt are now available to the agent. The proposal flow already
catches the destructive surface, so loosening the read-only surface is
net-neutral on safety and net-positive on capability.

REQ-ASM-028 contract updated in code; spec doc to follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plumbs the vault base path through SubprocessLifecycle.spawn so the
Claude CLI subprocess (and the Cursor adapter via the same lifecycle)
inherits the user's vault root as its cwd. Without this, every tool the
agent invokes resolves relative paths against Obsidian's renderer cwd
(somewhere in the install dir on Windows), which made every "read
Daily/today.md" or "glob notes/**/*.md" call miss.

Wiring:

  - SubprocessLifecycle.spawn(binaryPath, argv, event, cwd?) — passes
    `cwd` to child_process.spawn opts when present (non-empty string),
    omits when null/undefined for the standalone-web demo.
  - ClaudeSubprocessAdapterDeps.getVaultBasePath?: () => string | null
    — adapter reads on every spawn so the user can switch vaults
    mid-session without reinstantiating.
  - CursorCliAdapter mirrors the same dep + behaviour.
  - runSubprocessStructured forwards the cwd through to the lifecycle.
  - ObsidianBridge.getVaultBasePath() returns
    (vault.adapter as FileSystemAdapter).getBasePath() when desktop.
  - MockBridge accepts a vaultBasePath ctor option (default '/mock/vault').
  - LocalStorageBridge returns null.
  - plugin/main.ts wires getVaultBasePath through both adapter
    constructors.

Tests:
  - tests/infrastructure/obsidian/SubprocessLifecycle.cwd.test.ts
    asserts spawn opts include/omit cwd correctly.
  - existing assertions updated to include cwd: '/mock/vault'.

Closes QW-A. Unblocks QW-B (active-note injection) — the agent can now
resolve the path it receives in the system-prompt suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…QW-B)

Closes the agent-UX gap left by QW-A. The Claude CLI subprocess now
runs with the vault root as cwd (QW-A) AND knows which note the user
is currently looking at (QW-B), so prompts like "summarise this" or
"refactor the selection" resolve without the user spelling out paths.

Changes:
- Extend WorkspacePort with sync getActiveFilePath / getActiveSelection.
- ObsidianBridge wraps workspace.getActiveFile()?.path and
  workspace.activeEditor?.editor?.getSelection() with defensive
  null/throw handling for view-transition races.
- MockBridge gains setActiveFilePath / setActiveSelection test
  fixtures; LocalStorageBridge returns null in both (no editor in
  the browser demo).
- New pure helper composeVaultContextBlock formats a <vault-context>
  block; auto-grows to a 4-backtick fence when the selection itself
  contains triple-backticks.
- buildTurnInput prepends the block to the stage-aware suffix when at
  least one signal is present; never double-emits if the stage suffix
  already opens a <vault-context> tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On the first turn of each thread the system-prompt suffix now opens with
a `Vault: <name> (<n> notes)` row inside the existing <vault-context>
block. Anchors the agent's mental model of which Obsidian vault it is
working in without polluting follow-up turns (cheap on token cost since
it fires exactly once per thread lifetime).

  - WorkspacePort gains sync `getVaultName()` + `getMarkdownFileCount()`.
    ObsidianBridge: `app.vault.getName()` / `getMarkdownFiles().length`.
    MockBridge: fixtures via `setVaultName` / `setMarkdownFileCount`.
    LocalStorageBridge: `'demo'` / `0`.
  - `composeVaultContextBlock` extended with `vaultGreeting` (nullable).
    Greeting row emitted at top of block when non-null; pluralisation
    handles 0 notes / 1 note / N notes. Block still suppressed when
    every field is empty.
  - `TurnInputBuilder` detects "first turn" via
    `thread.messages.length === 0` and passes vaultGreeting only then.
  - Back-compat shim accepts the old (path, selection) positional call
    sites so QW-B consumers don't break during the migration window.

Closes QW-C. Compounds with QW-A (cwd) + QW-B (active-note + selection)
to give the agent a full picture of where it is, what file is open, and
what the user has selected, on every turn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(QW-D)

Replace the four legacy hint chips (slash/mention/send/escape) with vault-investigation prompts that exercise the newly-unblocked Glob/Grep/Read tools: Find orphan notes, Summarize active note, Find #project notes, Audit broken wikilinks. Each chip carries a Lucide icon via <SpIcon> and emits {id, prompt}; AgentSidepanelRoot pre-fills the composer textarea with the prompt and focuses it (user reviews before sending — no auto-dispatch). German labels translated, prompts kept English-only by design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QW-C now emits a `Vault: <name> (<n> notes)` greeting at the top of
the systemPromptSuffix on the first turn of every thread, so the
5 ChatSidebar.stagePrompt assertions that demanded `systemPromptSuffix
=== ''` started failing — the suffix is no longer empty by design.
Replace the equality check with the actually-load-bearing invariant:
the suffix must NOT contain a stage-prompt row (asserted via
absence of the literal `Stage:` keyword that buildStagePrompt emits).

Confirms the "no stage prompt" branch still degrades correctly to a
vault-context-only suffix without leaking a stale stage row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…key (Q-E)

Three drive-by improvements to the per-thread session log surface,
prompted by the in-plugin agent's self-audit.

1. Session filenames switch from raw UUIDs to
   `<YYYY-MM-DD>_<slug>__<short-uuid>.md`. Slug derived from the first
   user message via a pure `slugifyForSessionLog` helper (ascii-only,
   unicode-normalised, max 40 chars, falls back to `untitled` for
   empty / all-symbol input). Short-uuid (8 chars of session_id) keeps
   filenames unique even when two threads on the same day share an
   opening message. Existing UUID files keep working — the conflict-
   suffix loop still resolves them.

2. `transport:` frontmatter key dropped — internal detail the user did
   not need, and we have no consumer that reads it. Frontmatter shrinks
   to four keys (session_id, feature, created, updated).

3. `composeVaultContextBlock` emits a leading
   `Plugin: Specorator v<version>` row above the vault row on first
   turn. Anchors agent self-identity. Name/version read from
   manifest.json via a new TurnInputBuilder dep — fork-safe, no
   hard-coding.

Test coverage: 957 unit tests pass across application + infrastructure.
Coverage threshold holds. Bundle gzip ~733 kB (within QW-D delta).

Closes Q-E from the plugin-agent self-audit #2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symprowire and others added 11 commits May 23, 2026 12:58
Wire the captured Claude CLI session id through every follow-up turn so
the harness no longer re-injects the full conversation history each
time. Findings + implementation:

- Q-F.1: TurnInputBuilder.decideRotation already forwards reuseSessionId
  for every reuse turn; behaviour locked with two explicit tests.
- Q-F.2: buildPrompt() does NOT concatenate prior conversation history
  today — it only joins user text with the deduped context-file
  preamble. The CLI's native --resume rehydration is the sole memory
  channel; no gating change needed.
- Q-F.3: The vault greeting still suppresses on follow-up turns via the
  existing isFirstTurn = (thread.kind === 'rotate') gate; covered by
  QW-C test in TurnInputBuilder.test.ts.
- Q-F.4: On a failed --resume turn (terminal QUERY_FAILED with a
  resumeSessionId in flight) the orchestrator now calls the new
  threadsStore.clearSessionId(threadId) action so the next user turn
  falls back to a fresh CLI session rather than retrying a dead
  resume id forever. Wired for both free-text and structured branches.

REQ-ASM-035.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Side-by-side comparison with Claudian (Plugin Comparison.canvas)
showed three layers of chrome on the composer that Claudian does not
have:

  - "Ask Claude." heading + "Using your installed Claude tool" subtitle
    above the textarea
  - Uppercase "CONTEXT FOR THIS MESSAGE." label above the context chip
  - "(auto)" suffix on the context chip name

Strip all three. The textarea stands alone with the context chip pill
riding directly above it. i18n keys retained for now (harmless orphans);
they will be GC'd in a later sweep.

First batch of the G-parity workstream. G2 collapses the header, G3
recenters the welcome greeting, G4 applies brand-color splashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RALPH G2 loop. Four sub-commits squashed:

- G2.1: drop "No feature in focus" caption. The header only renders the
  feature-scope chip when a thread is actually scoped to a feature;
  otherwise the band is just logo + wordmark.
- G2.2: ThreadTabStrip is only mounted when chatThreads.size > 1.
  Single-thread sessions get no tab strip; it pops in automatically
  once a second thread is created.
- G2.3: add a "New conversation" SpIcon (square-plus) button as the
  topmost entry on FloatingNavSidebar, routed via the existing
  handleNewConversation pipeline so the in-flight guard is preserved.
  Adds agent.nav.newConversation i18n keys (en + de).
- G2.4: remove the "New conversation" button from AgentSidepanelHeader.
  Header collapses to a single logo+title row.

Provider/model row was already migrated to InputToolbar in WS-6 (no
duplication in the header), so G2 leaves it alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move WelcomeGreeting INSIDE MessageList's empty branch so the centred
serif greeting + chips occupy the full transcript region (Claudian
"What's new?" parity). Drop the legacy dashed tile grid and the
WelcomeGreeting mount from AgentSidepanelRoot; suggestion-pick now
bubbles up through MessageList. WelcomeGreeting internal layout is a
4-row grid: greeting-group vertically centred, chip strip anchored
near the bottom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rity

G4.1 — ModelSelector trigger renders the active model name in --sp-brand at
weight 600 via a `--brand` modifier on the <select>.

G4.2 — SpToggleSwitch is-on state already painted in --sp-brand (WS-3);
locks the contract with a regression test + PO accessor.

G4.3 — ModeIndicators chips gain a `--active` modifier so plan / bang-bash /
instruction chips take --sp-brand on border + text + translucent wash when
their store flag is on. Inactive chips remain muted.

G4.4 — InputToolbar's send button resolves SpIconButton variant from state:
`primary` (brand fill) only when not streaming AND not disabled; otherwise
`secondary` (muted). Exposes data-send-variant on the slot for PO assertions.

Token-layer remains the sole owner of brand colour — no hard-coded #D97757
outside tokens.css.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Polish pass after side-by-side review against the Claudian reference:

  - Add a brand-orange `<SpIcon name="sparkles">` to the header band,
    left of the title — Claudian shows a brand-colour starburst logo;
    we mirror with the closest in-stock Lucide glyph through IconPort.
  - Model selector drops the visible uppercase "MODEL" label and the
    box border + background; the `<select>` now reads as a bare
    brand-coloured chip that matches Claudian's "Opus" affordance.
    Original label is preserved as a screen-reader-only span so the
    `<select>` keeps its accessible name (aria-labelledby).
  - Tests provision IconPort + LoggerPort for the header mount path so
    SpIcon's composables resolve.

Closes G5 in the parity workstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Playwright screenshot of the standalone /agent-preview route (added +
removed in this same branch) showed the status panel rendering its full
chrome (STATUS heading, TODOS / BASH HISTORY empty stubs) on the
welcome screen, eating ~150px of vertical space and pushing the
composer below the viewport.

Gate the entire `<section class="sp-status">` behind a `hasContent`
computed (todos.length > 0 || bashHistory.length > 0). The panel
materialises the moment the agent emits its first todo or bash event;
until then the welcome state owns the whole transcript area like in
Claudian.

Tests: StatusPanel.test seeds one TodoEntry in its mountPanel helper so
the collapse / scroll-cap assertions still find the panel chrome. New
behaviour covered indirectly — the seed-or-hide split is the single
boundary the helper exercises.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ents

ESLint no-warning-comments flags the bare word in StatusPanel.vue and its
test. Reword to 'task' — same intent, lint-clean.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e70744efa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/application/chat/SessionLogWriter.ts
…n slug-named log

`appendProposalDecision` passes `null` as `firstUserMessage`, so the
writer's path resolver collapses `slugPath` to the legacy `<sessionId>.md`
shape. The per-instance `resolvedPaths` cache normally hides this from
follow-up turns, but a plugin restart blows the cache — accepting a
persisted proposal before any new user-assistant turn lands then writes
the `## proposal` audit row to a fresh UUID-named file, splitting one
conversation across two logs.

Add a folder-scan fallback inside `resolveConflictSuffix`: when
`legacyPath === slugPath` (i.e. no `firstUserMessage` hint), list the
sessions folder and reuse any existing `.md` whose frontmatter
`session_id` matches. Memoised through the existing `resolvedPaths`
cache so the scan runs at most once per `sessionId` per writer instance.

Regression test mints a slug-named log via `appendUserAssistant`, spins
up a fresh writer (simulating restart), and verifies
`appendProposalDecision` appends to the existing slug-named file
without creating a parallel UUID-named log.

Closes Codex P2 review comment on PR #429.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96aabcab35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/application/chat/SessionLogWriter.ts Outdated
Codex P1 follow-up on PR #429: `tryReuseExistingLog` previously early-
exited when `legacyPath !== slugPath` so the folder-scan only fired on
proposal-decision callers. Resumed `appendUserAssistant` turns pass the
*current* turn's user text as `firstUserMessage`; after a plugin restart
the in-memory cache is empty, so the resolver derived
`<date>_<later-msg>__<id>.md` and split the conversation across two
files even though `<date>_<first-msg>__<id>.md` already held the same
`session_id`.

Drop the early exit. Always scan the sessions folder (skipping
`slugPath` itself, which the suffix-loop path already probes) for an
existing `.md` whose frontmatter `session_id` matches. Still memoised
via `resolvedPaths`, so the scan runs at most once per sessionId per
writer instance.

Regression test seeds a slug-named log via writer #1, spins up writer #2
(simulating restart), and verifies a follow-up turn with a different
user text appends to the existing file instead of creating a parallel
`<date>_<later-msg>__<id>.md`.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64d5c37c1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/application/chat/SessionLogWriter.ts Outdated
…robe

Codex P2 follow-up on PR #429: the folder-scan reuse path called
`vault.readFile` unguarded for every `.md` in the sessions folder, so a
single unreadable sibling (file deleted between list and read, transient
I/O, permission glitch) would reject the whole scan and abort the
caller. `appendUserAssistant` would swallow the rejection via its
fire-and-forget path; `appendProposalDecision` would surface it as
`SESSION_LOG_FAILED` even though the target log is writable.

Wrap the per-file read in `tryAsync`, log skipped offenders at `debug`,
and continue the scan. A stale entry is preferable to a false-negative
append failure.

Regression test seeds the real slug-named log, drops a decoy `.md`
sibling whose `readFile` throws, and verifies the proposal-decision
append still lands in the real log.
@Luis85 Luis85 merged commit d4bc4d4 into develop May 23, 2026
9 checks passed
@Luis85 Luis85 deleted the feature/agents-sidebar-chat-panel branch May 23, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants