From e3c33de0560e15b56d6419065ca0ef5db02d703a Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:09:28 +0300 Subject: [PATCH] Differ clean for scrims routes; cookbook and manifest updates --- MIGRATION.md | 127 ++++++++++++++++-- apps/web-react/locales/da/common.json | 2 + apps/web-react/locales/de/common.json | 2 + apps/web-react/locales/es-ES/common.json | 2 + apps/web-react/locales/es-US/common.json | 2 + apps/web-react/locales/fr-CA/common.json | 2 + apps/web-react/locales/fr-EU/common.json | 2 + apps/web-react/locales/he/common.json | 2 + apps/web-react/locales/it/common.json | 2 + apps/web-react/locales/ja/common.json | 2 + apps/web-react/locales/ko/common.json | 2 + apps/web-react/locales/nl/common.json | 2 + apps/web-react/locales/pl/common.json | 2 + apps/web-react/locales/pt-BR/common.json | 2 + apps/web-react/locales/ru/common.json | 2 + apps/web-react/locales/zh/common.json | 2 + apps/web-react/scripts/route-census.ts | 8 +- apps/web/scripts/e2e-server.mjs | 6 +- apps/web/src/lib/components/DatePicker.svelte | 33 +---- .../src/lib/components/DefaultMetaTags.svelte | 11 ++ .../lib/components/TournamentSearch.svelte | 5 + apps/web/src/lib/components/UserSearch.svelte | 5 + .../scrims/components/ScrimPostCard.svelte | 45 ++----- .../components/ScrimTeamMembersPopover.svelte | 30 +---- .../scrims/components/WithFormField.svelte | 11 ++ .../routes/scrims/[id=integer]/+page.svelte | 3 + apps/web/src/routes/scrims/new/+page.svelte | 3 + apps/web/vite.config.ts | 8 +- migration-manifest.json | 9 +- 29 files changed, 231 insertions(+), 103 deletions(-) create mode 100644 apps/web/src/lib/components/DefaultMetaTags.svelte diff --git a/MIGRATION.md b/MIGRATION.md index 829030dd2..4f26ce7c2 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -67,26 +67,77 @@ Auth: `requireUser()` / `getUser()` / `actorId()` from `#lib/features/auth/user.server.ts` read `event.locals.user`, resolved once per request in `hooks.server.ts`. No AsyncLocalStorage. -## Write path (action → command), first shape +## Write path (action → command), the golden pattern -The full write-path pattern lands with the `/scrims` slice; `/leaderboards` -established the command shape for fixed-field mutations: +From the `/scrims` slice (`scrims.remote.ts`): - Each `_action` branch of a React action union becomes its own `command()` with its own valibot schema (the `_action` discriminator disappears). -- **Server-driven refresh is the default**: the handler calls - `getX(args).refresh()` / `.set(result)` for exactly the queries it - invalidated. -- When the server genuinely can't know which query instance the client holds - (filter/pagination args — the leaderboards case), the **client** rides the - refresh on the mutation round trip instead: +- **Single-flight refresh**: the client rides the refresh on the mutation round + trip by passing the query *function* — `acceptScrimRequest(args) + .updates(getScrimPosts)` — and the handler fulfils it with + `await requested(getScrimPosts, 5).refreshAll()`. Kit resolves the client's + active instances of that query (whatever args they hold), re-runs them + server-side, and the fresh data rides back on the command response. +- Mutations that live queries observe skip `.updates()` entirely: the handler + publishes to the event bus (`Events.publish(Events.scrimChannel(id))`) and + every subscribed stream — including the actor's own tab — re-yields. +- React's `errorToast`/`errorToastIfFalsy`/`notFoundIfNullish` port as + `#lib/utils/respond.server.ts` (they `error(400/404, message)` the command). + Handlers that used `{ fieldErrors }` returns keep them — `SendouForm` shows + them under the fields. +- `requirePermission(obj, permission)` is ported in + `#lib/modules/permissions/guards.server.ts`. -```ts -skipTeam({ season, identifier }).updates(getLeaderboards(queryArgs)); -``` +## SendouForm (schema-driven forms) -Never leave a mutation without one of the two — the refresh-everything default -must not ship. +`~/form/` ports as `#lib/form/`: `SendouForm.svelte`, `FormField.svelte` +(dispatch by schema metadata), `fields.ts` (valibot builders + a WeakMap +registry replacing zod's `formRegistry`), per-type field components in +`fields/`. The React data flow is kept — values in `$state`, client-side +valibot validation with translated `forms:*` message keys, submit sends the +raw values — but the transport is a remote **command whose arg schema is the +form schema** (server-side validation for free). Kit's remote `form()` stays +for simple fixed-field forms (the sidenav pattern); the complex fields (Date +values, tuple/union shapes) don't fit its FormData-backed field model, and +these forms were JS-dependent in React too. + +- Page wiring: `onSubmit={async (values) => { const r = await + createScrimPost(values as v.InferInput); if (r?.fieldErrors) + return r; await goto(...); }}` plus `onSuccess` for closing dialogs. +- Cross-field `superRefine` → `v.pipe(v.object(...), v.transform(...), + v.forward(v.check(...), [path]), ...)`; `.overwrite` → `v.transform` in the + same pipe. +- Fields render through context (`form-context.ts`); custom fields are + `{#snippet children(props)}...{/snippet}` + with the same `CustomFieldRenderProps` contract as React. +- The unsaved-changes guard is `#lib/form/UnsavedChangesGuard.svelte` in the + root layout + a `registerDirtyChecker` registry (`beforeNavigate` replaces + `useBlocker`; a `willUnload` cancel triggers the native prompt). +- `DatePicker.svelte` replaces react-aria's DatePicker: contenteditable + `spinbutton` segments named `"{segment}, {label}"` (the e2e contract), built + from `Intl.DateTimeFormat.formatToParts`. Segment quirks that pixel parity + depends on: no `white-space: pre` (flex whitespace collapsing is what glues + react-aria's literals), no explicit font-size (inherits like react-aria), + month/day/hour unpadded, minute 2-digit. + +## Live queries (`query.live`) + the event bus + +- `#lib/server/events.ts` is the in-process bus: mutations `publish(channel)`, + live-query generators `for await (const _ of Events.subscribe(channel))` + re-yield a fresh snapshot per (coalesced) wake-up. Always snapshots, never + event logs. A built-in heartbeat keeps proxies from idle-closing streams. +- Live queries stream over GET + SSE — they never interfere with the e2e + helpers' POST waits, and `fetch()` resolving on headers keeps the + hydration-indicator busy counter honest. +- SSR awaits the stream's first value; generators must yield before blocking + on the bus. +- Consumers: `getNotifications` (bell, per-user channel), `getChatRoom`/ + `getChatRooms` (chat), `getScrim` (both teams' scrim pages update live — + replaces skalop's revalidation broadcasts). + +Never leave a mutation without a refresh story — the refresh-everything +default must not ship. ## Search params @@ -193,6 +244,23 @@ zod codec. `IS_E2E_TEST_RUN` only; it counts in-flight remote fetches for the busy state. +## Popovers, menus, selects (mount semantics) + +react-aria unmounts popover/menu content when closed; the handwritten +`Popover`/`Menu` components now do the same (`{#if open}` around children). +This is what keeps strict-mode e2e selectors (and ARIA snapshots) from seeing +N hidden copies of testids/labels. `Select` keeps its *items* mounted (the +trigger text comes from item registration) but mounts the search field only +while open; its listbox is labelled only while open so a closed select exposes +exactly one element under its label. Search-item locators in the e2e helpers +filter `{ visible: true }` for the same reason. + +Icon-only buttons: React applies the size classes to the icon element itself +(an 18px icon inside the 20px slot ends up 20×18); the Svelte `Button` wraps +icons in a span whose `svg { width: 100%; height: auto }` wins on specificity. +Where parity matters, out-specify it with a tripled class +(`:global(.x.x.x) { height: 18px; }`) — see `ScrimTeamMembersPopover.svelte`. + ## One-offs ledger (discoveries) - Kit 3 moved `Handle` to `@sveltejs/kit/hooks` and `getRequestEvent` to @@ -222,3 +290,34 @@ zod codec. back to `.avif` while the oracle baked `.png` from `apps/web-react/.env` (broken sidebar event logos). One shared `.env` is the source of truth until the cutover; tooling overrides passed via `process.env` still win over it. +- Kit 3 param matchers are Standard Schemas in a single `src/params.ts` + exporting `params = defineParams({ integer: v.pipe(v.string(), + v.regex(/^\d+$/)) })` — the `src/params/.ts` convention is gone. +- Files named `*.remote.server.ts` are treated as remote-function modules by + Kit (everything exported must be a remote function) — hence + `#lib/utils/respond.server.ts`, not `remote.server.ts`. +- adapter-node 6 bakes `ORIGIN` (from `paths.origin`) at build time, and a + bare request guesses `https://`. One e2e/differ build serves many worker + ports, so the tooling spawns `apps/web/scripts/e2e-server.mjs` — a shim that + injects `x-forwarded-proto: http` with `PROTOCOL_HEADER` set — and + remote-function CSRF (which ignores `csrf.trustedOrigins`) sees the right + per-port origin. +- Context in async components must be set *before* the first `await` + (`set_context_after_init` is fatal in prod). Pattern: `setUserCardContext({ + userCards: () => data.userCards })` above `const data = $derived(await …)` — + the getter only runs after the await resolves. +- Mutating `$state` synchronously inside a `$derived`-triggered fetch is a + `state_unsafe_mutation` — the e2e fetch-counter patch defers its counter + writes a microtask (`HydrationTestIndicator.svelte`). +- Routes without their own meta render `` — the React root + supplies default title/description/og tags to meta-less routes, and the SSR + head diff flags their absence. +- The census walk must use an index entry's own `path` (from `prefix()`); + `/scrims` was silently missing from the census before. +- `RENDER_GIT_COMMIT` comes from `apps/web-react/.env` via `loadEnv` in + vite.config (plain `process.env` misses it and the footer loses its Version + line — a differ pixel diff). +- `notify`/`resolveNotifications` port to + `#lib/features/notifications/core/`, publishing to per-user bus channels + instead of skalop pings; web push is still unported (`xxx:` in + `notify.server.ts`). diff --git a/apps/web-react/locales/da/common.json b/apps/web-react/locales/da/common.json index fc986bb8d..3b5bc1532 100644 --- a/apps/web-react/locales/da/common.json +++ b/apps/web-react/locales/da/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/de/common.json b/apps/web-react/locales/de/common.json index 15aeb4960..30b9a4255 100644 --- a/apps/web-react/locales/de/common.json +++ b/apps/web-react/locales/de/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/es-ES/common.json b/apps/web-react/locales/es-ES/common.json index dfa4234be..142841959 100644 --- a/apps/web-react/locales/es-ES/common.json +++ b/apps/web-react/locales/es-ES/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "Nuevos mensajes", "chat.sidebar.title": "Chat", "chat.sidebar.noActiveChats": "No hay chats activos", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "Clave de amigo", "fc.whereToFind": "Encuentra tu clave de amigo en tu Nintendo Switch (2)", "fc.onceSetStaffOnly": "Una vez guardada, solo un miembro del staff puede modificarla.", diff --git a/apps/web-react/locales/es-US/common.json b/apps/web-react/locales/es-US/common.json index e7d77bd21..ef14de329 100644 --- a/apps/web-react/locales/es-US/common.json +++ b/apps/web-react/locales/es-US/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "Nuevo mensajes", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "Clave de amigo", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/fr-CA/common.json b/apps/web-react/locales/fr-CA/common.json index c375ad0f9..cef0d1eda 100644 --- a/apps/web-react/locales/fr-CA/common.json +++ b/apps/web-react/locales/fr-CA/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/fr-EU/common.json b/apps/web-react/locales/fr-EU/common.json index 714ade1bf..3c682ea3a 100644 --- a/apps/web-react/locales/fr-EU/common.json +++ b/apps/web-react/locales/fr-EU/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "Nouveau message", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "Code ami ", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/he/common.json b/apps/web-react/locales/he/common.json index 8d0ba8874..70def9657 100644 --- a/apps/web-react/locales/he/common.json +++ b/apps/web-react/locales/he/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/it/common.json b/apps/web-react/locales/it/common.json index 0fa1363db..856903bc8 100644 --- a/apps/web-react/locales/it/common.json +++ b/apps/web-react/locales/it/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "Nuovi messaggi", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "Codice amico", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/ja/common.json b/apps/web-react/locales/ja/common.json index 936d1d701..7f7646bcd 100644 --- a/apps/web-react/locales/ja/common.json +++ b/apps/web-react/locales/ja/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "新着メッセージ", "chat.sidebar.title": "チャット", "chat.sidebar.noActiveChats": "使用中のチャットはありません", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "フレンドコード", "fc.whereToFind": "フレンドコードは Nintendo Switch から見ることができます", "fc.onceSetStaffOnly": "フレンドコードは一度設定すると、変更できるのはスタッフのみです。", diff --git a/apps/web-react/locales/ko/common.json b/apps/web-react/locales/ko/common.json index ef119148e..77fba30af 100644 --- a/apps/web-react/locales/ko/common.json +++ b/apps/web-react/locales/ko/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/nl/common.json b/apps/web-react/locales/nl/common.json index c40e7870b..b80703d6e 100644 --- a/apps/web-react/locales/nl/common.json +++ b/apps/web-react/locales/nl/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/pl/common.json b/apps/web-react/locales/pl/common.json index e15f9a542..ca6e78dab 100644 --- a/apps/web-react/locales/pl/common.json +++ b/apps/web-react/locales/pl/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/pt-BR/common.json b/apps/web-react/locales/pt-BR/common.json index 700d61ec6..b4131639c 100644 --- a/apps/web-react/locales/pt-BR/common.json +++ b/apps/web-react/locales/pt-BR/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "Código de amigo", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/ru/common.json b/apps/web-react/locales/ru/common.json index 1bed30556..dd95c9004 100644 --- a/apps/web-react/locales/ru/common.json +++ b/apps/web-react/locales/ru/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "Новые сообщения", "chat.sidebar.title": "", "chat.sidebar.noActiveChats": "", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "Код друга", "fc.whereToFind": "", "fc.onceSetStaffOnly": "", diff --git a/apps/web-react/locales/zh/common.json b/apps/web-react/locales/zh/common.json index 10357130c..1deab68c4 100644 --- a/apps/web-react/locales/zh/common.json +++ b/apps/web-react/locales/zh/common.json @@ -372,6 +372,8 @@ "chat.newMessages": "新消息", "chat.sidebar.title": "聊天", "chat.sidebar.noActiveChats": "暂无活跃聊天", + "chat.sidebar.inactive": "", + "chat.room.scrim": "", "fc.title": "好友编号", "fc.whereToFind": "在您的 Nintendo Switch (2) 上找到好友编号", "fc.onceSetStaffOnly": "设置完成后,只有工作人员能修改", diff --git a/apps/web-react/scripts/route-census.ts b/apps/web-react/scripts/route-census.ts index 60cd4a8f8..d39a3ebd6 100644 --- a/apps/web-react/scripts/route-census.ts +++ b/apps/web-react/scripts/route-census.ts @@ -92,9 +92,11 @@ async function buildCensus(): Promise { const walk = (entries: RouteEntry[], parentPath: string) => { for (const entry of entries) { - const fullPath = entry.index - ? parentPath - : joinPaths(parentPath, entry.path); + // an index entry inside `prefix()` carries the prefix as its own path + const fullPath = + entry.path !== undefined + ? joinPaths(parentPath, entry.path) + : parentPath; if (entry.children) { walk(entry.children, fullPath); diff --git a/apps/web/scripts/e2e-server.mjs b/apps/web/scripts/e2e-server.mjs index f7ac788ce..e98a7e28a 100644 --- a/apps/web/scripts/e2e-server.mjs +++ b/apps/web/scripts/e2e-server.mjs @@ -5,7 +5,11 @@ // from `x-forwarded-proto` + Host keeps remote-function CSRF checks passing. // Run with PROTOCOL_HEADER=x-forwarded-proto (set by the tooling). import { createServer } from "node:http"; -import { handler } from "../build/handler.js"; + +// the build output only exists after `pnpm run build`; the indirection keeps +// svelte-check from following (and type-checking) the generated chunks +const buildDir = "../build"; +const { handler } = await import(`${buildDir}/handler.js`); const port = Number(process.env.PORT ?? 3000); diff --git a/apps/web/src/lib/components/DatePicker.svelte b/apps/web/src/lib/components/DatePicker.svelte index 10b6c41ba..1b965a402 100644 --- a/apps/web/src/lib/components/DatePicker.svelte +++ b/apps/web/src/lib/components/DatePicker.svelte @@ -1,4 +1,5 @@ + + diff --git a/apps/web/src/lib/components/TournamentSearch.svelte b/apps/web/src/lib/components/TournamentSearch.svelte index 44f251868..7979b722a 100644 --- a/apps/web/src/lib/components/TournamentSearch.svelte +++ b/apps/web/src/lib/components/TournamentSearch.svelte @@ -197,6 +197,11 @@ const composedAriaLabel = $derived( white-space: nowrap; } + .root :global(button:disabled .selectValue) { + color: var(--color-text-high); + font-style: italic; + } + .itemTextsContainer { line-height: 1.1; font-size: var(--font-sm); diff --git a/apps/web/src/lib/components/UserSearch.svelte b/apps/web/src/lib/components/UserSearch.svelte index d886d4004..dca089d0a 100644 --- a/apps/web/src/lib/components/UserSearch.svelte +++ b/apps/web/src/lib/components/UserSearch.svelte @@ -186,6 +186,11 @@ const composedAriaLabel = $derived( white-space: nowrap; } + .root :global(button:disabled .selectValue) { + color: var(--color-text-high); + font-style: italic; + } + .itemTextsContainer { line-height: 1.1; font-size: var(--font-sm); diff --git a/apps/web/src/lib/features/scrims/components/ScrimPostCard.svelte b/apps/web/src/lib/features/scrims/components/ScrimPostCard.svelte index 7a1867f83..bf733e8fe 100644 --- a/apps/web/src/lib/features/scrims/components/ScrimPostCard.svelte +++ b/apps/web/src/lib/features/scrims/components/ScrimPostCard.svelte @@ -117,14 +117,13 @@ const timePopoverFooterText = $derived( {#if post.isPrivate} {#snippet trigger(triggerProps)} - + {#snippet icon()}{/snippet} + {/snippet} {m.scrims_limitedVisibility()} @@ -209,10 +208,10 @@ const timePopoverFooterText = $derived( {@const tournament = post.mapsTournament} {#snippet trigger(triggerProps)} - + {/snippet}
- .iconButton { - display: flex; - align-items: center; - justify-content: center; - background: none; - border: none; - cursor: pointer; - padding: 0; - color: var(--color-text); - border-radius: var(--radius-field); - - &:focus-visible { - outline: var(--focus-ring); - outline-offset: 1px; - } - } - - .iconButton :global(.usersIcon) { - width: 18px; + /* the React app sizes the icon itself (18px) inside the 20px button icon + slot; the tripled class out-specifies Button's own svg sizing */ + :global(.scrimUsersIcon.scrimUsersIcon.scrimUsersIcon) { height: 18px; } @@ -392,7 +375,7 @@ const timePopoverFooterText = $derived( font-size: var(--font-xs); } - .tournamentPopoverTrigger { + :global(.tournamentPopoverTrigger) { height: auto; } diff --git a/apps/web/src/lib/features/scrims/components/ScrimTeamMembersPopover.svelte b/apps/web/src/lib/features/scrims/components/ScrimTeamMembersPopover.svelte index ddcababdc..f587d50e1 100644 --- a/apps/web/src/lib/features/scrims/components/ScrimTeamMembersPopover.svelte +++ b/apps/web/src/lib/features/scrims/components/ScrimTeamMembersPopover.svelte @@ -1,6 +1,6 @@ + + {#key scrimPostId} {/key} diff --git a/apps/web/src/routes/scrims/new/+page.svelte b/apps/web/src/routes/scrims/new/+page.svelte index 3d96b052c..a8daad0b9 100644 --- a/apps/web/src/routes/scrims/new/+page.svelte +++ b/apps/web/src/routes/scrims/new/+page.svelte @@ -1,8 +1,11 @@ + +
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 5a6a0bfa2..99397ef87 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,7 +1,7 @@ import { paraglideVitePlugin } from "@inlang/paraglide-js"; import adapter from "@sveltejs/adapter-node"; import { sveltekit } from "@sveltejs/kit/vite"; -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; const IS_E2E_BUILD = process.env.VITE_E2E_TEST_RUN === "true"; @@ -10,7 +10,11 @@ export default defineConfig(({ mode }) => ({ // apps/web-react/.env is the single source of truth for them envDir: "../web-react", define: { - __GIT_COMMIT__: JSON.stringify(process.env.RENDER_GIT_COMMIT ?? ""), + __GIT_COMMIT__: JSON.stringify( + process.env.RENDER_GIT_COMMIT ?? + loadEnv(mode, "../web-react", "").RENDER_GIT_COMMIT ?? + "", + ), }, plugins: [ paraglideVitePlugin({ diff --git a/migration-manifest.json b/migration-manifest.json index 16d132683..20e8500bb 100644 --- a/migration-manifest.json +++ b/migration-manifest.json @@ -46,7 +46,8 @@ "status": "pending" }, "chat": { - "status": "pending" + "status": "migrated", + "notes": "rebuilt (not ported) on ChatRoom/ChatRoomMessage/ChatRoomRead + query.live per plan; scrim rooms are the first consumer. UI is a redesign so no differ parity gate; sendouq/tournament rooms + SplatNet QR links arrive with their features" }, "comp-analyzer": { "status": "pending" @@ -105,7 +106,8 @@ "status": "pending" }, "notifications": { - "status": "pending" + "status": "pending", + "notes": "bell peek/live stream/mark-seen/10s grace dot + notify/resolve server core shipped with the scrims slice (notifications e2e green against apps/web); /notifications page, web push and remaining surface still pending" }, "object-damage-calculator": { "status": "pending" @@ -126,7 +128,8 @@ "status": "pending" }, "scrims": { - "status": "pending" + "status": "verified", + "notes": "phase 3 vertical slice; scrims e2e (9 tests) green against apps/web and differ clean (pixel/head/resource) for /scrims, /scrims/new, /scrims/:id 2026-08-17" }, "search": { "status": "pending"