Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
73081ce
FE-710: Settle thread substrate as option (q) in SPEC/PLAN
kostandinang May 14, 2026
4d45803
FE-710: Land thread substrate — migration, schema, store, tests
kostandinang May 14, 2026
58c8f6f
FE-710: Inline thread collapsible scaffold — server helpers, stream i…
kostandinang May 14, 2026
28e4825
FE-710: Fix review findings — thread ownership, invariant enforcement…
kostandinang May 14, 2026
41d1742
FE-710: Consolidate interview-thread lookup, scope turn-count query
kostandinang May 14, 2026
65e86a2
FE-710: Add unified chat UX design brief
kostandinang May 14, 2026
e84fa50
FE-710: Refactor ThreadCollapsible to brief tone — icons, mode labels…
kostandinang May 14, 2026
e238992
FE-710: ThreadCollapsible inline input + live SSE streaming for side-…
kostandinang May 14, 2026
8158027
FE-710: Route 'Chat' action to inline ThreadCollapsible when thread e…
kostandinang May 14, 2026
ef863f6
FE-710: Add POST /api/specifications/:id/threads endpoint
kostandinang May 14, 2026
3b1164a
FE-710: Update PLAN execution pointer after slices 5-7
kostandinang May 14, 2026
44f9a6c
FE-710: ThreadCollapsible edit mode + patch staging
kostandinang May 14, 2026
4dfe732
FE-710: Eager thread creation cutover — openFor always routes inline
kostandinang May 14, 2026
d133469
FE-710: Update PLAN execution pointer after slices 8-9
kostandinang May 14, 2026
c4dd4c4
FE-710: Delete SideChatPopover and clean up dead code
kostandinang May 14, 2026
82f425e
FE-710: Update PLAN — thread substrate + popover cutover arc is complete
kostandinang May 14, 2026
6ca8806
FE-710: Turn-zero kickoff for side-chat threads
kostandinang May 14, 2026
2fad286
FE-710: All acceptance criteria met — frontier ready to close
kostandinang May 14, 2026
b3ab28e
FE-710: Close frontier — update acceptance wording, move to Recently …
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
46 changes: 0 additions & 46 deletions HANDOFF.md

This file was deleted.

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