hensei-web/prd/migrate-next-to-sveltekit-runes.md
Justin Edmund 2d02f88622 Fix Link component legacy behavior and tab switching
- Remove legacyBehavior prop and nested <a> tags from Link components
- Modernize to Next.js 13+ Link API with className directly on Link
- Convert external links to plain <a> tags (LinkItem, Discord link)
- Remove unnecessary passHref props from Header components
- Fix tab switching by mapping string values to GridType enum

The tab switching issue was caused by trying to parse string values
("characters", "weapons", "summons") as integers when they needed to
be mapped to GridType enum values.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-03 15:58:12 -07:00

147 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD: Migrate Web App from Next.js to SvelteKit (Svelte 5 + Runes)
## Context
The app is a small but interactive site for building, saving, and sharing Granblue Fantasy team compositions. It currently runs on Next.js 14 using a hybrid of App Router and legacy Pages, with `next-intl` for i18n, Valtio for state, Radix-based UI wrappers, and Tiptap for rich text. The backend is a Rails API and will not change.
## Problem / Opportunity
- The UI layer carries Next-specific complexity (hybrid routing, middleware composition, Valtio, React wrappers around primitives) for a scope that aligns well with SvelteKits simpler mental model.
- Svelte 5 (Runes) and SvelteKit provide small, fast bundles and ergonomic state management for highly interactive UIs like the party editor.
- A rewrite would impose upfront cost but could yield better performance, maintainability, and developer experience.
## Goals
- Achieve feature and URL parity (including localized routing and auth/method guards) with the current Next.js app.
- Reuse domain types, data contracts, and i18n content; minimize churn to the Rails API layer.
- Maintain or improve perceived performance, accessibility, and UX of primitives (menus, dialogs, tooltips, toasts).
## NonGoals
- No changes to the Rails API endpoints, auth semantics, or data shape.
- No new product features beyond parity (performance and a11y improvements are in scope).
## Current State (Summary)
- Framework: Next.js 14 with `app/` and `pages/` side by side.
- Routing/i18n: `app/[locale]/…` with `next-intl` and “as-needed” default-locale prefixing; middleware composes i18n with auth/method guards.
- State: Valtio global stores for `accountState` and `appState` (party/grid/editor flows).
- Data: axios-based `utils/api.tsx`, app route handlers under `app/api/**` proxy to Rails; server helpers in `app/lib/*` with zod validation.
- UI: Components under `components/**` using SCSS modules, Radix-based wrappers, custom editor based on Tiptap React + custom extensions.
- URL State: `nuqs` binds filters to query params.
- Storybook: React/Next preset.
## Feature/Parity Requirements
- Public routes: `/`, `/new`, `/teams`, `/p/[party]` (+ tab segments), `/[username]`, `/about`, `/updates`, `/roadmap` with locale variants under `/ja` and default-locale “as-needed” behavior.
- Auth/method gates: protect `/saved` and `/profile`; 401 JSON for unauthorized API mutations; preserve method-based rules under `/api/**`.
- Party editor: weapons/summons/characters, job/skills/accessory, guidebooks, toggles, remix/delete, editability rules by cookie/user.
- Search & filters: team discovery with element/raid/recency, advanced filters from cookie, pagination (append/replace).
- i18n: reuse JSON namespaces; preserve current translation keys and locale cookie behavior.
- Primitives: dropdown menu, dialog, tooltip, toast, popover, select, switch, slider, command, segmented control with keyboard a11y.
- Editor: Tiptap with mentions, link, lists, headings, youtube, highlight, placeholder.
## Proposed Direction
Rebuild the web app on SvelteKit 2 and Svelte 5 Runes. Keep the Rails API contract intact. Adopt Svelte stores for global state and Runes for local component state. Replace React-specific libraries with Svelte-native equivalents while reusing JSON messages, type declarations, API contracts, and Tiptap core extensions.
## Target Architecture (SvelteKit)
- Routing: `src/routes/[lang=locale]/…` to mirror localized segments; top-level `/` redirects to `/new`.
- Hooks: `hooks.server.ts` handles locale detection/redirects and reproduces auth/method gates; `handleFetch` attaches bearer tokens from cookies.
- Data loading: `+layout.server.ts`/`+page.server.ts` for SSR (e.g., version, lists); `+page.ts` for client/SSR-unified fetches.
- Endpoints: `src/routes/api/**/+server.ts` proxy to Rails with zod validation and consistent status codes.
- State: Svelte `writable` stores for `account` and `app` (party, grid, search, jobs, skills, version), with typed helper actions; local UI state via runes.
- Theming: small store plus SSR-safe initial theme; apply class/data-theme on `<html>`.
- i18n: `svelte-i18n` (recommended) loading existing JSON bundles; locale cookie + accept-language fallback; “as-needed” default-locale paths.
## Dependency Mapping (Equivalents)
- Framework/Core:
- `next``@sveltejs/kit`
- `react`, `react-dom``svelte@5`
- `@svgr/webpack``vite-svg-loader` (or inline SVG)
- Routing/URL State:
- `next/navigation` → SvelteKit `goto`, `$page`, `afterNavigate`
- `nuqs``$page.url.searchParams` + small helper or `sveltekit-search-params`
- Data/Query:
- `@tanstack/react-query``@tanstack/svelte-query@5`
- `axios` → keep or migrate to `fetch`; keep zod
- i18n:
- `next-intl``svelte-i18n` (reuse JSON); remove `next-i18next`
- `i18next*` libs → only if choosing an i18next-based Svelte binding (otherwise remove)
- UI/A11y:
- `@radix-ui/*``bits-ui` primitives and/or `shadcn-svelte`
- `tippy.js``svelte-tippy` (or small custom)
- `cmdk``cmdk-svelte`/`cmdk-sv`
- Theming:
- `next-themes` → small Svelte store + cookie bootstrap
- Editor:
- `@tiptap/react` → Tiptap core with Svelte integration (e.g., `svelte-tiptap`); reuse custom extensions
- Typeahead/Select/Infinite:
- `react-bootstrap-typeahead``svelte-select`
- `react-infinite-scroll-component``svelte-infinite-loading` or IO-based action
- Misc:
- `cookies-next``event.cookies` server-side; `js-cookie` client
- `react-use`/`usehooks-ts` → `svelte-use`
- `remixicon-react``svelte-remixicon` or `unplugin-icons` (`ri:*`)
- `react-linkify``linkifyjs`/`linkify-html`
- SCSS → `svelte-preprocess` with component-scoped styles and global imports
- Storybook: `@storybook/nextjs``@storybook/sveltekit`
## Reuse vs. Rewrite
- Reuse
- i18n bundles under `public/locales/{en,ja}`
- Domain types `types/*.d.ts` (move to `src/lib/types`)
- Pure utilities in `utils/*` (parsers, enums, formatters)
- API contracts and zod schemas (`app/lib/api-utils.ts`)
- Tiptap custom extensions (`extensions/CustomMention`, `CustomSuggestion`)
- Public assets and SVGs
- Rewrite
- All React components and Valtio stores as Svelte components/stores
- Next middleware as `hooks.server.ts` (auth + locale); API handlers as `+server.ts`
- URL/query state helpers (replace `nuqs`)
- Theming (replace `next-themes`)
- Storybook stories
- Font handling (move `pages/fonts/gk-variable.woff2` to `static/` and add `@font-face`)
## Routing & i18n Details
- Localized route group `[lang=locale]` constrained to `en|ja` (from existing `i18n.config.ts`).
- “As-needed” default-locale behavior via redirects in `handle` and locale-aware links.
- Preserve existing route structure and query semantics (e.g., `/teams?element=…&raid=…`).
## Auth & Cookies
- Mirror `middleware.ts` logic: protect `/saved` and `/profile`; method-guard mixed routes; return 401 JSON for API.
- Server: use `event.cookies` and set `locals.user`; client: `js-cookie` where necessary.
- Use `handleFetch` to inject `Authorization` header from `account` cookie token.
## State & Data
- Global state: writable stores mirroring `initialAccountState` and `initialAppState` with typed actions; local UI state via runes.
- Queries: `@tanstack/svelte-query` for client/SSR-safe caching; SSR data via `+page.server.ts` where beneficial.
- Invalidation: use SvelteKit `invalidate`/`invalidateAll` in place of Next `revalidatePath`.
## UI & Editor
- Primitives: build on Bits-UI/shadcn-svelte; verify keyboard navigation and focus management; carry over SCSS look-and-feel.
- Editor: use Tiptap core with Svelte wrapper; reuse mention/suggestion logic and toolbar feature set (bold/italic/strike/heading/list/link/youtube/highlight/placeholder).
## Performance & SEO
- Expect smaller bundles and faster hydration from Svelte runtime.
- Maintain SSR for primary pages and content; keep linkable, crawlable party pages and listings.
- Optimize images/SVGs via Vite pipeline; keep existing public assets.
## Tradeoffs (Pros / Cons)
- Pros: performance, simpler state and data flows, clearer SSR/CSR boundaries, componentscoped styling, fast DX.
- Cons: full component rewrite, parity tuning for UI primitives, community-maintained editor bindings, team rampup, Storybook conversion.
## Alternatives Considered
- Stay on Next.js and complete the ongoing App Router + nextintl consolidation (lowest risk and cost).
- Prototype SvelteKit for the party editor only (dualstack) and evaluate before committing.
## Open Questions
- Keep proxy endpoints under `/api/**` or talk to Rails directly with CORS? (Proxy simplifies auth and error shaping.)
- UI library choice finalization: BitsUI vs shadcnsvelte (or a minimal custom layer where needed).
- Icon strategy: `unplugin-icons` vs direct SVG imports.
## Acceptance Criteria
- Route and locale parity with auth/method guards and “asneeded” defaultlocale behavior.
- Party editor parity (create/update/remix/delete; grids; jobs/skills/accessories; guidebooks; toggles).
- Querylinked filters and pagination on `/teams`, `/saved`, `/[username]`.
- Editor parity (mentions, link, lists, headings, youtube, highlight, placeholder) and equivalent styling.
- Primitives parity with keyboard a11y and visual consistency.
- i18n keys resolve from existing JSON bundles; theme persists SSR + client.
## Success Measures
- Timetointeractive and bundle size improved vs. Next build.
- No regression in error rates for API interactions (remix/delete/save).
- Positive qualitative feedback on responsiveness in editor/grid interactions.