Skip to content

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)

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-NSurfaceLands in Subtask
P-1Hook signature + cache key + fetcher contract (useApplicationTypes())29.6
P-2Hook return shape + three sync replacement helpers29.6
P-3UI consumer migration patterns (workspaces-content, workspace-card, workspace-create-dialog)29.7
P-4Static registry deletion (WORKSPACE_TYPE_REGISTRY + registerType + getWorkspaceType + …)29.8
P-5Test 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 MigrationDDL: ADD COLUMN label_plural + description on application_types + backfill 6 seed rows29.5

Option C hybrid metadata partition (ratified S251):

FieldLives where
key, label, labelPlural, description, defaultIcon, defaultColourDB: 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.

SubtaskTitleSibling depsEffortFiles-touched estimate
29.5DDL migration — add label_plural + description columns + backfill 6 seed rows + regen DB typesnone~1h2 files (migration SQL + regenerated supabase/types/database.types.ts)
29.6Author useApplicationTypes() hook + /api/application-types route + queryKey namespace + hook tests[5]~1.5h4 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.7Migrate 3 UI consumers to the hook (workspaces-content, workspace-card, workspace-create-dialog)[6]~0.75h3 files (the 3 UI consumer files)
29.8Delete static WORKSPACE_TYPE_REGISTRY surface from lib/workspace-types.ts + align legacy tests[7]~0.75h2 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.

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.ts regenerated and committed, application_types row 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 — /workspaces launcher 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 -rn across app/, components/, lib/, hooks/, contexts/, __tests__/ confirming zero remaining call sites”). bun run test + bun lint + bun run build all 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 + description columns 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 key returns 6 rows with label_plural and description non-NULL for the 3 currently-rendered types (procurement, intelligence, sales_proposal) per TECH.md §6 backfill block. supabase/types/database.types.ts regenerated to include the 2 new columns on application_types Row/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() returns UseQueryResult<WorkspaceTypeConfig[]> with 6 rows from application_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-types returns ApplicationTypeRow[] JSON, authenticated (any role) via getAuthorisedClient(['admin', 'editor', 'viewer']). New applicationTypes namespace in lib/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).
  • Acceptance: All 3 UI consumer files import from @/hooks/workspaces/use-application-types instead 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). /workspaces launcher renders identically to pre-refactor. Workspace card renders identically. Workspace create dialog opens identically (custom-creation flow gate via useEffect still triggers correctly when typeConfig?.hasCustomCreation === true). After this Subtask, grep -rn "@/lib/workspace-types" app/ components/ hooks/ contexts/ returns zero matches outside lib/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.ts retains ONLY APPLICATION_TYPE_KEYS tuple (L166-173) + getValidTypeValues() function (L181-183) + updated top-of-file comment block per TECH.md §3 P-4. The following are deleted: WorkspaceTypeConfig interface, WORKSPACE_TYPE_REGISTRY const, registerType(), 3 inline registerType({…}) calls, getWorkspaceType(), getAllWorkspaceTypes(), getLauncherTypes(), formatTypeCount(), logger import, Lucide icon imports + LucideIcon import. Zero-callers gate cleared via grep -rn (and/or bun scripts/ast-dataflow-cli.ts callers <symbol>) before each symbol delete. __tests__/lib/workspace-types.test.ts retains only the describe('getValidTypeValues') block. bun run test + bun lint + bun run build all pass. bun run knip reports no new unused-export findings.
  • Files: lib/workspace-types.ts, __tests__/lib/workspace-types.test.ts.
  • Effort: ~0.75h (delete + test-prune + regression suite).

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)”
#RiskMitigationOwner Subtask
R-1Loading-state flicker on /workspaces launcher between server-rendered count grid and client-resolved metadataFirst paint renders the count grid skeleton (existing layout container) until useApplicationTypes() resolves (~50ms). TECH.md AC-3a gates this.29.7
R-2Hook 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-3Icon-name → LucideIcon resolution may drift if a new icon-name is seeded that the static map doesn’t carrytoWorkspaceTypeConfig() 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-4application_types table is read on every workspace render — N+1 risk if uncachedstaleTime: 5 * 60_000 (5 min) on the hook; closed-list reference data, no invalidation triggers exist.29.6
R-5Hook tests need a Supabase client mock; pattern is well-trodden via __tests__/helpers/mock-supabase.tsUse 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 returnsExecutor 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_KEYS sync tuple at lib/workspace-types.ts:166-173 — preserved verbatim across all 4 Subtasks. This is the whole point of Path c.
  • getValidTypeValues() at lib/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_types rows — 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_types INSERT in lockstep, same constraint as today).

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).

  • TECH.md ratified — docs/specs/id-29-tanstack-workspace-types/TECH.md (S251 commit 8581bf4f, 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.ts L15-52 (application_types Row/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-ref before 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).