Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
468f047
FE-710: Settle thread substrate as option (q) in SPEC/PLAN
kostandinang May 14, 2026
9cae13d
FE-710: Land thread substrate — migration, schema, store, tests
kostandinang May 14, 2026
bafc0a9
FE-710: Inline thread collapsible scaffold — server helpers, stream i…
kostandinang May 14, 2026
5f787b6
FE-710: Fix review findings — thread ownership, invariant enforcement…
kostandinang May 14, 2026
4922be7
FE-710: Consolidate interview-thread lookup, scope turn-count query
kostandinang May 14, 2026
cd48b49
FE-710: Add unified chat UX design brief
kostandinang May 14, 2026
968bb94
FE-710: Refactor ThreadCollapsible to brief tone — icons, mode labels…
kostandinang May 14, 2026
f6a4fb6
FE-710: ThreadCollapsible inline input + live SSE streaming for side-…
kostandinang May 14, 2026
622da31
FE-710: Route 'Chat' action to inline ThreadCollapsible when thread e…
kostandinang May 14, 2026
9e65a19
FE-710: Add POST /api/specifications/:id/threads endpoint
kostandinang May 14, 2026
43406e1
FE-710: Update PLAN execution pointer after slices 5-7
kostandinang May 14, 2026
9fbc16e
FE-710: ThreadCollapsible edit mode + patch staging
kostandinang May 14, 2026
d28053f
FE-710: Eager thread creation cutover — openFor always routes inline
kostandinang May 14, 2026
4081d33
FE-710: Update PLAN execution pointer after slices 8-9
kostandinang May 14, 2026
eb5a388
FE-710: Delete SideChatPopover and clean up dead code
kostandinang May 14, 2026
a310a4e
FE-710: Update PLAN — thread substrate + popover cutover arc is complete
kostandinang May 14, 2026
1a53c5e
FE-710: Turn-zero kickoff for side-chat threads
kostandinang May 14, 2026
4a56f44
FE-710: All acceptance criteria met — frontier ready to close
kostandinang May 14, 2026
2b83112
FE-710: Close frontier — update acceptance wording, move to Recently …
kostandinang May 14, 2026
6ccd07f
FE-710: Fix chat-from-item entry bridge regression
kostandinang May 14, 2026
5a9ef97
FE-710: Fix free-floating thread rendering, remove floating panel
kostandinang May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions docs/design/UNIFIED_CHAT_UX.md

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions drizzle/0020_thread_substrate.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- 1. Create thread table
CREATE TABLE `thread` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`chat_id` integer NOT NULL,
`kind` text NOT NULL,
`target_item_id` integer,
`context_spec` text,
`kickoff_turn_id` integer,
`invoked_in_turn_id` integer,
`active_turn_id` integer,
`status` text DEFAULT 'open' NOT NULL,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
FOREIGN KEY (`chat_id`) REFERENCES `chat`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`target_item_id`) REFERENCES `knowledge_item`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`kickoff_turn_id`) REFERENCES `turn`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`invoked_in_turn_id`) REFERENCES `turn`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`active_turn_id`) REFERENCES `turn`(`id`) ON UPDATE no action ON DELETE no action
);--> statement-breakpoint

-- 2. Partial unique index: exactly one interview thread per chat
CREATE UNIQUE INDEX `thread_interview_unique` ON `thread` (`chat_id`) WHERE kind = 'interview';--> statement-breakpoint

-- 3. Seed one interview thread per existing chat
INSERT INTO `thread` (`chat_id`, `kind`, `active_turn_id`)
SELECT `id`, 'interview', `active_turn_id` FROM `chat`;--> statement-breakpoint

-- 4. Recreate turn with thread_id instead of chat_id
CREATE TABLE `turn_new` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`specification_id` integer NOT NULL,
`thread_id` integer NOT NULL,
`parent_turn_id` integer,
`phase` text NOT NULL,
`turn_kind` text DEFAULT 'question' NOT NULL,
`question` text DEFAULT '' NOT NULL,
`why` text,
`impact` text,
`answer` text,
`is_resolution` integer DEFAULT false NOT NULL,
`user_parts` text,
`assistant_parts` text,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
FOREIGN KEY (`specification_id`) REFERENCES `specification`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`thread_id`) REFERENCES `thread`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`parent_turn_id`) REFERENCES `turn_new`(`id`) ON UPDATE no action ON DELETE no action
);--> statement-breakpoint
INSERT INTO `turn_new` (`id`, `specification_id`, `thread_id`, `parent_turn_id`, `phase`, `turn_kind`, `question`, `why`, `impact`, `answer`, `is_resolution`, `user_parts`, `assistant_parts`, `created_at`)
SELECT t.`id`, t.`specification_id`, th.`id`, t.`parent_turn_id`, t.`phase`, t.`turn_kind`, t.`question`, t.`why`, t.`impact`, t.`answer`, t.`is_resolution`, t.`user_parts`, t.`assistant_parts`, t.`created_at`
FROM `turn` t
JOIN `thread` th ON th.`chat_id` = t.`chat_id` AND th.`kind` = 'interview';--> statement-breakpoint
DROP TABLE `turn`;--> statement-breakpoint
ALTER TABLE `turn_new` RENAME TO `turn`;--> statement-breakpoint

-- 5. Recreate chat without kind and active_turn_id
CREATE TABLE `chat_new` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`specification_id` integer NOT NULL,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
FOREIGN KEY (`specification_id`) REFERENCES `specification`(`id`) ON UPDATE no action ON DELETE no action
);--> statement-breakpoint
INSERT INTO `chat_new` (`id`, `specification_id`, `created_at`)
SELECT `id`, `specification_id`, `created_at` FROM `chat`;--> statement-breakpoint
DROP TABLE `chat`;--> statement-breakpoint
ALTER TABLE `chat_new` RENAME TO `chat`;
27 changes: 27 additions & 0 deletions drizzle/0021_turn_phase_nullable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- Make turn.phase nullable so non-interview thread kinds (side, reconciliation,
-- qa, agent_run) can persist turns without an interview phase.

CREATE TABLE `turn_new` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`specification_id` integer NOT NULL,
`thread_id` integer NOT NULL,
`parent_turn_id` integer,
`phase` text,
`turn_kind` text DEFAULT 'question' NOT NULL,
`question` text DEFAULT '' NOT NULL,
`why` text,
`impact` text,
`answer` text,
`is_resolution` integer DEFAULT false NOT NULL,
`user_parts` text,
`assistant_parts` text,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
FOREIGN KEY (`specification_id`) REFERENCES `specification`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`thread_id`) REFERENCES `thread`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`parent_turn_id`) REFERENCES `turn_new`(`id`) ON UPDATE no action ON DELETE no action
);--> statement-breakpoint
INSERT INTO `turn_new` (`id`, `specification_id`, `thread_id`, `parent_turn_id`, `phase`, `turn_kind`, `question`, `why`, `impact`, `answer`, `is_resolution`, `user_parts`, `assistant_parts`, `created_at`)
SELECT `id`, `specification_id`, `thread_id`, `parent_turn_id`, `phase`, `turn_kind`, `question`, `why`, `impact`, `answer`, `is_resolution`, `user_parts`, `assistant_parts`, `created_at`
FROM `turn`;--> statement-breakpoint
DROP TABLE `turn`;--> statement-breakpoint
ALTER TABLE `turn_new` RENAME TO `turn`;
14 changes: 14 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@
"when": 1776360000000,
"tag": "0019_reconciliation_need_agent_columns",
"breakpoints": true
},
{
"idx": 20,
"version": "7",
"when": 1776370000000,
"tag": "0020_thread_substrate",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1776380000000,
"tag": "0021_turn_phase_nullable",
"breakpoints": true
}
]
}
27 changes: 14 additions & 13 deletions memory/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,12 @@ The May 2026 intent-spec, multi-chat, changeset-ledger, prompt/context, and agen

### Next

1. `chat-runtime-threads` — Track 2 of the runtime umbrella; immediate successor to continuous-workspace, unblocker for Tracks 3 and 5. First slice should be a sub-RFC on the thread substrate shape (p / q / r).
2. `intent-graph-semantics` — highest-coordination semantic substrate after FE-705 reconciliation.
3. `changeset-ledger` — Track 4 of the runtime umbrella; parallel with Track 2; semantic history spine needed before canonical proposal acceptance, direct-edit atomicity, and productized scenario options.
4. `thread-context-provision` — Track 5 of the runtime umbrella; after Track 2 lands the thread substrate.
5. `reconciliation-runtime` — Track 3 of the runtime umbrella; after Track 2 + Track 4 provide thread substrate and durable attribution.
6. `graph-review-scenario-options` — artifact-only critique/probe lane; can advance in parallel with FE-700 if it does not commit canonical graph truth.
7. `productized-scenario-options` — user-facing acceleration surface after FE-700 semantics, FE-701 changesets, and graph-review probes.
1. `intent-graph-semantics` — highest-coordination semantic substrate after FE-705 reconciliation.
2. `changeset-ledger` — Track 4 of the runtime umbrella; parallel with Track 2; semantic history spine needed before canonical proposal acceptance, direct-edit atomicity, and productized scenario options.
3. `thread-context-provision` — Track 5 of the runtime umbrella; after Track 2 lands the thread substrate.
4. `reconciliation-runtime` — Track 3 of the runtime umbrella; after Track 2 + Track 4 provide thread substrate and durable attribution.
5. `graph-review-scenario-options` — artifact-only critique/probe lane; can advance in parallel with FE-700 if it does not commit canonical graph truth.
6. `productized-scenario-options` — user-facing acceleration surface after FE-700 semantics, FE-701 changesets, and graph-review probes.

### Parallel / Low-conflict

Expand Down Expand Up @@ -74,15 +73,16 @@ The May 2026 intent-spec, multi-chat, changeset-ledger, prompt/context, and agen
### chat-runtime-threads

- **Name:** Chat runtime — thread substrate + in-stream rendering (Conversational Workspace Runtime — Track 2)
- **Linear:** unassigned in this plan snapshot
- **Linear:** FE-710
- **Kind:** structural
- **Status:** not-started
- **Objective:** Add a thread primitive to the chat substrate, render threads inline as collapsibles in the main chat surface (Cursor-style), and retire the SideChatPopover and transient staged-patches strip. Decide the thread substrate shape via a sub-RFC: (p) `parent_chat_id` on `chat`, (q) new `thread` table, or (r) UI-only rendering.
- **Why now / unlocks:** Track 1 (workspace shell) ships, providing the stable host. Threads are the critical unblocker for reconciliation absorption into the chat surface (Track 3), `#` mention / turn-zero / context provision (Track 5), and the retirement of the V3.1 popover and staged-patches surfaces. Supersedes the prior side-chat V4a persistence horizon — persistent side-chat history becomes the main chat stream where threads stay collapsed.
- **Acceptance:** Thread kinds (`interview`, `side`, `reconciliation`, `qa`) are representable in the substrate; threads render inline as collapsibles in the unified chat surface; SideChatPopover retires as cutover; transient staged-patches strip retires (replaced by in-thread mutation state); turn-zero (`turn_kind='kickoff'`) becomes the universal thread entry.
- **Status:** done
- **Objective:** Add a `thread` primitive between chat and turn, render threads inline as collapsibles in the main chat surface (Cursor-style), and retire `SideChatPopover`. Substrate sub-RFC settled: option (q) new `thread` table; chat collapses to a pure container; flat threads with `thread.invoked_in_turn_id` for inline agent runs (no nested threads in V1).
- **Why now / unlocks:** Track 1 (workspace shell) shipped, providing the stable host. Threads are the critical unblocker for reconciliation absorption into the chat surface (Track 3), `#` mention / turn-zero / context provision (Track 5), changeset attribution (Track 4), and the retirement of the V3.1 popover and staged-patches surfaces. Supersedes the prior side-chat V4a persistence horizon — persistent side-chat history becomes the main chat stream where threads stay collapsed.
- **Acceptance:** `thread` table exists with kinds (`interview`, `side`, `reconciliation`, `qa`, `agent_run`); `turn.thread_id` replaces `turn.chat_id`; `chat.kind` and `chat.active_turn_id` retire; threads render inline as collapsibles in the unified chat surface; `SideChatPopover` retires as cutover; each thread carries its own in-thread mutation state (edit mode + patch staging via ThreadCollapsible) — the global `PatchListOverlay` strip remains as a deliberate cross-thread summary surface; turn-zero (`turn_kind='kickoff'`) becomes the universal thread entry; agent runs render inline via `thread.invoked_in_turn_id`. `PendingReviewSection` retirement deferred to `reconciliation-runtime` (Track 3).
- **Verification:** Thread substrate schema/migration tests, in-stream collapsible rendering tests, manual walkthroughs for thread creation/display/collapse per kind, regression on existing interview flow.
- **Traceability:** A82, A83, A88; D86, D87, D110, D114, D138, D146; I111, I113.
- **Traceability:** Requirements 39 (updated), 45; A88, A94; D86, D87, D110, D114, D138, D146, D153, D154; I111 (extended), I113.
- **Design docs:** `docs/design/CONVERSATIONAL_WORKSPACE_RUNTIME.md` §3.2 + §5 Track 2; `docs/design/MULTI_CHAT.md`; `docs/design/SIDE_CHAT.md`.
- **Current execution pointer:** all acceptance criteria met (slices 1–11). Thread substrate, inline streaming + edit mode, popover retirement + dead-code deletion (-3200 LOC), turn-zero kickoff, agent runs inline. Frontier ready to close.

### reconciliation-runtime

Expand Down Expand Up @@ -387,6 +387,7 @@ The May 2026 intent-spec, multi-chat, changeset-ledger, prompt/context, and agen

## Recently Completed

- [2026-05-15] `chat-runtime-threads` — Done: FE-710 / PR #138. Thread substrate (new `thread` table, `turn.thread_id`, chat simplified to pure container), inline ThreadCollapsible with streaming + edit mode + patch staging, SideChatPopover fully retired and deleted (-3200 LOC), turn-zero kickoff, agent runs inline via `invoked_in_turn_id`. Global `PatchListOverlay` strip remains as deliberate cross-thread summary. `PendingReviewSection` retirement deferred to `reconciliation-runtime` (Track 3). Verified: `npm run verify` 1157 pass / 15 skipped (retirement debt). Watch: 15 skipped tests cover retired popover bridge/undo modules and annotate auto-apply — delete when replacement coverage exists.
- [2026-05-13] `continuous-workspace` — Done: FE-709 / PR #134. Replaced per-phase InterviewView with ContinuousWorkspaceView (cumulative center pane), extracted `useContinuousWorkspaceController`, added sidebar scroll-spy via WorkspaceFocusContext, extracted shared controller helpers to core, retired route-first test assumptions. Verified: `npm run verify` 1213 / 1214 pass (1 pre-existing flake). Watch: Step 5 route-collapse decision deferred — hybrid works as intended.
- [2026-05-11] `side-chat-v3-1-agent-grouped-reconciliation` — Done: FE-674 / PR #124 + downstack closed the V3.x arc end-to-end with spec-level classifier route, per-row reset route, agent classification lifecycle, chips, per-class actions, and bulk Confirm-all / Apply-all-suggested. Verified: `npm run verify` 1178 / 1179 pass with one unrelated `side-chat-route` flake. Watch: A88 outer-loop walkthrough on a dense spec remains open to assess legibility vs V3.0's flat list.
- [2026-05-11] `fe-698-reconciliation-context-pack` — Done: added proposal-only reconciliation prompt/context scenario rendering open reconciliation needs with source/target anchors, reason/status, prompt/context fingerprints, and read-only capability metadata. Verified: `npm run verify`. Watch: next FE-698 work can broaden read-only/proposal-only probes and Pi adapter spike without treating this pack as a resolution agent.
Expand Down
Loading
Loading