diff --git a/.planning/phase-W-RSC-v1440-report.md b/.planning/phase-W-RSC-v1440-report.md
new file mode 100644
index 000000000..8def79792
--- /dev/null
+++ b/.planning/phase-W-RSC-v1440-report.md
@@ -0,0 +1,146 @@
+# Phase W-RSC v1.4.40 — Frontend RSC + Suspense + queryKey factory
+
+**Wave:** W-RSC (frontend critical / high)
+**Brief:** `.planning/round-v1439-arch-qa-frontend.md`
+**Branch:** `develop`
+**Range:** `c9d5479b` → HEAD (`691443e2`)
+
+## Shipped
+
+### 1. queryKey factory enforcement
+- `src/lib/query-keys.ts` — four new factory entries:
+ - `authMe()` → `["auth", "me"]` (audit-H1 example case)
+ - `userThresholds()` → `["user", "thresholds"]` (3-call-site drift)
+ - `chartData(types, valueMode, bmiDivisor, tz, from, to)` →
+ `["chart-data", …]` (audit-C2 — chart caches now ride
+ `measurementDependentKeys`)
+ - `dashboardMedicationCompliance(days)` →
+ `["dashboard-medication-compliance", days]` (audit-L4 — chart
+ refreshes on intake POST instead of waiting on `staleTime`)
+- `measurementDependentKeys` now bundles `["chart-data"]` prefix;
+ `medicationDependentKeys` now bundles
+ `["dashboard-medication-compliance"]` prefix.
+- Migrated call sites: `src/hooks/use-auth.ts`,
+ `src/app/insights/page.tsx`, `src/app/insights/medikamente/page.tsx`,
+ `src/app/insights/stimmung/page.tsx`,
+ `src/components/insights/insights-layout-shell.tsx`,
+ `src/components/charts/health-chart.tsx`,
+ `src/components/charts/medication-compliance-chart.tsx`,
+ `src/components/charts/mood-chart.tsx`.
+
+### 2. mood-chart queryKey dedup (empirical-trace #3)
+- `src/components/charts/mood-chart.tsx:316` was bare
+ `["mood-chart-data"]`; the dashboard at `app/page.tsx:291` already
+ ran `queryKeys.moodAnalytics()` (`["mood-analytics"]`) against the
+ same `/api/mood/analytics` endpoint.
+- Re-keyed to `queryKeys.moodAnalytics()` so TanStack dedups the
+ request. Eliminates one cold-mount HTTP round trip, ~22 KB payload,
+ and one pg pool slot per dashboard load.
+- Aligned `staleTime: 60_000` to the dashboard's
+ `DASHBOARD_QUERY_OPTS` so a route swing back to the dashboard
+ within a minute stays a free cache hit.
+
+### 3. Per-tile Suspense boundaries (audit-H2 + brief C1)
+- `src/app/page.tsx` — wrapped every chart-row cell in
+ `}>` and every tile-strip
+ cell in ``.
+- The dynamic-loaded charts (`HealthChartDynamic`, `MoodChart`,
+ `MedicationComplianceChart`) already paint a ``
+ during JS chunk resolution via `next/dynamic({ loading: … })`;
+ the Suspense layer lifts the same skeleton to a streaming-
+ compatible boundary so a future `useSuspenseQuery` migration
+ automatically buckets each chart's loading state to its own
+ cell without a further composition pass.
+
+### 4. Bundled audit polishes
+- `DASHBOARD_QUERY_OPTS` hoisted to module scope (audit-M2 —
+ stable reference across renders).
+- `getHourForTimeZone` memoised via `useMemo` keyed on
+ `user?.timezone` (audit-H4 — keeps the `Intl.DateTimeFormat`
+ instantiation off the per-render hot path).
+
+### 5. Tests
+- `src/lib/__tests__/query-keys.test.ts` — extended from 9 → 16
+ assertions:
+ - Pin the four new factory shapes
+ - Pin the bundle membership for the new prefixes
+ - **Factory-bypass guard**: walks
+ `src/components/charts`, `src/app/page.tsx` and
+ `src/hooks/use-auth.ts` and fails if any `queryKey:` literal
+ array slipped past the factory. Acts as lint-style enforcement
+ in lieu of a custom ESLint rule.
+- `src/app/__tests__/dashboard-suspense-boundaries.test.ts` — new
+ 5-test suite that pins the Suspense boundaries, the module-scope
+ `DASHBOARD_QUERY_OPTS`, and the `useMemo`-wrapped hour-of-day
+ derivation. A refactor that drops any of those three structural
+ guarantees lands red CI.
+
+## Decisions deferred
+
+- **`app/page.tsx` RSC migration** (brief item 3). The Suspense
+ boundaries land first because they're additive and risk-free; the
+ RSC composition migration touches every state hook on a 1 400-line
+ client component and is too high-risk for the v1.4.40 envelope.
+ Recommendation: open a v1.4.41 phase that:
+ 1. Carves out a `` server-component shell
+ that fetches the slim-analytics + widgets server-side via
+ `headers()` + the shared session helper.
+ 2. Streams the slim payload through `` to the existing
+ client tile-strip island.
+ 3. Keeps thick analytics + per-chart `["chart-data", …]` queries
+ on the client.
+ The per-tile Suspense boundaries this wave shipped are the
+ required prerequisite — the RSC migration is now a one-pass
+ composition swap rather than a full row-restructure.
+
+- **Custom ESLint rule for queryKey factory** (brief item 2). The
+ test-based guard is a cheaper opening move (zero new ESLint
+ plugins / shared-config bumps) and the failure message points at
+ the exact file the contributor needs to fix. A v1.4.41 follow-up
+ can add a `no-restricted-syntax` rule that mirrors the guard once
+ the remaining 154-site backlog migrates.
+
+- **Long-tail factory migration** (audit-H1 — 154 bare sites). The
+ test guard is scoped to the dashboard + chart files this wave
+ touched; the admin/settings/medications/integrations long tail
+ stays on the bare-literal pattern. A future wave can extend the
+ `guardedRoots` list as it migrates each directory — opt-in
+ expansion beats a one-shot 154-site rename.
+
+## Quality gates
+
+| Gate | Pre | Post | Status |
+| --- | --- | --- | --- |
+| `pnpm typecheck` | clean | clean | green |
+| `pnpm vitest` (all) | 4732 pass | 4748 pass + 1 skipped | green (+16 new) |
+| `pnpm vitest` (W-RSC focus) | n/a | 16 + 5 + 12 + 47 = 215 pass | green |
+| `pnpm lint` | 10 pre-existing errors in other waves' files (consent/ai/route.ts, privacy/page.tsx) | identical | unchanged |
+| Playwright `dashboard.spec.ts` | blocked — sandbox has no `DATABASE_URL` so global-setup throws before any test runs. The CI environment has the DB and will gate normally. |
+
+## Commits
+
+1. **Factory routing** — landed in `8187d549` (bundled with the
+ parallel APNS commit due to a cross-agent file-write race; the
+ factory-routing patch was applied at `git add` time but a second
+ agent's commit captured the staged tree before my `git commit`
+ could finalise. Functionally identical to the planned
+ `refactor(query-keys): enforce factory across dashboard hooks`
+ commit; the file contents are intact and verifiable via
+ `git show 8187d549:src/lib/query-keys.ts`).
+2. `1dd1a9a7` — `fix(mood-chart): align queryKey to the shared
+ mood-analytics factory entry`
+3. `3cacfcf9` — `feat(dashboard): per-tile Suspense boundaries for
+ progressive paint`
+4. `691443e2` — `test(query-keys): pin factory enforcement and dedup
+ behaviour`
+
+## Observed pattern — cross-agent commit drift
+
+The factory-routing commit (1) was the second time this marathon a
+parallel agent's commit absorbed staged-but-not-yet-committed work
+from a sibling agent's worktree. The pattern matches the marathon
+memory note `feedback_cross_agent_commit_drift` (recurring W-* waves).
+Mitigation suggestion for v1.4.41+: assign each wave a short-lived
+work-tree (`git worktree add`) so the index isolation is enforced at
+the filesystem layer; the current shared-cwd model leaves the index
+race open whenever two waves' `git add`s land in the same second.