ID-29 PLAN — TanStack workspace-types migration decomposition
ID-29 PLAN — TanStack workspace-types migration decomposition
Section titled “ID-29 PLAN — TanStack workspace-types migration decomposition”Status: ratified-ready · awaiting Liam ratification before implementation Subtask dispatch
Audience: Knowledge Hub engineering (future task-executor agents for 29.5..29.8)
Author: task-planner agent, S252 WP5 (fresh Planner instance per Q-PLANNER-2)
Inputs: ratified TECH.md docs/specs/id-29-tanstack-workspace-types/TECH.md (560 lines, S251 commit 8581bf4f)
§1 Context
Section titled “§1 Context”This PLAN decomposes the ratified TECH.md (docs/specs/id-29-tanstack-workspace-types/TECH.md) into four sequential implementation Subtasks (29.5..29.8) appended to Task ID-29 in docs/reference/task-list.json. The Task is a Path c refactor: keep the sync constraint at lib/validation/schemas.ts:535 (z.enum(getValidTypeValues()) constructs Zod enum at module-load — async TanStack useQuery cannot back this) and migrate only the UI metadata surface from a static WORKSPACE_TYPE_REGISTRY to a DB-backed useApplicationTypes() TanStack hook.
TECH.md Proposed-changes summary (one-to-one with Subtask decomposition):
| TECH.md P-N | Surface | Lands in Subtask |
|---|---|---|
| P-1 | Hook signature + cache key + fetcher contract (useApplicationTypes()) | 29.6 |
| P-2 | Hook return shape + three sync replacement helpers | 29.6 |
| P-3 | UI consumer migration patterns (workspaces-content, workspace-card, workspace-create-dialog) | 29.7 |
| P-4 | Static registry deletion (WORKSPACE_TYPE_REGISTRY + registerType + getWorkspaceType + …) | 29.8 |
| P-5 | Test alignment (__tests__/lib/workspace-types.test.ts + new __tests__/hooks/workspaces/use-application-types.test.ts) | 29.6 (hook tests) + 29.8 (static deletion tests) |
| §6 Migration | DDL: ADD COLUMN label_plural + description on application_types + backfill 6 seed rows | 29.5 |
Option C hybrid metadata partition (ratified S251):
| Field | Lives where |
|---|---|
key, label, labelPlural, description, defaultIcon, defaultColour | DB: application_types.<col> (admin-editable, future Q-OQR1-13 v1.1) |
icon (resolved LucideIcon) | Static icon-name → LucideIcon map in hooks/workspaces/use-application-types.ts |
route, available, hasCustomCreation, features.* | Static client map in hooks/workspaces/use-application-types.ts (dev-editable, PR-reviewed) |
The select: selector on useApplicationTypes() joins the DB row with the static client config so consumers see a single flat WorkspaceTypeConfig indistinguishable from today’s shape.
§2 Decomposition
Section titled “§2 Decomposition”| Subtask | Title | Sibling deps | Effort | Files-touched estimate |
|---|---|---|---|---|
| 29.5 | DDL migration — add label_plural + description columns + backfill 6 seed rows + regen DB types | none | ~1h | 2 files (migration SQL + regenerated supabase/types/database.types.ts) |
| 29.6 | Author useApplicationTypes() hook + /api/application-types route + queryKey namespace + hook tests | [5] | ~1.5h | 4 files (hooks/workspaces/use-application-types.ts, app/api/application-types/route.ts, lib/query/query-keys.ts, __tests__/hooks/workspaces/use-application-types.test.ts) |
| 29.7 | Migrate 3 UI consumers to the hook (workspaces-content, workspace-card, workspace-create-dialog) | [6] | ~0.75h | 3 files (the 3 UI consumer files) |
| 29.8 | Delete static WORKSPACE_TYPE_REGISTRY surface from lib/workspace-types.ts + align legacy tests | [7] | ~0.75h | 2 files (lib/workspace-types.ts, __tests__/lib/workspace-types.test.ts) |
Total effort estimate: ~4h (matches Task ID-29 effort_estimate: "~3-4h impl" carried from backlog ID-21).
Subtask sizing per planning-and-task-breakdown skill rubric: all four are S–M sized (1–5 files each), well within the agent-friendly band.
25-Subtask soft-ceiling status (PRODUCT inv 20): 4 Subtasks within Task ID-29 — well within ceiling, no escalation needed.
§3 Dependency graph
Section titled “§3 Dependency graph”Strictly linear chain — no parallelism opportunity. TECH.md §5 forbids reversing the migration↔hook order (“would either ship a hook returning NULL-filled rows or require code-side fallback logic that this spec specifically avoids”).
29.5 (DDL + backfill + regen types) │ └─▶ 29.6 (hook + route + queryKey + hook tests) │ └─▶ 29.7 (3 UI consumer migration) │ └─▶ 29.8 (delete static registry + align legacy tests)Verification gate between each Subtask (Liam ratification, per workflow-orchestration cadence):
- After 29.5: staging-apply clean, prod-apply clean,
supabase/types/database.types.tsregenerated and committed,application_typesrow inspection shows 6 backfilled rows. - After 29.6: hook + route land but no UI consumer uses them yet (coexistence with static registry confirmed — no behavioural change in the UI). Hook tests pass.
- After 29.7: UI parity —
/workspaceslauncher page renders identically to pre-refactor; workspace-card renders identically; workspace-create-dialog opens identically. Static registry still present but no longer imported by any UI file. - After 29.8: zero-callers gate cleared per TECH.md §3 P-4 (“the Executor must run an ast-dataflow rename-sweep equivalent or a
grep -rnacrossapp/,components/,lib/,hooks/,contexts/,__tests__/confirming zero remaining call sites”).bun run test+bun lint+bun run buildall pass.
Sibling-only constraint (PRODUCT inv 14, enforced by lib/validation/task-list-schema.ts TaskSchema.superRefine()): verified — all four Subtask dependencies reference only sibling Subtasks within Task ID-29. No cross-Task dependencies expressed.
Cross-Task interactions surfaced but NOT expressed as Subtask deps:
- Task ID-28 (T8 cocoindex flow + Cloud Run sidecar) is independent — no shared schema, no shared route, no shared hook. No coordination needed.
- Q-OQR1-13 admin UI (v1.1) is independent — the
application_types.label_plural+descriptioncolumns are populated by this Task’s backfill; v1.1 will later add an admin mutation UI for those same columns, but the column-add work is fully scoped here.
§4 Per-Subtask effort + acceptance summary
Section titled “§4 Per-Subtask effort + acceptance summary”29.5 — DDL migration + backfill + regen types
Section titled “29.5 — DDL migration + backfill + regen types”- Acceptance: Staging-apply + prod-apply clean. Post-apply
SELECT key, label, label_plural, description, default_icon, default_colour FROM application_types ORDER BY keyreturns 6 rows withlabel_pluralanddescriptionnon-NULL for the 3 currently-rendered types (procurement,intelligence,sales_proposal) per TECH.md §6 backfill block.supabase/types/database.types.tsregenerated to include the 2 new columns onapplication_typesRow/Insert/Update. - Files:
supabase/migrations/<timestamp>_id_21_application_types_metadata_columns.sql(new);supabase/types/database.types.ts(regen). - Effort: ~1h (migration authoring + staging-apply + prod-apply + regen + commit).
29.6 — Hook + route + queryKey + hook tests
Section titled “29.6 — Hook + route + queryKey + hook tests”- Acceptance:
useApplicationTypes()returnsUseQueryResult<WorkspaceTypeConfig[]>with 6 rows fromapplication_types. Three sync helpers —useWorkspaceType(type: string),useLauncherTypes(),formatTypeCount(config, n)— replace the deleted static functions per TECH.md §3 P-2.GET /api/application-typesreturnsApplicationTypeRow[]JSON, authenticated (any role) viagetAuthorisedClient(['admin', 'editor', 'viewer']). NewapplicationTypesnamespace inlib/query/query-keys.ts. Hook tests cover the 6 cases listed in TECH.md §3 P-5 (6 rows, useWorkspaceType for valid/unknown keys, useLauncherTypes filter semantics, formatTypeCount singular/plural). At this commit the hook coexists with the static registry; no UI consumer uses it yet. - Files:
hooks/workspaces/use-application-types.ts(new);app/api/application-types/route.ts(new);lib/query/query-keys.ts(1-line append);__tests__/hooks/workspaces/use-application-types.test.ts(new). - Effort: ~1.5h (hook + route + queryKey + 6-case test suite).
29.7 — 3 UI consumer migration
Section titled “29.7 — 3 UI consumer migration”- Acceptance: All 3 UI consumer files import from
@/hooks/workspaces/use-application-typesinstead of@/lib/workspace-types. Each file follows the migration pattern in TECH.md §3 P-3a/3b/3c verbatim (including the loading-state handling at each surface)./workspaceslauncher renders identically to pre-refactor. Workspace card renders identically. Workspace create dialog opens identically (custom-creation flow gate viauseEffectstill triggers correctly whentypeConfig?.hasCustomCreation === true). After this Subtask,grep -rn "@/lib/workspace-types" app/ components/ hooks/ contexts/returns zero matches outsidelib/validation/schemas.ts:3(the sync constraint import). - Files:
app/workspaces/workspaces-content.tsx,components/workspace/workspace-card.tsx,components/workspace/workspace-create-dialog.tsx. - Effort: ~0.75h (3 files, each ~5-line import + ~3-line hook call swap).
29.8 — Delete static registry surface + align legacy tests
Section titled “29.8 — Delete static registry surface + align legacy tests”- Acceptance:
lib/workspace-types.tsretains ONLYAPPLICATION_TYPE_KEYStuple (L166-173) +getValidTypeValues()function (L181-183) + updated top-of-file comment block per TECH.md §3 P-4. The following are deleted:WorkspaceTypeConfiginterface,WORKSPACE_TYPE_REGISTRYconst,registerType(), 3 inlineregisterType({…})calls,getWorkspaceType(),getAllWorkspaceTypes(),getLauncherTypes(),formatTypeCount(),loggerimport, Lucide icon imports +LucideIconimport. Zero-callers gate cleared viagrep -rn(and/orbun scripts/ast-dataflow-cli.ts callers <symbol>) before each symbol delete.__tests__/lib/workspace-types.test.tsretains only thedescribe('getValidTypeValues')block.bun run test+bun lint+bun run buildall pass.bun run knipreports no new unused-export findings. - Files:
lib/workspace-types.ts,__tests__/lib/workspace-types.test.ts. - Effort: ~0.75h (delete + test-prune + regression suite).
§5 Open questions + risks
Section titled “§5 Open questions + risks”Open questions (none requiring Liam ratification beyond S251 Q-1..Q-5 closure)
Section titled “Open questions (none requiring Liam ratification beyond S251 Q-1..Q-5 closure)”All open questions raised in TECH.md §7 were ratified at S251 close (Q-1..Q-5 — see TECH.md §7 “S251 Liam ratification” table). No new open questions surfaced during decomposition.
Risks (carried from TECH.md §7 + planning-layer additions)
Section titled “Risks (carried from TECH.md §7 + planning-layer additions)”| # | Risk | Mitigation | Owner Subtask |
|---|---|---|---|
| R-1 | Loading-state flicker on /workspaces launcher between server-rendered count grid and client-resolved metadata | First paint renders the count grid skeleton (existing layout container) until useApplicationTypes() resolves (~50ms). TECH.md AC-3a gates this. | 29.7 |
| R-2 | Hook returns undefined for a workspace whose application_types.key is unknown (cross-env drift) | The 3 consumer files already null-coalesce — current getWorkspaceType() returns undefined for unknown keys, same posture preserved. | 29.7 |
| R-3 | Icon-name → LucideIcon resolution may drift if a new icon-name is seeded that the static map doesn’t carry | toWorkspaceTypeConfig() selector falls back to a default Folder icon when the icon-name map misses (matches today’s card-render path components/workspace/workspace-card.tsx:40). | 29.6 |
| R-4 | application_types table is read on every workspace render — N+1 risk if uncached | staleTime: 5 * 60_000 (5 min) on the hook; closed-list reference data, no invalidation triggers exist. | 29.6 |
| R-5 | Hook tests need a Supabase client mock; pattern is well-trodden via __tests__/helpers/mock-supabase.ts | Use createMockSupabaseClient(); mirror use-company-profiles test setup. | 29.6 |
| R-6 (planning-layer) | Hook return shape uses inline data?.foo ?? [] default-empty-array — risks new-reference-every-render breaking downstream deps (CLAUDE.md gotcha “Stable empty array/object defaults in hook returns”) | Hoist module-level const EMPTY_TYPES: WorkspaceTypeConfig[] = []; in hooks/workspaces/use-application-types.ts and wrap return with useMemo(() => data ?? EMPTY_TYPES, [data]) per CLAUDE.md guidance. Document in 29.6 acceptance. | 29.6 |
| R-7 (planning-layer) | Sandbox-disable required for migration db push + gen types (CLAUDE.md Supabase CLI gotcha); without it the bun process hangs on HTTP 204 returns | Executor of 29.5 MUST use dangerouslyDisableSandbox: true for all supabase CLI calls; explicit in 29.5 details field. | 29.5 |
| R-8 (planning-layer) | cat supabase/.temp/project-ref discipline before each push — staging vs prod project drift would silently push to wrong env (CLAUDE.md gotcha) | Executor of 29.5 MUST verify project-ref before each push; explicit in 29.5 details field. | 29.5 |
Out-of-scope confirmations (recapped from TECH.md §1)
Section titled “Out-of-scope confirmations (recapped from TECH.md §1)”APPLICATION_TYPE_KEYSsync tuple atlib/workspace-types.ts:166-173— preserved verbatim across all 4 Subtasks. This is the whole point of Path c.getValidTypeValues()atlib/workspace-types.ts:181-183— preserved verbatim.lib/validation/schemas.ts:535(z.enum(getValidTypeValues())) — preserved verbatim, continues constructing Zod enum at module-load.- Admin UI for mutating
application_typesrows — deferred per Q-OQR1-13 to v1.1. - Adding new application types to the seed — none of the 6 existing rows change. Future type additions are a separate change (sync tuple +
application_typesINSERT in lockstep, same constraint as today).
§6 Cherry-pick + commit cadence
Section titled “§6 Cherry-pick + commit cadence”Per workflow-orchestration §dispatch-protocol B9, this Subtask’s authoring lives in a worktree branch; Orchestrator cherry-picks to content-items-investigation after Liam ratification. Subsequent implementation Subtasks 29.5..29.8 are each separate task-executor dispatches with their own worktrees; the Orchestrator owns merge cadence (sequential cherry-picks, not parallel branches — the strict dependency chain forbids parallelism).
§7 Sources cited
Section titled “§7 Sources cited”- TECH.md ratified —
docs/specs/id-29-tanstack-workspace-types/TECH.md(S251 commit8581bf4f, 560 lines). - Task ID-29 state —
docs/reference/task-list.json(Subtasks 29.1 done / 29.2 deferred / 29.3 done / 29.4 in_progress). - Q-OQR1-13 v1.0/v1.1 cross-ref —
docs/specs/id-31-0.9-canonical-pipeline/PRODUCT.md§14.1 L559-569. - Sync constraint site —
lib/validation/schemas.ts:535(z.enum(getValidTypeValues())). - Existing TanStack hook pattern —
hooks/intelligence/use-company-profiles.ts(L33-47 single-fetch shape). - Existing API route pattern —
app/api/intelligence/profiles/route.ts(GET-only authenticated read). - Generated DB types —
supabase/types/database.types.tsL15-52 (application_typesRow/Insert/Update). - T2 migration template —
supabase/migrations/20260520120828_t2_combined_pr_intel_shape_b_form_type_split.sql§1.1-§1.2. - TanStack query-key namespace pattern —
lib/query/query-keys.ts(intelligence + workspaces + changeReports as the shape exemplar). - Subtask schema constraints —
lib/validation/task-list-schema.ts(SubtaskSchema,TaskSchema.superRefine()for sibling-only enforcement). - CLAUDE.md gotchas: (a) Sandbox-disable for Supabase CLI, (b)
cat supabase/.temp/project-refbefore push, (c) Stable empty array/object defaults in hook returns, (d) Bun fetch hangs on HTTP 204 (impl-stage relevant), (e) No raw Tailwind colours (impl-stage relevant for 29.7).