Skip to content

[dashboards] open-brain-dashboard-next: type Thought.id as string for UUID#250

Open
tswicegood wants to merge 1 commit intoNateBJones-Projects:mainfrom
tswicegood:contrib/tswicegood/dashboard-uuid-thought-id
Open

[dashboards] open-brain-dashboard-next: type Thought.id as string for UUID#250
tswicegood wants to merge 1 commit intoNateBJones-Projects:mainfrom
tswicegood:contrib/tswicegood/dashboard-uuid-thought-id

Conversation

@tswicegood
Copy link
Copy Markdown

Contribution Type

  • Dashboard (/dashboards)

What does this do?

Fixes a navigation bug: every Browse → Detail click in the dashboard 404'd because app/thoughts/[id]/page.tsx:29 ran parseInt(id, 10) on the URL param, but the upstream thoughts.id column is UUID. UUIDs parse to NaN, the isNaN guard fired, and the page returned 404. Confirmed live before this change; confirmed fixed after.

This is the dashboard-side counterpart to the type mismatch I documented as Known Limitation #1 in PR #239 (the REST gateway). The gateway has been UUID-native the whole time — c.req.param("id") flows straight to .eq("id", id) and Supabase coerces it correctly. The dashboard's Thought.id: number was the side that needed fixing.

Purely a type-correctness change. Vercel deploys benefit equally — no runtime behavior change beyond Detail navigation now working.

Changes

  • lib/types.tsThought.id, Reflection.thought_id, DuplicatePair.thought_id_a/_b, AddToBrainResult.thought_idstring
  • lib/api.tsfetchThought, updateThought, deleteThought, fetchReflections parameter types; CaptureResult.thought_id and updateThought response shape
  • app/thoughts/[id]/page.tsx — drop parseInt/isNaN, pass route param straight through
  • components/ConnectionsPanel.tsx, ReflectionComposer.tsx — prop types
  • components/Kanban{Board,Card,CardModal,Column}.tsx — every thoughtId in handler signatures, plus the active.id as string cast in handleDragEnd
  • app/audit/page.tsx — bulk-select Set<number>Set<string>
  • API proxy routes (audit/delete, duplicates/resolve, kanban/{delete,update}) — typeof checks and id array element types

What stays as number

Tested end-to-end

Live deploy on Cloudflare Workers (REST gateway from #239 backed by my Open Brain Supabase):

  • ✅ Browse → click thought → Detail page loads with full content
  • ✅ Edit thought (content / type / importance), save, refresh persists
  • ✅ Connections panel renders, links navigate to working Detail pages
  • ✅ Workflow/Kanban drag-and-drop, priority dot, modal save
  • ✅ Audit page bulk-select
  • ✅ Add to Brain quick capture
  • ✅ No NaN console errors, no type-coercion runtime errors

Requirements

None — pure TypeScript / control-flow change. No new dependencies. tsc --noEmit passes; OpenNext build passes.

Checklist

  • I've read CONTRIBUTING.md
  • No credentials, API keys, or secrets are included
  • I tested this on my own Open Brain instance (live Cloudflare Workers deploy, exercised the affected pages)

… UUID

The upstream thoughts.id column is UUID, but the dashboard typed
Thought.id (and most thought-FK references) as number and routed
detail pages through parseInt(id, 10). Every Browse → Detail click
parsed to NaN, hit the isNaN guard, and 404'd — confirmed live.

Switches every thought-id surface from number to string and removes
the parseInt at app/thoughts/[id]/page.tsx:29 so the route param
flows straight through. The Cloudflare REST gateway has been
UUID-native the whole time (.eq("id", id) with a string param), so
no backend change is needed.

Touched:
- lib/types.ts: Thought.id, Reflection.thought_id, DuplicatePair
  thought_id_a/_b, AddToBrainResult.thought_id
- lib/api.ts: fetchThought, updateThought, deleteThought,
  fetchReflections signatures + CaptureResult.thought_id +
  updateThought response shape
- app/thoughts/[id]/page.tsx: drop parseInt + isNaN, pass param
  through as string
- components/ConnectionsPanel.tsx: Connection.id + thoughtId prop
- components/ReflectionComposer.tsx: thoughtId prop
- components/Kanban{Board,Card,CardModal,Column}.tsx: every
  thoughtId in handler signatures, plus the active.id cast in
  handleDragEnd
- app/audit/page.tsx: bulk-select Set<number> → Set<string>,
  toggleSelect signature
- API proxy routes (audit/delete, duplicates/resolve,
  kanban/{delete,update}): typeof checks and id array types

IngestionJob/IngestionItem ids stay number — those tables are
BIGSERIAL, not UUID. Reflection.id stays number too; if the
reflections-table schema ever lands it may need revisiting.

Vercel deploys benefit equally — purely a type-correctness fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the dashboard Contribution: frontend template label Apr 27, 2026
@tswicegood
Copy link
Copy Markdown
Author

@alanshurafa — wanted to flag something puzzling in case you have context that'd help, since you originally landed this dashboard:

The dashboard's lib/types.ts has Thought.id: number (with uuid?: string as a separate optional field), and app/thoughts/[id]/page.tsx:29 runs parseInt(id, 10) on the route param. But the canonical thoughts.id column in docs/01-getting-started.md:67-72 is uuid default gen_random_uuid() primary key. UUIDs parse to NaN, the isNaN guard fires, and Detail navigation 404s. From git log, this shape has been there since the dashboard's initial commit (a082d26).

What I can't tell from the code alone is how it got that way — whether id/uuid were intended for two different purposes, whether there was a different backend in mind that returned numeric ids, or something else. The uuid?: string field next to id: number looks intentional rather than accidental, which is what makes me unsure.

If you've got context on the original intent, I'd love to hear it before this lands — happy to revise the approach. If the change as-is is fine, no action needed.

@alanshurafa
Copy link
Copy Markdown
Contributor

Thanks for flagging this carefully. No hidden intent the id: number + separate uuid?: string shape was a holdover from an earlier prototype against a different schema, not a deliberate two purpose design. By the time the dashboard landed, the canonical column was already UUID; the types just never got reconciled, and Detail navigation apparently never got clicked through on a real instance before merge.

Your fix is the right shape id: string everywhere except the genuinely numeric BIGSERIAL ingestion fields, which matches what's actually in the DB. Please land it as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard Contribution: frontend template

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants