Repository navigation
Generate the frontend's wire types from nodespace-types and gate them for drift - #3471
Conversation
Pragmatic Code Review — PR #3471Review Type: Initial Review (no prior reviews or comments on the PR; reviewed at SummaryThis is a clear net improvement and does what ADR-086 §8 asks. The hand-mirrored wire types are gone, the generated set is committed, and both What I verified by running or reading, rather than from the PR description:
Not run: the full test suite, the merge gate, and the "add a Rust variant, watch the check fail" experiment described in the PR. Requirements Check
Non-goal respected: no wire shape changes. Code Review FindingsCriticalNone. 🟡 Important (non-blocking)1. The coverage scan fails open in several ways and has no tests
The scanner also has no tests. The binary only compiles under Practical risk is low: a type that is referenced by a listed type but not listed itself produces an unresolved import that Recommendation: strip only Principle: a guard that fails open gives false assurance; guards should fail closed and be tested on their failure paths. 2. The generator and every
Recommendation: add Principle: the same quality bar for all committed code ("no lint suppression" should not be bypassed by a feature gate). 3. Vocabulary types left with no consumer Now that the generated fields are
The PR describes them as kept "as vocabulary types", but a union that types nothing is dead code. Recommendation: delete the unreferenced ones here. Where the narrowing is wanted back ( Principle: CLAUDE.md "delete unused code"; ADR-086 §8 (hand-written TypeScript keeps only guards, converters, aliases and helpers). 🟢 Suggestions4. Custom- The TypeScript literals come from 5. Rust omits 6. Pre-existing, now more visible: two Both are 7. Docs (nodespace-docs)
8. Nits
Frontend behaviour changes checked
Security / Performance
Recommendation: APPROVEFindings 1-3 are worth addressing in this PR but do not block the merge; the rest are optional. |
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Review addressedAddressed
Skipped
Outside this repo
Verification: Re-Review DecisionDecision: NO RE-REVIEW NEEDED Rationale: The review approved with no critical findings. The follow-up changes are a relocation of the coverage scan into tested script code, a lint-script addition, deletions of unreferenced types, a generator assertion, and two one-attribute deserializer fixes each pinned by a test. None changes a design decision the review assessed, and the full |
51bff64 to
e304117
Compare
|
Rebased onto main after #3470 landed (squashed to one commit). #3470 replaced |
e304117 to
112063c
Compare
… for drift (closes #3440) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
112063c to
515e0c2
Compare
Closes #3440. Implements ADR-086 §8: the frontend's wire types are generated from
nodespace-types, committed, and checked for drift by the merge gate.What changed
Rust (
nodespace-types)ts-rsis an optional dependency behind a newtsfeature. Every serialized type derivesTSunder that feature; default builds do not compile it (cargo tree --workspaceshows nots-rs).gen-tsbinary (requires the feature) writes one TypeScript file per type, the core type registry (CORE_NODE_TYPES) and the typed core field table (TYPED_CORE_FIELDS,TYPED_CORE_DEFAULTS). It scans the crate's source and refuses to run if a serialized type lacks the derive or is missing from its list.core_promoted_fieldsnow returnsPromotedField { storage, wire, shape, read_only }instead of(storage, wire)pairs, so the frontend table can be generated from it. The core schema test pins each field's shape and read-only flag to the schema's field type and protection level.TYPED_CORE_DEFAULTSis computed by converting an empty node of each type, not listed by hand.Tooling
bun run gen:typesruns the binary, formats with the frontend's Prettier config and writespackages/desktop-app/src/lib/types/generated/(only the files that changed; a second run is a no-op).bun run types:checkis the same with--check. It is a new stage in the merge gate (first in the Rust lane, so it fails in seconds) and intest:changed's Rust tier.gate-scope.ts: the generated directory,scripts/gen-types.tsand the frontend.prettierrcnow reach the Rust tier, so a hand edit to a generated file runs the check.Frontend
src/lib/types/(node.ts,task-node.ts,person-node.ts,project-node.ts,query.ts,schema-node.ts,ai-chat-node.ts,core-node-types.ts,typed-core-fields.ts) and three insrc/lib/services/are replaced by re-exports fromgenerated/. Guards, default-filling converters, primitive aliases and helpers stay.Drift this surfaced
SchemaNode.descriptionand.relationshipsare always serialized by Rust; the hand-written interface haddescriptionoptional and norelationships. Test fixtures were updated to carry both.DeleteResultisdeleted_counton the wire (the Rust struct has norename_all); the frontend type saiddeletedCount. Nothing reads the field. The type now matches Rust; the wire shape is unchanged.QueryFilter.typegainsrelated, andrelationshipName/filter, which the frontend type did not have.TaskNode.dueDate/startedAt/completedAtarestring | undefined(Rust omits them), not| null.nodeToTaskNodeandsetTaskDueDatenow writeundefinedwhere they wrotenull.Stringarestringin TypeScript (AiChatMessage.role,AiChatNode.turnStatus,lifecycleStatus,nodeType). The narrower unions the frontend had (AiChatTurnStatus, …) remain as vocabulary types but no longer type those fields.Notes for review
NodeEnvelope.titleistitle?: string | null: browser mode's dev-proxy sendsnullfor no title, while Rust omits it.ts-rsrenders#[serde(flatten)]), sonodeTypeisstring, not the'task'literal the hand-written interfaces had.ts-rs'sno-serde-warningsfeature is on: the only unsupported serde attribute in use isdeserialize_with, which does not affect the shape.Verification
bun run test:changed(every tier, since gate files changed): frontend, scripts, skill, browser,types:check, Rust workspace all pass.bun run quality:fixclean.bun run types:checkfail naming the stale file; reverting makes it pass.scripts/gen-types.test.tscovers the drift comparison against fixture directories (changed, missing, stale, hand-edited) and that both gates run the stage.🤖 Generated with Claude Code