Skip to content

Migrate frontend from Vite+Hono to Next.js App Router#458

Merged
rjroy merged 27 commits intomainfrom
feat/refactor-next-js-8npjk
Feb 6, 2026
Merged

Migrate frontend from Vite+Hono to Next.js App Router#458
rjroy merged 27 commits intomainfrom
feat/refactor-next-js-8npjk

Conversation

@rjroy
Copy link
Copy Markdown
Owner

@rjroy rjroy commented Feb 6, 2026

Summary

  • Replaces Vite SPA + Hono backend server with Next.js 15 App Router (single process)
  • Migrates all REST routes from Hono to Next.js API routes (app/api/)
  • Replaces WebSocket transport with SSE for AI chat streaming
  • Removes ~16,700 lines of dead code (Hono server, WebSocket handler, Vite config, old frontend shell)
  • Fixes PairWriting actions not appearing in Discussion (dual useChat instance bug)
  • Fixes user messages disappearing after send (session_ready overwriting local state)
  • Restores git commit hash as app version display

What changed

Architecture: The backend workspace is now a library (domain logic only, no HTTP server). Next.js API routes import directly from it. Communication is REST for stateless operations, SSE (POST /api/chat) for streaming AI responses. The frontend, API, and scheduled tasks all run in one Next.js process.

Deleted: Hono server (server.ts, index.ts), WebSocket handler (839 lines), all Hono routes (11 files), Hono middleware, WebSocket-only handlers (pair-writing, memory, card-generator), health collector, Vite config, old frontend entry point. ~25 files removed from backend alone.

Added: Next.js app shell (app/layout.tsx, app/page.tsx), 40+ API route handlers, SSE streaming infrastructure (lib/sse.ts, lib/controller.ts), useChat hook (SSE client), instrumentation.ts for scheduled tasks, ActiveSessionController + SessionStreamer for SDK orchestration.

Fixed (this session):

  • PairWriting Quick/Advisory actions now route through Discussion's chat pipeline via sendMessageRef prop
  • session_ready no longer overwrites locally-added user messages on resume
  • App version shows git commit hash instead of "0.1.0"

Test plan

  • 4109 tests passing across all workspaces (2071 backend + 85 shared + 1953 nextjs), 0 failures
  • 91.24% line coverage, 89.83% function coverage
  • TypeScript type checking clean across all 3 workspaces
  • Next.js production build succeeds
  • ESLint clean across all workspaces
  • Manual testing: vault selection, chat streaming, pair writing actions, session resume, file browsing

🤖 Generated with Claude Code

rjroy and others added 27 commits February 5, 2026 10:42
Phase 1-3 of Next.js + SSE migration plan:

Backend streaming module:
- ActiveSessionController for transport-agnostic session management
- SessionStreamer for SDK event transformation
- Types for SessionEvent, PendingPrompt, PromptResponse

Next.js app structure:
- SSE chat endpoint (POST /api/chat)
- Permission/question resolution endpoints
- Abort endpoint
- Webpack config for .js extension resolution

Frontend integration:
- useChat hook for SSE-based chat communication
- Discussion component supports transport="sse" prop
- Unified interface for both WebSocket and SSE transports

WebSocket handler update deferred for separate commit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WebSocket handler now delegates to ActiveSessionController for all
streaming logic, becoming a thin transport layer. The controller owns
AI state (SDK connection, pending prompts, token tracking) while the
handler just subscribes to events and maps them to WebSocket messages.

Removed ~440 lines of streaming code from websocket-handler.ts.
Satisfies REQ-6 from session-viewport separation spec.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Next.js build output should not be version controlled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1: Move React components to Next.js
- Move all components from frontend/src/components/ to nextjs/components/
- Move contexts, hooks, styles to nextjs/
- Create App.tsx wrapper and update layout.tsx

Phase 2: Migrate all REST API routes to Next.js
- Create 30 API route files covering all endpoints
- Vault management, files, capture, search, meetings, cards, config
- Add vault-helpers.ts for common route utilities
- Update backend package.json exports for new modules
- Create spaced-repetition/index.ts for module re-exports

Phase 3: Cleanup
- Delete frontend/ workspace entirely
- Update root package.json: remove frontend, update scripts
- Update pre-commit hook to check nextjs instead of frontend
- dev/build/start now point to Next.js directly

WebSocket proxy retained for streaming until full SSE migration.

Known issues (to fix in follow-up):
- Test files reference bun:test which isn't available in Next.js tsconfig
- Lint errors from moved test files
- Some import paths need updating

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move fonts, images, icons, and manifest from deleted frontend/public/
to nextjs/public/ for proper static file serving.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace all WebSocket usage with REST/SSE:

BrowseMode:
- Remove useWebSocket import and hook call
- Remove disconnect effect and error handling effect
- All file operations already use REST API hooks

PairWritingMode:
- Remove sendMessage, lastMessage, connectionStatus props
- Use useChat hook for quick/advisory actions via SSE
- Discussion component now uses transport="sse"

PairWritingEditor:
- Remove sendMessage and lastMessage props
- Delegate quick actions to parent via callback

Chat API fixes:
- Send vault info (vaultPath, vaultName) in request instead of fetching
- Initialize SDK provider in controller singleton
- Remove non-existent /api/vaults/:vaultId fetch

Config endpoints:
- Add REST endpoints for extraction prompt and card generator
- Components now fetch their own data via REST

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Return immediately after receiving 'result' event (terminal event)
  to avoid errors when SDK process exits
- Increase SSE stream close delay and track closing state to prevent
  duplicate close attempts

The SDK subprocess may exit after completing a response, which throws
an error if we try to continue iterating the event stream. Since
'result' is the terminal event, we can safely return at that point.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The SDK event flow is: stream_event* → assistant → result

- stream_event: partial content deltas (for real-time streaming)
- assistant: complete message with all content blocks
- result: statistics and terminal signal

Previously we only processed stream_event deltas and missed the
'assistant' event entirely. Now we use the assistant event's complete
content as the authoritative source, ensuring no content is lost.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Strips out useWebSocket hook and its tests. Discussion component
now uses useChat (SSE-based) instead of direct WebSocket messaging.
Session streamer patched to emit content missed by stream_events.
Components updated to use REST API client imports. Known issues remain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e-commit

Phase 1 - Fix ESLint + TypeScript config:
- Create nextjs/eslint.config.mjs (flat config, ignores .next/, disables
  type-checked rules in test files)
- Exclude test files from nextjs/tsconfig.json (bun:test unresolvable)
- Remove unnecessary type assertion in session-streamer.ts
- Update nextjs lint script for flat config format

Phase 2 - Remove dead code:
- Strip all Hono REST routes from backend/src/server.ts (now in Next.js)
- Create nextjs/instrumentation.ts for scheduler init (extraction + cards)
- Update launch.sh and systemd service to run Next.js instead of Hono
- Delete 12 dead backend test files for removed Hono routes

Phase 3 - Fix remaining lint/type errors:
- Fix test imports from "../@/lib/api/types" to "@/lib/api/types"
- Create nextjs/test-helpers.ts for test utilities
- Remove debug console.log from useCapture, useHome, reducer
- Fix floating promise, unnecessary assertions, unused variables
- Update CLAUDE.md to reflect Next.js + SSE architecture

Pre-commit hook now passes: backend typecheck/lint/tests, nextjs
typecheck/lint/build, shared typecheck/lint all green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Session resume was silently dropping message history because
session_ready was only emitted for new sessions. On resume, the
frontend received no previous messages and showed an empty chat.

Now both new and resumed sessions emit session_ready. Resumed sessions
include the full conversation history loaded from session metadata.
Slash commands are also sent on resume (previously only on new sessions).

Also fix Next.js dev startup error where webpack followed the
instrumentation.ts import chain into cron's child_process dependency.
Uses require() with variable paths to stay opaque to webpack's
static analysis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the browser tab went to background or the user navigated away,
the SSE stream would close but the controller kept emitting events.
Each event hit the closed stream controller and logged a full stack
trace, repeating for every remaining chunk.

Add cancel() handler on ReadableStream to unsubscribe on client
disconnect. First write failure now also triggers cleanup instead
of just logging, preventing the error cascade.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ev mode

Two issues fixed:

1. useChat.ts: Translate prompt_pending SSE events to the
   tool_permission_request / ask_user_question_request types that
   Discussion components expect. The controller emits prompt_pending
   (with nested prompt object) but the frontend was built for the old
   WebSocket protocol event names.

2. controller.ts: Move singleton to globalThis so it survives webpack
   HMR in Next.js dev mode. Without this, module reloading creates a
   fresh controller with no session state, causing "Session mismatch"
   409 errors when resolving pending questions or permissions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues:

1. daily-prep/today route was never ported from Hono to Next.js,
   causing 404 on the Ground tab.

2. The gitignore pattern `vaults/` matched `nextjs/app/api/vaults/`
   too, preventing all vault-scoped API routes from being tracked.
   Changed to `/vaults/` to only match the top-level data directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POST /directories and DELETE /directories/:path were in the Hono
backend but never ported to Next.js, breaking file browser directory
operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Directory delete now fetches real contents from the API for the
confirmation preview instead of showing empty placeholder. Removed
the archive context menu action from the file browser (just noise,
users can move directories manually).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The health system (HealthCollector, HealthPanel, useHealth, health schemas,
health API routes) was infrastructure built for WebSocket-based health
reporting but never wired to actual health checks. No production code
ever called .report() on the collector. Remove the entire system across
all three workspaces (~2300 lines deleted).

Also replace require() with dynamic import() using webpackIgnore magic
comments in instrumentation.ts to eliminate the webpack "Critical
dependency" build warning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All Next.js test files were still using MockWebSocket infrastructure
from the pre-migration Hono backend. Since the app now uses REST API
calls and SSE streaming, the tests needed to match production behavior.

- Add happy-dom setup (bunfig.toml, test-setup.ts) for DOM environment
- Remove MockWebSocket class and WS message assertions from 12 test files
- Rewrite Discussion.test.tsx for SSE: mock fetch + createSSEResponse pattern
- Rewrite VaultSelect.test.tsx for REST: POST /api/vaults/:id/sessions
- Fix storage key mismatch (was "memory-loop-vault-id", actually "memory-loop:vaultId")
- Inject slash commands after vault selection (SELECT_VAULT resets state)

1944 tests passing, 0 failures, 91.24% line coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…race

Resume button was broken: it set pendingSessionId in context but nobody
loaded the conversation history. Now RecentActivity calls the same
POST /api/vaults/:vaultId/sessions endpoint that VaultSelect uses on
login, populating messages and session ID before switching to Discussion.

Discussion captures the pending session ID on mount and passes it to
useChat as initialSessionId so subsequent messages resume correctly
instead of creating new sessions.

Also fixed SDK initialization race where API routes using the SDK
(inspiration, vault setup, card/extraction triggers) would fail with
SdkNotInitializedError if accessed before the first chat. Made
initializeSdkProvider() idempotent and added ensureSdk() utility for
routes that need the SDK independently of the chat controller.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The TLS system in backend/server.ts was built for Hono's Bun.serve()
and became unreachable when production moved to `next start`. Scripts
and docs were setting TLS env vars that nothing consumed.

- Remove TLS_CERT/TLS_KEY/TLS_PASSPHRASE from scripts, service, .env
- Rename HOST to HOSTNAME (what Next.js actually reads)
- Add NODE_ENV=production to launch.sh and service file
- Rewrite https-setup.md for reverse proxy (nginx, caddy)
- Use bun run --cwd instead of cd in launch.sh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Next.js migration is complete. The frontend uses REST+SSE exclusively,
making the entire Hono/WebSocket layer dead code. This removes ~7,700 lines
across 25 deleted files: websocket-handler, Hono server entry points, all
Hono routes (replaced by Next.js API routes), middleware, and WebSocket-only
handlers (pair-writing, memory, card-generator).

Backend is now a pure library with no HTTP server of its own.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PairWritingMode and Discussion each created their own useChat instance,
so Quick/Advisory actions sent from PairWritingMode never appeared in
Discussion's conversation. Fix by adding a sendMessageRef prop to
Discussion that exposes its sendChatMessage function. PairWritingMode
passes a ref and routes all actions through it, ensuring both user
messages and AI responses flow through Discussion's pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On session resume, the backend emits session_ready with stored message
history before appending the new user message. The frontend was blindly
replacing its messages array with this stale history, wiping the user
message that was just added locally. Now only applies server history
when local state is empty (initial resume), not during active send.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The app-version was showing "0.1.0" (package.json fallback) instead of
the git commit hash. Inject NEXT_PUBLIC_APP_VERSION from git rev-parse
at build time via next.config.ts, matching the original behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI referenced the deleted frontend/ workspace. Updated to nextjs/ for
test runs and coverage uploads. Added lcov coverage reporter to nextjs
bunfig.toml. Included nextjs in root test and test:coverage scripts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rjroy rjroy merged commit 06d70ca into main Feb 6, 2026
1 check passed
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 31.14754% with 252 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/src/streaming/active-session-controller.ts 30.38% 252 Missing ⚠️

📢 Thoughts on this report? Let us know!

@rjroy rjroy deleted the feat/refactor-next-js-8npjk branch February 6, 2026 04:04
rjroy added a commit that referenced this pull request Feb 6, 2026
All .lore/ reference docs, README, and copilot-instructions still
referenced the pre-migration Hono/WebSocket architecture. This updates
every stale reference to reflect the current Next.js App Router + SSE
stack (PR #458).

Changes across 55 files:
- Rewrite all frontend/src/ paths to nextjs/
- Replace WebSocket protocol sections with REST/SSE endpoints
- Remove references to deleted files (websocket-handler.ts, Hono
  handlers, server.ts)
- Archive 12 outdated .lore/ documents (WebSocket-era designs,
  diagrams, specs, research)
- Rename 3 auto-generated plan files to descriptive names
- Delete unused test-vault-widgets fixture (15 files)
- Update lore-agents registry with 4 new agents
- Fix frontmatter statuses and tags across 10 documents

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rjroy added a commit that referenced this pull request Feb 6, 2026
All .lore/ reference docs, README, and copilot-instructions still
referenced the pre-migration Hono/WebSocket architecture. This updates
every stale reference to reflect the current Next.js App Router + SSE
stack (PR #458).

Changes across 55 files:
- Rewrite all frontend/src/ paths to nextjs/
- Replace WebSocket protocol sections with REST/SSE endpoints
- Remove references to deleted files (websocket-handler.ts, Hono
  handlers, server.ts)
- Archive 12 outdated .lore/ documents (WebSocket-era designs,
  diagrams, specs, research)
- Rename 3 auto-generated plan files to descriptive names
- Delete unused test-vault-widgets fixture (15 files)
- Update lore-agents registry with 4 new agents
- Fix frontmatter statuses and tags across 10 documents

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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