Skip to content

FE-553: Multi-project routing#16

Merged
lunelson merged 10 commits into
mainfrom
ln/fe-553-multi-project-routing
Apr 2, 2026
Merged

FE-553: Multi-project routing#16
lunelson merged 10 commits into
mainfrom
ln/fe-553-multi-project-routing

Conversation

@lunelson
Copy link
Copy Markdown
Contributor

@lunelson lunelson commented Apr 1, 2026

feat: add multi-project routing with TanStack Router (FE-553)

Add project-scoped API routes (GET/POST /api/projects, GET /api/projects/:id,
POST /api/projects/:id/chat), TanStack Router with three client routes
(project list, interview workspace, export preview placeholder), and new
db/core functions for project listing and creation.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

refactor: port tool-call rendering to InterviewWorkspace, delete dead App.tsx

InterviewWorkspace was missing dynamic-tool part rendering that 3b added to
App.tsx (merge gap from parallel builds). App.tsx is now dead code — main.tsx
uses RouterProvider. Also removes unused getOrCreateProject import from core.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

docs: traceability updates for slices 3b and 3d

Mark 3b (FE-541) and 3d (FE-553) done in PLAN.md. Update SPEC.md:
A16 partially validated, invariants I14/I15 established, coverage
table updated to 72 tests across 4 files.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

chore: replace eslint+tsc with oxlint+oxfmt+tsgolint verification harness

Install oxlint (lint + type-aware + type-check via tsgolint), oxfmt
(formatting: single quotes, 110 width, sorted imports). Remove eslint.
Add npm scripts: lint, lint:fix, fmt, fmt:check, verify (full pipeline).
Update SPEC.md verification commands. Fix all lint warnings (unused vars,
floating promises, await-thenable).

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

chore: add fix/check/verify scripts, document inner loop in CLAUDE.md

npm run fix (lint:fix + fmt) is the fast inner loop — auto-fixes and
formats. npm run verify (check + test + build) is the commit gate.
CLAUDE.md documents the two-tier workflow. SPEC.md verification
commands updated to match.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@linear
Copy link
Copy Markdown

linear Bot commented Apr 1, 2026

FE-553 Multi-project routing

Install tanstack/react-router. Three client routes: project list (/), interview workspace (/project/:id), export preview (/project/:id/export). Route loaders replace useEffect hydration. Server API becomes project-scoped (/api/projects/:id/...). Project list page with phase badges.

Part of slice 3d in PLAN.md. Depends on slice 3c (Drizzle + core extraction).

Copy link
Copy Markdown
Contributor Author

lunelson commented Apr 1, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@lunelson lunelson changed the title feat: add multi-project routing with TanStack Router (FE-553) FE-553: Multi-project routing Apr 1, 2026
@lunelson lunelson marked this pull request as ready for review April 1, 2026 15:29
@lunelson lunelson self-assigned this Apr 1, 2026
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 1, 2026

🤖 Augment PR Summary

Summary: Adds multi-project support by introducing project-scoped routing and API endpoints, so each interview session is tied to a specific project and can be revisited via a stable URL.

Changes:

  • Install and wire TanStack React Router with routes for project list (/), interview workspace (/project/:id), and export preview placeholder (/project/:id/export).
  • Move initial client hydration into route loaders that fetch /api/projects and /api/projects/:id.
  • Add/adjust Express endpoints for listing/creating projects and streaming chat per project (POST /api/projects/:id/chat via SSE).
  • Extend server core/DB helpers for listing projects, creating projects, and retrieving a project’s active-path turns.
  • Port tool-call rendering into InterviewWorkspace and remove now-dead legacy app wiring.
  • Update docs and verification scripts to reflect the new lint/format/check pipeline.

Technical Notes: Chat streaming is translated into the AI SDK UI message stream protocol via an SSE adapter that emits text, reasoning, and tool-call events.

🤖 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. 5 suggestions posted.

Fix All in Augment

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

const isLoading = status === 'submitted' || status === 'streaming';

// Hydrate turns into useChat messages on mount
useEffect(() => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

useEffect only calls setMessages when turns?.length > 0, so navigating from a project with turns to a project with zero turns can leave the previous project’s messages visible. Also, the effect depends only on project.id, so if turns changes for the same project (e.g. loader revalidation) the chat hydration won’t rerun.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/app.ts Outdated
res.status(400).json({ error: 'name is required' });
return;
}
const project = createNewProject(db, name.trim());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The project name validation happens before trimming, so a request like { "name": " " } will pass the !name check and create a project with an empty name after trim(). That can lead to blank entries in the project list.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/app.ts

const project = getProjectState(db).project;
const prompt = extractPrompt(req.body.messages ?? []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If extractPrompt(req.body.messages ?? []) returns an empty string (e.g., empty messages or no text parts), this route still calls conductTurn and persists a turn with an empty answer. Consider rejecting empty prompts to avoid creating “empty” turns in the active path.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/core.ts
assistantText += delta.text;
yield { type: 'text-delta', delta: delta.text };
} else if (delta.type === 'input_json_delta' && delta.partial_json) {
const toolBlock = toolUseBlocks.get(event.index!);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When handling input_json_delta, if toolUseBlocks.get(event.index!) is missing, this code still yields a tool-call-delta event with toolCallId: ''. Emitting tool-call events with an empty id can break client/tool-stream state machines, so it may be safer to drop or explicitly handle the missing-tool-block case.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/core.ts Outdated
if (assistantText) {
updateTurn(db, turn.id, { question: assistantText });
}
advanceHead(db, projectId, turn.id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

advanceHead(db, projectId, turn.id) runs even if the streaming/query path throws and no assistant question was persisted, which can advance the project’s HEAD to a turn with an empty question. If errors are transient, this can leave the active path/UI in a confusing state where the latest turn has no assistant content.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@lunelson lunelson force-pushed the ln/fe-541-rich-chat-ui branch from 5cd25b7 to edd3dc7 Compare April 1, 2026 15:58
@lunelson lunelson force-pushed the ln/fe-553-multi-project-routing branch from 231bff6 to 1e4e403 Compare April 1, 2026 15:58
Copy link
Copy Markdown
Contributor Author

lunelson commented Apr 2, 2026

Merge activity

  • Apr 2, 6:20 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 2, 6:29 AM UTC: Graphite rebased this pull request as part of a merge.
  • Apr 2, 6:30 AM UTC: @lunelson merged this pull request with Graphite.

@lunelson lunelson changed the base branch from ln/fe-541-rich-chat-ui to graphite-base/16 April 2, 2026 06:27
@lunelson lunelson changed the base branch from graphite-base/16 to main April 2, 2026 06:28
lunelson and others added 10 commits April 2, 2026 06:29
Add project-scoped API routes (GET/POST /api/projects, GET /api/projects/:id,
POST /api/projects/:id/chat), TanStack Router with three client routes
(project list, interview workspace, export preview placeholder), and new
db/core functions for project listing and creation.

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

InterviewWorkspace was missing dynamic-tool part rendering that 3b added to
App.tsx (merge gap from parallel builds). App.tsx is now dead code — main.tsx
uses RouterProvider. Also removes unused getOrCreateProject import from core.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mark 3b (FE-541) and 3d (FE-553) done in PLAN.md. Update SPEC.md:
A16 partially validated, invariants I14/I15 established, coverage
table updated to 72 tests across 4 files.

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

Install oxlint (lint + type-aware + type-check via tsgolint), oxfmt
(formatting: single quotes, 110 width, sorted imports). Remove eslint.
Add npm scripts: lint, lint:fix, fmt, fmt:check, verify (full pipeline).
Update SPEC.md verification commands. Fix all lint warnings (unused vars,
floating promises, await-thenable).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
npm run fix (lint:fix + fmt) is the fast inner loop — auto-fixes and
formats. npm run verify (check + test + build) is the commit gate.
CLAUDE.md documents the two-tier workflow. SPEC.md verification
commands updated to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SPEC.md: update D8 (Express is now thin adapter, not SDK iterator),
D14 (AI Elements deferred, hand-built rendering), D19 (full DomainEvent
list: 9 types implemented + 2 future), lexicon domain event entry,
add D21 (oxlint+oxfmt tooling decision). PLAN.md: update parallelism
section (3b/3d done, spike+4 now parallel).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port arc-oracle's diagnostic framework and oracle taxonomy into a
self-contained ln-oracles skill. Update SPEC.md with three-tier oracle
strategy, diagnostic assessment, observer history projection design,
and acknowledged blind spots. Annotate PLAN.md slices 4/5/6 and
observer spike with verification approaches. Sharpen ln-spec (delegates
middle/outer loop to ln-oracles), ln-scope (requires verification
approach per slice), ln-build (oracle strategy informs test design),
and ln-review (audits oracle coverage).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Delete pre-v2 code and config superseded by src/ over slices 1-3d:
- server/ (Dolt/mysql2 JS server) → src/server/ (Express + SQLite + Drizzle)
- client/ (Preact/React UI) → src/client/ (React + TanStack Router)
- config.yaml (Dolt server config), opencode.json (OpenCode MCP sidecar)
- test-opencode.js, test-sse-raw.js (manual test scripts)
- scripts/kill-port.js (unused utility)
- docs/design/schema.dbml, schema.dbdiagram (superseded by drizzle/schema.ts)
- @types/cors devDependency (cors not imported in src/)
- HANDOFF.md (session state captured in memory)

Update SPEC.md references to removed schema.dbml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Slim CLAUDE.md to always-loaded essentials, extract operational
protocols into docs/praxis/:
- graphite-workflow.md: gt-vs-git boundary, branch lifecycle,
  parallel work reintegration
- worktree-agents.md: isolation protocol, known limitations,
  merge gap risk
- manual-testing.md: outer-loop verification, fixture capture

Add explicit git-vs-gt rule to CLAUDE.md. Remove inline manual
testing section (now in praxis).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Trim project name before empty check to reject whitespace-only names
- Reject empty prompt in chat endpoint instead of creating empty turns
- Always call setMessages on project change (including for 0 turns)
  and add turns to dependency array for revalidation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lunelson lunelson force-pushed the ln/fe-553-multi-project-routing branch from 1e4e403 to 8bdf9df Compare April 2, 2026 06:29
@lunelson lunelson merged commit 246126c into main Apr 2, 2026
2 checks passed
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.

1 participant