Port the production add-in to a unified Next.js 16 app#445
Draft
kcarnold wants to merge 11 commits into
Draft
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 theexperiment/app's conventions (Next 16, React 19, ai-sdk v5, Tailwind 4, vitest). Model calls move server-side into route handlers that ownOPENAI_API_KEY, deleting the FastAPI/api/openaiproxy and the browsercreateOpenAI({ 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:
experiment/)next.configrewrite/taskpane.html→/taskpane(manifest unchanged), assets moved topublic/assets//api/chat(UI message stream),/api/draft(one-shot JSON),/api/revise(text stream); prompts ported verbatim; unit-tested withMockLanguageModelV2doLogin/doLogoutdropped fromEditorAPI)/— Lexical editor + sidebar (Draft / Revise / Chat) wired to the AI routesStill to come (separate commits on this branch):
/taskpane(Office.js onReady gating +wordEditorAPI)/logs+ proxy/api/log*→ FastAPIScope decisions
/api/log*to FastAPI (it owns the JSONL logs, theLOG_SECRETguard, the two-tier privacy redaction, and PostHog analysis).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.<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 Lexicalcontenteditabletyping path still want a human eyeball in a real browser.OPENAI_API_KEYin the sandbox); routes were confirmed reachable and to read the key from the env.Known follow-up
docContext) instead of a clean 400. Happy path is safe (the panels always send a fulldocContext); azodsafeParseguard per route would harden them.https://claude.ai/code/session_01QQgdYZ38NpK3mzzPKrwFzn