Type from origin: eliminate any/cast smells (P1–P5)#15
Merged
Conversation
… any-casts) P3: a single app/js/globals.d.ts declares the debug/test window handles and the shared cross-window context (DEC-61) — __remit, __remitFault, __remitShell, __REMIT_SAMPLE, __map. Removes ~11 `/** @type {any} */ (window)` / (globalThis) / (window.opener) casts across context/main/shell/popout/map/data-analysis; the opener and popIn now type-check through the augmented Window. P4: timer handles typed at declaration with ReturnType<typeof setTimeout|setInterval> (matching data-analysis.js) instead of casting the setTimeout/setInterval result to any — main.js steeringShareTimer, wingman.js playTimer. Also: shell.js error helper uses `err instanceof Error` instead of two any-casts. tsconfig includes app/js/**/*.d.ts. 0 typecheck errors; 32 unit green; type-level only (no runtime behaviour change). https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf
…drop any-casts) orbat.js leaned on `/** @type {any} */ (patch).x` to read nested patch/next fields even though patch is Partial<Asset> and the generated Red/Green/BlueParams carry every field — vestigial casts. Removed them all: - tuneAsset reads patch.kind/symbol/.../red/green/blue + availability_window through the real types; the availability_window assignment/delete is typed (BlueParams.availability_window: TimeWindow) - validate(): a small `inVocab(vocab, string)` helper replaces `VOCAB.includes(/** @type {any} */ (x))` (LinkML emits enum slots as `string`, which trips the readonly-tuple literal narrowing); the param-group check is an explicit blue/red/green branch instead of a dynamic-index cast; the Omit `rest` returns without a cast - position/inAO/self params typed HexCell (not any); sanitizeWindows takes TimeWindow[]; cleanStr takes unknown orbat.js any-casts 18 → 0. 0 typecheck errors; 32 unit green; behaviour identical. https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf
…invariants in schema Types the Overview lap's serialisable state with the LinkML-generated types instead of `/** @type {any} */ (null)` casts: requirement: Requirement|null, handful: Plan[], selected/preview/execPlan: Plan|null, steering: Constraint[]. Reading those surfaced that the generated types marked always-present fields optional (LinkML declares few required), which would have forced read-site casts/guards — the very smell we're removing. The principled fix is to declare the real invariants at the origin (the schema): - Requirement.commitments, Plan.strategy, Plan.scores, Scores.satisfaction, Materialisation.schedule, Materialisation.trajectory → required: true (regenerated) Plus genuine null-safety (no casts): `if (!state.requirement) return` guards in the compare/execute stages (requirement is genuinely null pre-capture), `?? null` on the COA `.find()`, and optional-chaining on the observe-band lookup. tsconfig lib → ES2023 (the cards already use Array.findLast at runtime). main.js any-casts 23 → 12 (the rest are deck.gl view-state, P5). schema-required is a faithful reconciliation (schema ≡ code, DEC-57); golden plan ids unchanged (runtime untouched); adherence still validates whole; 32 unit green; 0 typecheck errors. https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf
…any-casts) views/map.js took its render options as `any`. Added a RenderOpts typedef (plans: Plan[], selected: Plan|null, assets: Asset[] — the serialisable data — plus typed view-state fields) and typed buildLayers/setData/render against it, so the plan/ asset/candidate/obstruction iterations infer real types instead of casting each callback param to any. Accessor params (col/alpha/path/…) are annotated with real types (Asset/Plan/HexCell), not any. The residual casts in map.js are the genuine library boundary: deck.gl's typed accessor props (Accessor<DataT,…>) don't accept a plain JSDoc callback, so the get* props are cast there (documented, like the existing MapOptions cast) — the accessor BODIES stay type-checked. Plus idiomatic `keyof typeof` index narrowing. Also marked TrajectoryPoint.lat/lng/t/fuel_pct required (always present on a rendered point) so route coords type as number, not number|undefined — regenerated. map.js any-casts 39 → 25. 0 typecheck errors; 32 unit green; golden ids unchanged. https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf
ADR-0031 documents P1–P5 (the maintainer-directed cast elimination), the schema `required`-invariant declarations that let main.js consume generated types without read-site casts, the assertion-vs-annotation distinction, and the consumer-ctx follow-up. Work-log row added. https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf
Contributor
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses your note that type-casting is a code smell — "data should be correctly typed from origin." I audited the codebase and applied the five fixes from the to-do list.
What the audit found
The loud smells were already gone: zero
@type {unknown}double-casts, zero@ts-ignore/@ts-nocheck, zeroascasts. What remained were@typeassertions — a big idiomatic bucket (DOM narrowing,const/tuple literals — not smells) and a real-smell bucket dominated byany.The five fixes (one commit each)
requirement/handful/plans/steeringwith generatedRequirement/Plan/Constraint. Reading them showed the generated types marked always-present fields optional — so I declared the real invariantsrequiredin the schema (Requirement.commitments,Plan.strategy/scores,Scores.satisfaction,Materialisation.schedule/trajectory,TrajectoryPoint.lat/lng/t/fuel_pct) rather than cast at the read sites. Genuine null-guards /?? nullfor real nullability. lib→ES2023 (findLast).(patch).xcasts (vestigial —Partial<Asset>already carries the fields);inVocab()helper replacesVOCAB.includes(/** any */(x)); explicit allegiance branch vs a dynamic-index cast. 18 → 0.app/js/globals.d.tsfor the window/context debug handles — drops ~11(window)/(globalThis)casts across 6 files.ReturnType<typeof setTimeout|setInterval>annotations instead of casting the handle toany.RenderOptstypedef types the data into the view; accessor parameters annotated with real types.The key distinction
A
/** @type {T} */ (expr)assertion (overriding inference) is the smell — removed. A/** @type {T} */ nameparameter/field annotation (declaring a type) is the fix — kept.Honest residue (documented, not chased)
getElementById→HTMLElement) — the required idiom, not a smell.Accessor<DataT,…>generics won't accept a plain JSDoc callback, soget*props are cast at that library boundary (like the existingMapOptionscast); the accessor bodies stay type-checked.compare/wingman/learn/entities) still cast(s) => s.labelbecause they receive plans via loosely-typedctx. Typing those ctx params is the same pattern one layer out — a clean follow-up (now unblocked by therequiredinvariants).App-wide
any-casts ~133 → ~78 (the rest are the two categories above).Verification
requiredreflects the real shapes; the schema is validation-only and never touches the runtime canonical form (NF3).Recorded as ADR-0031; work-log updated.
https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf
Generated by Claude Code