Skip to content

refactor(lokee): one declaration per wire contract, and stop casting node data - #257

Merged
huyplb merged 5 commits into
mainfrom
cursor/lokee-postgres-e2e-847b
Aug 15, 2026
Merged

refactor(lokee): one declaration per wire contract, and stop casting node data#257
huyplb merged 5 commits into
mainfrom
cursor/lokee-postgres-e2e-847b

Conversation

@huyplb

@huyplb huyplb commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Follow-on cleanup to the Lokee Weave feature. No behaviour change intended —
this is a type-surface refactor plus two real defects it exposed.

The problem

Seven types were declared twice: once in lokee-weave.module.ts, once
hand-copied into lokeeApi.ts, with nothing checking the copies against each
other. Renaming a field type-checked cleanly on both sides and broke only in
the browser.

Three had already drifted:

  • source was widened from a four-value union to string on both
    LokeeVersion and LokeeHistoryEvent — the UI could compare against a typo
    and the compiler stayed quiet.
  • VersionGraphObject.schemaName was declared on a field no producer ever
    emits
    and no consumer reads.
  • script / previousScript (added by feat(lokee): table click — column type/constraint subtitles + GitHub script diff #255 while this was in flight) are
    required on the backend and were optional on the frontend.

The fix

One declaration per contract in apps/web/src/shared/lokee-wire.ts — the
directory permissions.ts and server-beam.ts already use for exactly this,
imported by both tiers. Because apps/web/tsconfig.json includes src and
../../packages, the repo's primary gate now checks producer, contract and
consumer in one pass: drift is a compile error.

Not in @foxschema/sql. That package is published to npm and scoped to
dialect knowledge; these types carry metadata-DB primary keys, user ids and row
counters. Types that genuinely are dialect knowledge — ObjectBlueprint,
StoredWeaveObject, ReversalPlan — stay there and are now imported instead
of hand-copied. lokeeApi.ts loses 112 lines of type declarations.

Two defects this exposed

A per-open Map rebuild. inspectObject re-materialised the entire object
map — one spread per live object, 20,000 on a schema this module explicitly
budgets for — purely to add a key the map key already held. The row literal
carries it now. #255 had since added a second copy of the same rebuild for
previousScript; that one also produced a duplicate-key spread once key was
present, which tsc caught.

Node payloads were unchecked. extends Record<string, unknown> on the three
node-data types is not required by React Flow — a type alias satisfies the
constraint — and the index signature was defeating excess-property checking, so
a misspelled field in a satisfies literal compiled clean. Converting to
aliases and typing renderers as NodeProps<LokeeVersionNode> removes five
casts; onNodeClick now narrows node.data from node.type instead of
checking and casting independently (a fourth node type would previously have
been silently mis-cast).

Verified: introducing changeCout in a node payload is now a compile error.
One assertion remains at LOKEE_NODE_TYPES, where React Flow's NodeTypes
genuinely erases the payload type — the honest place for it.

Also

  • graph()'s 25-line inline return type is now Promise<VersionGraphDTO>, and
    the same shape it re-spelled a second time mid-method is
    VersionGraphObject[]. truncatedObjects moved onto the DTO, deleting two
    intersections, a redundant Boolean() coercion and a parallel useState.
  • The e2e postgres-only History assertions moved onto DialectFlowOptions
    (the seam sqlite.test.ts already uses) instead of a dialectLabel === 'postgres' check inside code shared by 14 dialects. main had dropped the
    guard entirely
    , so those assertions were running against every dialect.
  • waitForInspectorLoaded now waits on data-state="ready" (a new attribute on
    the inspector) rather than string-matching "Loading blueprint…". The old wait
    also matched selected.name — a prop set synchronously on click — so it
    proved selection propagated, not that the fetch landed. data-object-key is
    payload-derived and can't pass early. Unit-tested, since the e2e dependency
    would otherwise fail as a timeout rather than an assertion.
  • The dispatchEvent('click') fallback is gone: it bypassed the actionability
    check that is the whole point of an e2e click. It now clicks React Flow's Fit
    view control and retries for real.

Reviewer notes

This branch merges main. #255 and #256 landed on the same files mid-flight.
The auto-merge left a duplicate objectNamedVisible — main's non-waiting
version after the waiting one, which would have won at runtime and silently
reverted the fix. Removed, along with the superseded
inspectorHasGrowth/inspectorHasSource. Worth a look at that hunk.

Skipped deliberately: deleting the confirmed-dead windowByTime /
windowGraph / blueprintChildCounts / BLUEPRINT_CHILD_TYPES (~190 lines,
zero callers). They're exported from a package staged for npm publish, so
removal is a semver decision rather than a cleanup — cheap now at 0.x.

Not verified in a browser. Graph rendering is covered by typecheck and unit
tests only; the Fit-view fallback path in particular only triggers when a node
is off-viewport.

Gates: typecheck clean (web + e2e) · npx vitest run → 1533 passed / 31
skipped · npx eslint . → 0 errors.

🤖 Generated with Claude Code


Note

Low Risk
Primarily type consolidation and test contracts; runtime behavior is intended unchanged aside from inspector stale-data fix and e2e reliability improvements.

Overview
Consolidates Lokee Weave API types into apps/web/src/shared/lokee-wire.ts so backend (lokee-weave.module.ts) and frontend (lokeeApi.ts, graphTypes.ts) share one contract—replacing duplicated declarations that had already drifted (source as string, phantom schemaName, optional vs required script fields). VersionGraphDTO now always includes truncatedObjects; the view reads it from the DTO instead of a separate state.

Backend: graph() returns VersionGraphDTO; objectsAtVersion / inspectObject stop rebuilding maps to re-add key on every object; toCanonical takes StoredWeaveObject directly.

React Flow: Node payloads use type aliases instead of extends Record<string, unknown>; renderers use NodeProps<LokeeVersionNode> / LokeeObjectNode, onNodeClick narrows LokeeNode, and LOKEE_NODE_TYPES keeps a single registration-boundary cast.

Inspector UX: Clears stale data when selection changes; exposes data-state and payload-derived data-object-key for tests. E2E: Postgres History expectations move to DialectFlowOptions.historyObjects; inspector waits on data-state="ready"; off-viewport graph nodes use Fit view + real click instead of synthetic events.

Reviewed by Cursor Bugbot for commit c8d0dad. Bugbot is set up for automated code reviews on this repo. Configure here.

cursoragent and others added 5 commits August 14, 2026 05:09
Migrate already snapshots Lokee before and after. Assert the History graph
for demo_b: fn_order_total shows Source (no Table growth) and customers
shows growth after the non-destructive demo_a → demo_b sync.

Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
The skippable wizard can appear after /signup/state. Clicking Skip only in a
short loop left Playwright on Loading… and timed out the Postgres flow.

Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
…ript)

Cloud VMs cannot reach the developer laptop. Copy the docker-compose
foxdb credentials into apps/e2e/.env.example and add a script that reseeds
demo_a/demo_b then runs test:e2e:postgres against a local npm run dev.

Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
…node data

Seven types were declared twice — once in lokee-weave.module.ts, once
hand-copied into lokeeApi.ts — with nothing checking the copies against
each other. Two had already drifted: `source` was widened from a
four-value union to `string` on both LokeeVersion and LokeeHistoryEvent,
and VersionGraphObject.schemaName was declared on a field no producer
ever emits.

The contract now lives once in apps/web/src/shared/lokee-wire.ts, the
same place permissions.ts and server-beam.ts already serve. apps/web's
tsconfig includes both src and packages, so producer, contract and
consumer are checked in one pass and drift is a compile error.

It does not go in @foxschema/sql: that package is published to npm and
scoped to dialect knowledge, while these types carry metadata-DB primary
keys, user ids and row counters. Types that genuinely are dialect
knowledge — ObjectBlueprint, StoredWeaveObject, ReversalPlan — stay there
and are now imported rather than hand-copied.

Also:

- graph()'s 25-line inline return type is now Promise<VersionGraphDTO>,
  and the same shape it re-spelled a second time mid-method is
  VersionGraphObject[]. truncatedObjects moved onto the DTO, deleting two
  intersections, a redundant Boolean() coercion and a parallel useState.
- inspectObject rebuilt the whole object map on every open — one spread
  per live object, 20,000 on a schema this module budgets for — purely to
  add a `key` the map key already held. The row literal carries it now.
- The node-data interfaces became type aliases. `extends Record<string,
  unknown>` was not required by React Flow (a type alias satisfies the
  constraint) and was defeating excess-property checks: a misspelled
  field in a node payload compiled clean. Verified it is now an error.
  NodeProps<LokeeVersionNode> types `data`, so five casts are gone and
  onNodeClick narrows from node.type instead of checking and casting
  independently.

One assertion remains where React Flow's NodeTypes genuinely erases the
payload type — at registration, not inside every renderer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main gained #255 (script diff) and #256 (revert connection binding) on
the same files this branch refactored. Resolutions:

- lokeeApi.ts / lokee-weave.module.ts: kept the consolidated declarations
  and folded main's additions into the shared contract — `script` /
  `previousScript` on ObjectInspectResult, and `connection_mismatch` on
  LokeeRevertErrorCode. The two sides had also drifted on the new fields
  (backend required, frontend optional); the shared type takes the
  producer's guarantee.
- shared-flow.ts: kept the options-driven historyObjects. main's side had
  dropped the dialect guard entirely, so the postgres-only assertions ran
  against every dialect; supplying expectations from postgres.test.ts
  fixes that properly.
- LokeeHistoryPage.ts: the auto-merge left a duplicate objectNamedVisible
  — main's non-waiting version after the waiting one, which would have
  won at runtime and silently reverted the fix. Removed it along with the
  superseded inspectorHasGrowth / inspectorHasSource.
- inspectObject: #255 added a second copy of the object-map rebuild for
  previousScript. objectsAtVersion now carries `key`, so that spread was
  both redundant and a duplicate-key overwrite.

Gates: typecheck clean (web + e2e), 1533 unit tests, 0 ESLint errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9986886b-b54f-49fc-b75f-9b21f929b614)

@huyplb
huyplb merged commit 71530f5 into main Aug 15, 2026
11 checks passed
@huyplb
huyplb deleted the cursor/lokee-postgres-e2e-847b branch August 15, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants