Skip to content

FE-656: Side-chat V1.1 β€” design spec + Explore vertical slice#81

Merged
graphite-app[bot] merged 20 commits into
mainfrom
ka/fe-656-side-chat
May 7, 2026
Merged

FE-656: Side-chat V1.1 β€” design spec + Explore vertical slice#81
graphite-app[bot] merged 20 commits into
mainfrom
ka/fe-656-side-chat

Conversation

@kostandinang
Copy link
Copy Markdown
Contributor

@kostandinang kostandinang commented May 1, 2026

What

Adds the canonical design spec for the side-chat (FE-656) and ships the V1.1 Explore vertical slice β€” chat-with from graph view β†’ SideChatHost provider β†’ /side-chat SSE β†’ streaming response.

The side-chat is a popover-to-panel chat surface anchored to spec items. Two entry modes (per-row chat-with button and selection floating menu); three intents (Explore Β· Edit Β· Annotate); proposed changes stage in a top-bar patch list. Edit is internally a router to Refine / Soft / Hard tiers.

This design subsumes:

  • D128 graph-launched refinement (the disabled chat-with placeholder)
  • Trigger-popover composer
  • Revisit / edit mode + cascade preview (docs/design/REVISIT_MODULE.md)

Why

Today, all interaction with the spec runs through one long interview thread. When the user notices something in the structured spec view they want to discuss or edit, they have to navigate back to the chat and reintroduce the topic. The side-chat closes that gap: chat about an item, edits to an item, and annotations on an item all converge through the same review surface.

Phasing

  • V1.1 (this PR) β€” Explore vertical slice only: chat-with from graph view, corner-anchored popover, /side-chat SSE, streaming response, SideChatHost lifted to spec-route layout
  • V1.2 β€” Class 4 Annotate, floating selection menu, multi-item pinning, top-bar patch-summary scaffold
  • V2 β€” Edit + Drill-down + Propose-edge (None / Soft tiers apply directly; Hard deferred)
  • V3 β€” Hard edit absorbs REVISIT_MODULE
  • V4 β€” patch / event-stream data model + item versioning; architect loop integration

Scope of this PR

Design + planning

  • docs/design/SIDE_CHAT.md β€” canonical design spec
  • memory/PLAN.md β€” replace trigger-popover composer with side-chat; remove revisit/edit mode from Horizon; add architect/generator loop to Horizon
  • memory/SPEC.md β€” Requirement 34, A71/A72/A73 (status: future), D130, D131; soften D80
  • docs/design/REVISIT_MODULE.md β€” mark as subsumed; cascade lifecycle remains valid as the V3 hard-edit path

V1.1 implementation

  • Card A: prompt builder (functional core)
  • Card B: POST /side-chat SSE endpoint
  • Card C: SideChatPopover skeleton
  • Card D: graph view β†’ streaming response
  • Refactors: collapse pendingAssistantText into messages; extract SideChatHost provider
  • Polish (E1+E2+E3): lift SideChatHost to spec-route layout; corner-anchored popover; visible error states
  • No UI as per figma file right now - being addressed in subsequent PR.

View in action:

https://www.loom.com/share/84c413168ba34c0ab794a54c9f552fc3

Test plan

Design review

  • Read docs/design/SIDE_CHAT.md end-to-end
  • Verify memory/PLAN.md + memory/SPEC.md deltas capture full subsumption
  • Confirm A71/A72/A73 are scoped future, not blocking V1

V1.1 functional

  • Click chat-with on a graph row β€” popover anchors to row, opens to corner
  • Send a message β€” SSE streams the response into the panel
  • Trigger an error β€” error renders visibly inside the panel
  • Navigate between specs β€” SideChatHost survives the route change

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 1, 2026

FE-656 Side chat

Problem / Motivation

  • Given a structured view of the specs, user may want to ask questions or discuss the spec without losing their place

Acceptance criteria:

  • From the structured spec view, users can open a popover side chat
  • Users can highlight and attach context from the structured spec view to the chat
  • Users can trigger updates to the spec from the chat

Review in Linear

Copy link
Copy Markdown
Contributor Author

kostandinang commented May 1, 2026

@kostandinang kostandinang force-pushed the ka/fe-656-side-chat branch from 9183dc1 to 3f1db1e Compare May 1, 2026 15:05
@kostandinang kostandinang self-assigned this May 1, 2026
@kostandinang kostandinang changed the title FE-656: Add side-chat design spec, formalize in PLAN/SPEC FE-656: Side-chat V1.1 β€” design spec + Explore vertical slice May 4, 2026
@kostandinang kostandinang requested a review from lunelson May 4, 2026 11:41
@kostandinang kostandinang marked this pull request as ready for review May 4, 2026 11:42
@cursor
Copy link
Copy Markdown

cursor Bot commented May 4, 2026

PR Summary

Medium Risk
Introduces a new streaming SSE endpoint and client-side state/abort handling, which can impact reliability and resource cleanup (hanging streams, abort races) if incorrect. Changes are additive and well-covered by new unit/integration tests, but touch both client routing/layout and server request handling.

Overview
Adds the Side-chat V1.1 β€œExplore” vertical slice: graph view chat-with now opens a SideChatPopover via a new SideChatHost provider, sends messages to POST /api/specifications/:id/side-chat, and streams assistant text back incrementally via SSE.

On the server, introduces a new side-chat route with Zod validation, spec/item resolution, prompt construction (buildSideChatPrompt), and SSE streaming with client-abort handling and error events (explicitly not creating turns or invoking observer/interviewer). Planning/docs are updated to make side-chat the canonical refinement surface (new docs/design/SIDE_CHAT.md, PLAN/SPEC renumbering and subsumption notes), and test coverage is added across UI behavior, SSE parsing, and the new endpoint (plus a longer client build-boundary test timeout).

Reviewed by Cursor Bugbot for commit 9a94392. Bugbot is set up for automated code reviews on this repo. Configure here.

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented May 4, 2026

πŸ€– Augment PR Summary

Summary: Adds a proposed design spec for the new side-chat surface (FE-656) and ships the V1.1 β€œExplore” vertical slice end-to-end.

Changes:

  • Adds the canonical side-chat design doc (docs/design/SIDE_CHAT.md) and updates memory/PLAN.md / memory/SPEC.md to capture the new frontier item, decisions (D130/D131), and future assumptions (A71–A73).
  • Introduces a client-side SideChatHost provider + SideChatPopover UI that mounts at the specification route layout and can be opened from graph rows.
  • Activates the graph-view chat-with action rail when rendered under SideChatHost, opening the popover pinned to the clicked item.
  • Adds a client streaming helper (streamSideChatResponse) that POSTs to a new SSE endpoint and parses data: framed events.
  • Adds a new server endpoint POST /api/specifications/:id/side-chat that resolves the pinned item from entities and streams LLM text deltas via text/event-stream.
  • Adds focused unit/integration tests covering popover behavior, SSE parsing, and the route’s validation/invariants (no turn writes, no observer).
  • Extends the client build-boundary test timeout to reduce flakiness.

Technical Notes: The side-chat stream is intentionally separate from the main interview workflow (preserving D113 invariants) and currently implements Explore-only behavior with pending/error message states on the client.

πŸ€– Was this summary useful? React with πŸ‘ or πŸ‘Ž

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/client/lib/side-chat-stream.ts
Comment thread src/client/components/side-chat-host.tsx Outdated
Comment thread src/server/side-chat-route.ts
Comment thread src/client/components/side-chat-host.tsx Outdated
Comment thread src/client/components/side-chat-host.tsx Outdated
Comment thread src/server/side-chat-route.ts Outdated
kostandinang added a commit that referenced this pull request May 4, 2026
Addresses Cursor + Augment review feedback on PR #81:

- SideChatHost: tag each session with an id, hold an AbortController
  ref, abort on openFor/dismiss/unmount, and gate stream callbacks on
  the captured sessionId so stale chunks can't corrupt a swapped
  pinned item. Async work moved out of the setActiveSideChat updater.
- streamSideChatResponse: throw when response.body is null instead of
  silently returning, so callers surface an error state.
- Server SSE: AbortController wired to res 'close', abortSignal
  threaded into streamText so model generation halts on client
  disconnect.
- side-chat-route: switch zod import to 'zod/v4' to match the rest
  of the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/client/components/side-chat-host.tsx
kostandinang added a commit that referenced this pull request May 4, 2026
Addresses Cursor review feedback on PR #81: the UI rendered a
multi-turn thread but each request only carried the latest message,
so the LLM had no memory of prior exchanges.

- Server schema accepts an optional history: { role, text }[] array;
  empty-text entries are rejected.
- buildSideChatPrompt threads prior turns into the messages array,
  with the pinned-item context still anchored on the first user turn
  and the new user message appended at the end.
- streamSideChatResponse forwards history to the route.
- SideChatHost collects finalized turns (pending + error placeholders
  filtered out) and sends them on every submit.

Tests cover the new shape end-to-end: prompt builder, route schema +
forwarding, streaming-helper body, and a multi-turn scenario in the
SideChatHost integration test (including error-retry hygiene).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/client/components/side-chat-host.tsx
Comment thread src/client/components/side-chat-host.tsx
Comment thread src/client/components/side-chat-host.tsx Outdated
Comment thread src/client/components/side-chat-host.tsx
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cedca3a. Configure here.

Comment thread src/server/side-chat-route.ts
lunelson
lunelson previously approved these changes May 6, 2026
Copy link
Copy Markdown
Contributor Author

kostandinang commented May 6, 2026

Merge activity

  • May 6, 3:14 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 3:14 PM UTC: Graphite couldn't merge this PR because it had merge conflicts.
  • May 6, 3:18 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 3:18 PM UTC: Graphite couldn't merge this PR because it had merge conflicts.
  • May 6, 3:30 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 3:30 PM UTC: Graphite couldn't merge this PR because it had merge conflicts.
  • May 6, 3:36 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 3:36 PM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 6, 4:24 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 4:24 PM UTC: Graphite couldn't merge this PR because it had merge conflicts.
  • May 6, 4:38 PM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 6, 4:48 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 4:48 PM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 6, 4:54 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 4:54 PM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 6, 8:21 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 6, 8:22 PM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 7, 7:43 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 7, 7:44 AM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 7, 7:57 AM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 7, 7:57 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 7, 7:58 AM UTC: Graphite couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 7, 8:00 AM UTC: lunelson added this pull request to the Graphite merge queue.
  • May 7, 8:02 AM UTC: The Graphite merge queue couldn't merge this PR because it failed for an unknown reason (This repository has GitHub's merge queue enabled. Please configure the GitHub merge queue integration in Graphite settings.).
  • May 7, 8:15 AM UTC: lunelson added this pull request to the Graphite merge queue.
  • May 7, 8:16 AM UTC: Merged by the Graphite merge queue.

kostandinang and others added 4 commits May 6, 2026 17:51
Adds docs/design/SIDE_CHAT.md as the canonical design for the side-chat β€”
a popover-to-panel chat anchored to spec items in the structured spec view,
with patch-list staging in the persistent app top-bar. Subsumes the prior
trigger-popover composer, revisit / edit mode, and D128 chat-with seam into
one user-driven mutation surface.

memory/PLAN.md
- Replace trigger-popover composer (Next) with side-chat
- Remove revisit / edit mode + cascade preview from Horizon (subsumed)
- Add architect / generator loop to Horizon
- Update dependency graph

memory/SPEC.md
- Add Requirement 34 (side-chat surface)
- Add A71 (patch / event-stream model), A72 (item versioning),
  A73 (architect loop) β€” status: future
- Add D130 (side-chat as unified user-driven mutation surface)
- Add D131 (patch list canonical staging in top-bar)
- Soften D80 to acknowledge chat-level branching and replace the modal
  secondary thread with the side-chat panel mode

docs/design/REVISIT_MODULE.md
- Mark as subsumed; cascade lifecycle remains valid as the V3 hard-edit
  path inside the side-chat panel

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each version introduces a meaningfully different lifecycle/persistence
seam, passing the anti-fragmentation test as separate frontier items:

- V1 (Next 4): panel surface + Explore + Annotate. Establishes the
  popover-to-panel chat, top-bar patch summary scaffold, comment-store
  extension for annotations, and the floating selection menu.
- V2 (Next 5): Edit / Drill-down / Propose-edge. Activates cross-surface
  intent emission to the turn machinery and D125's typed relation policy.
  Soft-impact edits apply directly via soft-recompute; hard-impact defers
  to a placeholder until V3.
- V3 (Horizon): Hard edit absorbs REVISIT_MODULE β€” cascade preview inline,
  batch-resolution secondary-thread mode in the panel.

V1 stays in one branch (FE-656 currently); subsequent versions get their
own Linear issues + branches when their turn arrives.

Updates Track A dependency graph and the leading rationale paragraph.
V1.1's vertical slice decomposes into 5 sub-cards (A: prompt builder,
B: backend endpoint, C: popover skeleton, D: end-to-end wiring, E: polish).
A, B, C are independently scopable now; D and E are tentative anchors
to re-scope after A–C land.

Captured here so the next /ln-build session has a ready run-list with
acceptance criteria and promotion checklists per card.
Pure `buildSideChatPrompt(item, message, specContext)` returning the
`{ system, messages }` payload that biases the model to discuss the
pinned spec item without injecting interviewer phase-stage tone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kostandinang and others added 16 commits May 6, 2026 17:53
Wires `POST /api/specifications/:id/side-chat` as a thin I/O shell:
validates spec + (itemKind, itemId) lookup, builds the prompt via the
Card A pure function, and streams the Anthropic response as SSE.

The route never enters the chat-route transition or observer paths,
so the D113 zero-turns / zero-observer invariant holds structurally
without needing a new seam-level rule on top of D113.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Controlled popover surface with a pinned-item header, empty message
log, message input (auto-focused on mount), disabled-when-empty send
button, and a close button placed DOM-last so the input is the first
focusable element in tab order. Esc and click-outside both fire
onDismiss; Tab is trapped in both directions.

Inside-out skeleton only β€” graph view wiring (Card D) and persistence
+ anchoring (Card E) re-scope on top of this surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Activates the previously-disabled chat-with button on every item row in
the structured-list view (when specificationId is provided). Clicking
mounts SideChatPopover anchored to the row; typing and submitting a
message POSTs to /api/specifications/:id/side-chat; the SSE response
streams chunk-by-chunk into the popover's message log.

Inside-out:
- side-chat-stream.ts pure parser handles cross-chunk reassembly of
  the `data: {...}\n\n` ... `data: [DONE]` framing emitted by Card B.
- streamSideChatResponse async helper drives the fetch + ReadableStream
  reader chain, accepts an injected fetch for unit tests.
- SideChatPopover skeleton from Card C extended with messages,
  pendingAssistantText, and onSubmit props; Enter submits, Shift+Enter
  inserts a newline; the input clears after submit and the send button
  is disabled while a stream is in-flight.
- StructuredListView owns the active-popover state, threads onChatWith
  to ItemActionRail, and accumulates streamed deltas into pendingText.

Bumps the build-boundary test timeout from 30s to 60s β€” under suite
load with the new lib + component + test files, two back-to-back real
Vite builds were exceeding the 30s envelope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SideChatPopover dropped the parallel `pendingAssistantText: string | null`
prop; SideChatMessage gained optional `pending?: true`. The popover now
derives `isStreaming` from `messages.some(m => m.pending)` and renders
the pending row inline with the rest of the message list.

StructuredListView's orchestration composes the new shape: submit
appends a pending assistant message, deltas mutate its text via
`replacePendingText`, and completion drops the pending flag (or removes
empty pending messages) via `finalizePending`.

Eliminates the type-system-meaningless "messages ending in assistant
turn + non-null pendingAssistantText" combination noted in the V1.1
review. Behavior-preserving β€” same 687 tests pass, no semantic change.

First commit of the side-chat session boundary refactor; the second
commit will extract a SideChatHost provider so the activation gate
becomes a tree-mount fact rather than a derived optional prop.

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

The new SideChatHost component owns the active-session state, the
streaming side-effect, and the popover render; descendants read the
open callback via a useSideChat() hook backed by React context.

StructuredListView loses its specificationId prop, its activeSideChat
state, the submitSideChatMessage orchestration with its replacePendingText
and finalizePending helpers, and the inline popover mount. ItemActionRail
consults useSideChat() directly: present and non-null β†’ button is active;
absent or null β†’ disabled placeholder.

graph.tsx wraps the structured list in SideChatHost. The activation gate
is now a tree-mount fact rather than an optional-prop derivation chain β€”
the type system can no longer represent the previously-allowed almost-
active states.

Closes out the V1.1 review-driven refactor (findings #1, #2, #4).
Behavior-preserving β€” same 687 tests pass.

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

ln-sync:
- Drop completed Active items: workflow ownership extraction (FE-616 landed
  2026-04-29) and graph view structured-list (FE-643 landed 2026-04-30,
  closed by V1.1 chat-with activation).
- Add V1.1 / FE-643 / FE-616 to Recently Completed; archive older
  Distribution hardening + JSON payload entries to PLAN_HISTORY.
- Promote Side-chat V1 to the single Active frontier item (V1.1 done on
  branch, V1.2 Annotate + Card E polish remain).
- Update intro paragraph and Dependencies diagram.

ln-scope:
- Replace the V1.1 A/B/C/D queue (exhausted) with the V1.1 polish queue:
  E1 (lift SideChatHost to spec-level layout) Β· E2 (corner-anchored
  popover per design doc Β§11.5) Β· E3 (render side-chat errors). Each is
  a light scope card; row-anchoring explicitly deferred to V2/V3.

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

The host moves from graph.tsx up to route.tsx so an open side-chat
session survives in-spec navigation (e.g. graph β†’ grounding β†’ graph
round-trip). graph.tsx renders StructuredListView directly; the host
wraps the spec workspace's <Outlet /> instead.

Existing structured-list-view tests continue to pass β€” they wrap the
view in <SideChatHost> at the test boundary, so the move is invisible
to the test surface.

Matches design doc Β§2: "the panel persists for the spec session;
navigating within the spec preserves the panel and its thread."

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

E2 β€” anchor the popover as a fixed top-right corner panel per design
doc Β§11.5:
- fixed top-4 right-4 z-50 w-[360px], rounded-2xl, backdrop-blur,
  border-rule + ring-foreground/5 + shadow-xl
- message rows align user-right / assistant-left; close button
  absolute-positioned top-right inside the dialog
- data-side-chat-anchor="top-right" attribute for selection
- row-anchoring (popover follows the clicked row through scroll)
  explicitly deferred to V2/V3 per Β§11.5

E3 β€” render stream rejections as a visible error in the message log:
- SideChatMessage gains optional `error?: true`; popover renders
  error rows with bg-red-50 / text-red-900 / ring-red-200 and
  data-message-error="true"
- SideChatHost's catch block replaces the pending row with an
  error-flagged assistant message ("Something went wrong β€” try again.")
  via a new failPending helper, clears pending, re-enables sending

Combined into one commit because both touch SideChatPopover and the
error-row class branch reuses the shared className computation
introduced by the corner-anchor styling. CARDS.md queue exhausted β€”
deleting the file per ln-build retire-derivative-artifacts discipline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses Cursor + Augment review feedback on PR #81:

- SideChatHost: tag each session with an id, hold an AbortController
  ref, abort on openFor/dismiss/unmount, and gate stream callbacks on
  the captured sessionId so stale chunks can't corrupt a swapped
  pinned item. Async work moved out of the setActiveSideChat updater.
- streamSideChatResponse: throw when response.body is null instead of
  silently returning, so callers surface an error state.
- Server SSE: AbortController wired to res 'close', abortSignal
  threaded into streamText so model generation halts on client
  disconnect.
- side-chat-route: switch zod import to 'zod/v4' to match the rest
  of the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses Cursor review feedback on PR #81: the UI rendered a
multi-turn thread but each request only carried the latest message,
so the LLM had no memory of prior exchanges.

- Server schema accepts an optional history: { role, text }[] array;
  empty-text entries are rejected.
- buildSideChatPrompt threads prior turns into the messages array,
  with the pinned-item context still anchored on the first user turn
  and the new user message appended at the end.
- streamSideChatResponse forwards history to the route.
- SideChatHost collects finalized turns (pending + error placeholders
  filtered out) and sends them on every submit.

Tests cover the new shape end-to-end: prompt builder, route schema +
forwarding, streaming-helper body, and a multi-turn scenario in the
SideChatHost integration test (including error-retry hygiene).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Kostandin Angjellari <kostandinang@users.noreply.github.com>
Co-authored-by: Kostandin Angjellari <kostandinang@users.noreply.github.com>
The previous merge resolution put provider-setup decisions at D132-D135, but
PR #88 (ka/fe-656-side-chat-v1-2, stacked above this branch) already uses
D132 for the PatchListProvider module and D133 for the new annotation entity
with side-chat semantics. Shift provider items to D134-D137 so PR #88's
existing numbering can ride through the rebase without a new conflict.

- D134..D137 = first-run setup / provider seam / UI credentials / gitignore
- A74-A76 dependency refs updated
- I106/I107 traceability refs updated
- PLAN.md horizon traceability refs updated

Verified: npm run verify (726 tests pass, build clean).
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@kostandinang kostandinang force-pushed the ka/fe-656-side-chat branch from 6133314 to 9a94392 Compare May 6, 2026 16:27
@github-actions github-actions Bot dismissed lunelson’s stale review May 6, 2026 16:28

Latest approval commit 6133314 is not an ancestor of 9a94392, indicating rewritten history after approval

@kostandinang kostandinang requested a review from lunelson May 6, 2026 16:29
@graphite-app graphite-app Bot merged commit a2b75a6 into main May 7, 2026
6 checks passed
@graphite-app graphite-app Bot deleted the ka/fe-656-side-chat branch May 7, 2026 08:16
lunelson pushed a commit that referenced this pull request May 7, 2026
…ity, queue cards

Kicks off V1.2 (Side-chat Annotate vertical slice) on a stacked branch over
V1.1's PR #81. The /ln-design synthesis settled the patch-list module shape
and the annotation entity model; /ln-scope queued the three vertical-slice
cards (server seam, client module, end-to-end wiring) in CARDS.md.

memory/SPEC.md
- Add D132: patch-list module is React-context-native publicly, event-log-
  shaped internally. Provider + 3 hooks; closed discriminated union for
  patch kinds; useReducer over PatchEvent log; appliers prop forces V2
  kinds to typecheck-fail at mount until supplied. Internal events shaped
  to match A71's eventual server-side primitive β€” migration is reducer
  swap, not API rewrite.
- Add D133: annotation is a new durable entity with its own table. Item-
  anchored in V1; `selection_start`/`selection_end` columns are part of
  the schema from day one but stay NULL until V2/V3 lights up span anchors.
  Supersedes the optimistic "comment-store extension" framing β€” the spike
  showed there is no prior comment store; per-turn `itemComments` on
  review responses is a separate seam and stays unchanged.

memory/PLAN.md
- Update Active 1 to reflect V1.2 in progress on `ka/fe-656-side-chat-v1-2`,
  reference the CARDS.md vertical-slice queue, and correct the "comment-
  store extension" framing per D133.
- Add D132 / D133 to traceability; record both branches under Linear FE-656.

memory/CARDS.md (new)
- Card A: annotation server seam β€” drizzle migration, table with FK +
  cascade, POST/GET/DELETE endpoints, server-only tests.
- Card B: PatchListProvider client module β€” sibling to SideChatHost, hooks
  for mutations + reactive reads + filtered selectors, internal reducer
  + pure fold tests.
- Card C: end-to-end annotate wiring β€” annotate composer in side-chat
  header, in-panel inline patch list, in-panel Apply hits the new endpoint,
  Undo round-trips. Top-bar canonical UI, floating selection menu, and
  multi-pin re-scope after Card C lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
https://www.loom.com/share/b0d96bc2f0a849d8b57b043bb648a9aa

## Stack Context

This PR is the side-chat **V1.2 vertical slice** under FE-656, stacked on **PR #81** (V1.1 β€” Class 1 Explore: chat anchored to spec items). V1.1 proved the chat seam; **V1.2 proves the durable mutation seam** β€” the side-chat can now produce real, persistent changes to a spec.

PR #88 is sized as one coherent slice. **V1.2-D** (top-bar canonical Apply/Undo), **V1.2-E** (floating selection menu), and **V1.2-F** (multi-item pinning) are deferred to follow-up PRs per `memory/PLAN.md`.

## What

End-to-end Annotate flow: the user opens the side-chat on a knowledge item, types a note, hits Save, and it persists to the database. Existing notes for the pinned item render above the chat log as a collapsible `Notes` section.

The slice lands as three cards plus follow-on UX work:

- **A** β€” Annotation server seam: new `annotation` table + REST endpoints.
- **B** β€” `PatchListProvider` client module: React provider + hooks for staging and applying patches.
- **C** β€” End-to-end wiring: side-chat composer talks to the patch list and the server.
- **Auto-apply + UX coherence** β€” user-typed annotations save on submit (no extra Apply click); three clear states for in-flight / saved / failed.
- **C2 β€” Show existing annotations** β€” collapsible `Notes` section above the chat log.

## Why

V1.1 vertically proved the chat seam end-to-end (graph β†’ side-chat β†’ SSE β†’ streaming reply). Without V1.2, the side-chat is read-only β€” Class 4 Annotate, V2 Edit / Drill-down / Propose-edge, and V4 architect-loop emissions all need somewhere to write. V1.2 establishes that surface.

Two design choices worth attention before merge:

### D132 β€” Patch-list module shape

`/ln-design` explored four shapes in parallel (minimal-API, plug-in registry, event-log-public, react-context-native). The synthesis takes the React-native public surface (mirrors `SideChatHost` exactly) but uses an event-log internal primitive. Rationale:

- **Idiomatic for this codebase** β€” Zero contributor-onboarding cost.
- **A71-ready internally** β€” When the future `appendPatch(spec, patch[])` server primitive lands, migration is "swap the reducer," not "rewrite the public API."
- **Closed discriminated union** β€” V2 patch kinds force a typecheck failure at the provider mount until their applier is supplied. Silent drift impossible.

### D131 β€” User-driven annotations auto-apply

Originally framed as "uniform staging required for all patch kinds." User feedback during this PR pushed back: re-reviewing what you just typed adds nothing for annotations. Distinction is now explicit in D131:

- **Annotate** (user-driven, low-stakes, just-typed) β†’ auto-apply on Save. The patch list still records the patch, surfaces it transiently, and provides Undo on the resulting batch.
- **Edit / Drill-down / Propose-edge** (V2, mutates durable content) β†’ keep the explicit Apply step. Review-before-commit matters when the operation has cascading effects.
- **Architect-loop emissions** (V4, system-driven) β†’ keep the explicit Apply step. The user wasn't in the moment of authoring; review is the whole point.

The patch-list module supports both paths β€” kind-specific behavior is wired in `SideChatHost` via the auto-apply `useEffect`, not in the module itself.

## Spec / plan deltas

`memory/SPEC.md`:
- **D132** β€” patch-list module shape (React-context-native public, event-log-shaped internally)
- **D133** β€” annotation as a new durable entity, item-anchored in V1, span-anchor-ready in schema
- **D131** revised β€” adds the user-driven vs review-required distinction; supersedes the "uniform staging required" framing

`memory/PLAN.md`:
- Active 1 reflects V1.2 vertical slice landed; V1.2-D / E / F still owed.
- Earlier "comment-store extension" framing corrected β€” D133 supersedes it.

## Stacked on

- **PR #81** (`ka/fe-656-side-chat`, V1.1 Explore) β€” must merge first.
- **PR #82** (`ka/fe-656-side-chat_v4`, V4 patch / event-stream data model) β€” sibling on the same parent, independent of V1.2.

## Linear

[FE-656](https://linear.app/hashintel/issue/FE-656) β€” same issue as V1.1 (one Linear issue per frontier item per `CLAUDE.md`).

## Test plan

**Automated** β€” `npm run verify` passes (793/793 tests, includes the 9 V1.1-polish tests merged in).

**Manual walkthrough** (in browser, dev server running):

- [ ] **Happy path.** Open a spec β†’ graph view β†’ click `πŸ’¬` on any item row β†’ click `Annotate` β†’ type Summary + Body β†’ click `Save`. Expect: brief `Saving annotation…`, then `βœ“ Annotation saved` with Undo. New note appears in the `Notes (1)` section above the chat log.
- [ ] **Persistence.** Reload the page. Re-open chat on the same item. Note still there.
- [ ] **Multiple annotations.** Save a second note on the same item. `Notes (2)` lists both, each independently expandable.
- [ ] **Cross-item isolation.** Save a note on item A, switch chat to item B. B's panel does not show A's notes.
- [ ] **Undo.** Save a note β†’ click Undo. Notes section refetches; the just-undone note is gone.
- [ ] **Composer validation.** `Save` disabled until both Summary and Body are non-empty (whitespace-only doesn't count).
- [ ] **Cancel.** `Esc` or `Cancel` button in composer returns to chat without saving.
- [ ] **Streaming exclusivity.** While a chat reply is streaming, the `Annotate` button is disabled.
- [ ] **Notes collapse.** Click the `Notes (N) β€Ί` chevron to collapse the entire section. Click individual rows to expand bodies.
- [ ] **Failure path** *(stop dev server mid-save).* Stuck-staged panel appears with `Retry` and `Γ— Discard`. Restart server β†’ `Retry` succeeds.

**Server sanity:**
```bash
curl -s http://localhost:3000/api/specifications/<id>/annotations | jq
npm run studio  # query the `annotation` table directly
```

## Known transitional gaps (deferred to follow-ups)

- **Top-bar canonical surface** (V1.2-D). Until this lands, in-panel Undo is the only undo affordance. Per D131 the in-panel list is "convenience UI, not source of truth"; V1.2-D moves canonical Undo to the persistent app top-bar.
- **Span-anchored annotations** (V2/V3). Schema reserves `selection_start` / `selection_end` columns; V1 leaves them NULL.
- **Annotation deletion UI** beyond Undo. Broader management lands in a later card.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)
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.

3 participants