Skip to content

Port the production add-in to a unified Next.js 16 app#445

Draft
kcarnold wants to merge 11 commits into
mainfrom
claude/nextjs-16-migration-4H0Is
Draft

Port the production add-in to a unified Next.js 16 app#445
kcarnold wants to merge 11 commits into
mainfrom
claude/nextjs-16-migration-4H0Is

Conversation

@kcarnold

@kcarnold kcarnold commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What & why

Ports the production add-in from the webpack multi-entry SPA (frontend/) + Python OpenAI proxy onto a single Next.js 16 app (App Router), mirroring the experiment/ app's conventions (Next 16, React 19, ai-sdk v5, Tailwind 4, vitest). Model calls move server-side into route handlers that own OPENAI_API_KEY, deleting the FastAPI /api/openai proxy and the browser createOpenAI({ apiKey: 'unused' }) hack.

The full rationale, decisions, and a running log of anything surprising live in the repo:

  • frontend/docs/nextjs-migration/plan.md (checklist)
  • frontend/docs/nextjs-migration/log.md (running notes)

Status — in progress

Done (this PR so far), as micro-commits:

  • ✅ Scaffold unified Next 16 app (versions aligned to experiment/)
  • ✅ Wire the Word task pane endpoint — next.config rewrite /taskpane.html/taskpane (manifest unchanged), assets moved to public/assets/
  • ✅ Server-side AI route handlers — /api/chat (UI message stream), /api/draft (one-shot JSON), /api/revise (text stream); prompts ported verbatim; unit-tested with MockLanguageModelV2
  • ✅ Shared types / contexts / utilities (Auth0 doLogin/doLogout dropped from EditorAPI)
  • ✅ Standalone surface / — Lexical editor + sidebar (Draft / Revise / Chat) wired to the AI routes
  • ✅ Standalone render smoke test

Still to come (separate commits on this branch):

  • ⬜ Word task pane surface /taskpane (Office.js onReady gating + wordEditorAPI)
  • ⬜ Log viewer /logs + proxy /api/log* → FastAPI
  • ⬜ Commands FunctionFile, manifest prod build, Docker/deploy wiring, remove the legacy webpack app

Scope decisions

  • Anonymous-only this pass. Auth0 is removed; auth is deferred (being prototyped separately) and will land as the Better-Auth device-code / anonymous-usage-limit model — a clean seam is left for it.
  • Google Docs is deferred (loading design documented in the plan).
  • Logging stays in Python — the Next app will proxy /api/log* to FastAPI (it owns the JSONL logs, the LOG_SECRET guard, the two-tier privacy redaction, and PostHog analysis).
  • The legacy app is parked in frontend/legacy/ (excluded from build/lint/typecheck/vitest) and is ported then deleted in the cleanup commit.

Verification

  • npm run build, typecheck, lint, npm test (15 vitest) all green per commit.
  • Standalone UI verified at runtime by mounting the real <App> tree (reshaped + jotai + contexts + all three panels) and driving tab switching — confirms Lexical/reshaped run under React 19. A real browser could not be used in the CI sandbox (the network allowlist blocks the Playwright chromium download), so pixel/visual layout and the Lexical contenteditable typing path still want a human eyeball in a real browser.
  • AI round-trips weren't exercised (no OPENAI_API_KEY in the sandbox); routes were confirmed reachable and to read the key from the env.

Known follow-up

  • The AI routes currently 500 on a malformed body (missing docContext) instead of a clean 400. Happy path is safe (the panels always send a full docContext); a zod safeParse guard per route would harden them.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn

claude added 10 commits June 2, 2026 18:15
Replace the webpack multi-entry SPA toolchain with a single Next.js 16 App
Router app, mirroring the experiment/ reference (versions aligned to it:
Next 16.2.6, React 19.2.3, ai-sdk v5, Tailwind 4, vitest jsdom+globals).

- Add app/ (layout with Jotai Provider, placeholder / page, globals.css),
  next.config.ts, eslint.config.mjs, postcss.config.mjs, vitest config + setup.
- Rename legacy src/ -> legacy/ so Next stops treating src/pages as a Pages
  Router; legacy/ is excluded from build/lint/typecheck/vitest and ported then
  removed in later commits.
- Remove conflicting legacy build configs (babel/eslint/postcss/tailwind);
  babel.config.json in particular would force Next off SWC.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
The Office manifest hard-codes the task pane source as /taskpane.html and
the icons under /assets; neither URL can change without re-publishing.

- Add a next.config rewrite /taskpane.html -> /taskpane and a placeholder
  /taskpane route.
- Move assets/ -> public/assets/ so the manifest icon URLs resolve.

Manifest is unchanged. Verified over HTTP that /taskpane.html, /taskpane and
/assets/logo.png all serve 200.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
Move all model calls off the browser and into Next route handlers that own
OPENAI_API_KEY, replacing the Python /api/openai proxy and the browser
createOpenAI({ apiKey: 'unused' }) hack.

- lib/prompts.ts: port the Draft prompts + buildMessages, plus the Chat and
  Revise system prompts and buildRevisionPrompt.
- lib/ai.ts: model-agnostic streamChat / generateSuggestion / streamRevision
  (take a LanguageModel so tests inject MockLanguageModelV2).
- lib/models.ts: model id (gpt-4o) reading OPENAI_API_KEY from the env.
- Routes: /api/chat (UI message stream), /api/draft (one-shot JSON result),
  /api/revise (text stream).
- Unit tests for all three via MockLanguageModelV2 (re-add msw devDep that
  ai/test needs).

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
- lib/types.ts: add ChatMessage, SavedItem, EditorAPI as module exports
  (no global ambient d.ts). Drop Auth0-coupled doLogin/doLogout from
  EditorAPI; auth re-enters later via a separate session seam.
- contexts/: editorContext, chatContext, userContext (SSR-guarded), and a
  trimmed pageContext (pageNameAtom + PageName; drop the full/demo split).
- lib/: selectionUtil (+ tests, importing DocContext from @/lib/types) and
  the useDocContext hook.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
Standalone surface for /: a Lexical editor plus the sidebar app (tab bar +
panels), backed by an in-memory EditorAPI.

- components/Providers.tsx: PostHog + Reshaped (slate) + ChatContext, wired
  into the root layout.
- components/Navbar.tsx, App.tsx (panel switch), editor/LexicalEditor.tsx,
  editor/StandaloneEditor.tsx. Drop the legacy demo mode and Auth0 login/
  onboarding/calvin gates (anonymous-only).
- / renders the editor client-only via dynamic import (ssr: false).
- CSS modules ported verbatim; .editor-scrollbar moved to globals.css.
- Draft/Revise/Chat panels are stubs, wired to the AI routes next.

Confirms lexical/reshaped/posthog build and run under React 19 / Next 16.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
- components/pages/Draft.tsx: replace the in-browser streamText Fetcher with
  a fetch('/api/draft') POST returning the GenerationResult JSON.
- lib/log.ts: log() helper posting to /api/log (proxied to Python later).
- Move the in-render ref assignment into an effect (react-hooks/refs).
- Scope the Draft/Revise CSS-module palettes to .app and fix impure
  selectors (Turbopack CSS Modules require pure selectors).

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
- components/pages/Chat.tsx: use @ai-sdk/react useChat against /api/chat
  (DefaultChatTransport) instead of the browser streamText loop.
- Send current docContext with each turn; /api/chat prepends a doc-context
  message (buildChatDocContextMessage) ahead of the conversation.
- Retype ChatContext to UIMessage[] and seed/persist via guarded effects so
  chat history survives tab switches.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
- components/pages/Revise.tsx: read the /api/revise text stream via a
  ReadableStream reader + TextDecoder instead of the browser streamText loop;
  the viz system prompt + doc-prompt builder live server-side now.
- Drop unused legacy state; inline the remark anchor renderer to satisfy
  react-hooks/refs; escape a JSX apostrophe.

Completes commit 5: the standalone surface (editor + Draft/Revise/Chat) is
fully ported and wired to the server-side AI routes.

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
Mounts the real App tree (reshaped + jotai + contexts + Draft/Revise/Chat)
and drives tab switching, guarding against render-time regressions. Adds an
Element.prototype.scrollTo mock to vitest.setup.ts (jsdom lacks it; Chat's
auto-scroll effect needs it).

https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn
@kcarnold kcarnold changed the title docs(frontend): seed Next.js 16 migration plan and log Port the production add-in to a unified Next.js 16 app Jun 2, 2026
@kcarnold kcarnold marked this pull request as draft June 5, 2026 17:12
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.

2 participants