Uh oh!
There was an error while loading. Please reload this page.
fix(lint): walk object-nested list / listViews through the view completeness rules - #14432
Conversation
…mpleteness-walk-object-nested-views
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14320
What
validateFunctionalCompletenesswalked exactly one container loop —stack.views— soobjects[].listandobjects[].listViews.*(the ADR-0017 "Object has-many View" spelling) never reachedcheckViewCompleteness. This adds the second walk, beside the existingobjects[]field walk, in the same file.The two sibling rules in this family already walk both doors:
lint-view-refs.ts'scontainerFromObjectpullslist/listViewsstraight off the object definition, andvalidate-list-view-field-refs.tshas anobjects[].listViews.*loop resolving the bound object asdata.object ?? <the object's own name>. This was the one family member that stopped at the top-level container — and its own test docblock names the failure: "a completeness gate that walks half the stack is exactly that: green, and blind to the other half."Resolution semantics and location grammar — mirrored, not invented
data.objectretarget (ADR-0047) first, else the object the container belongs to. That isvalidate-list-view-field-refs'slistViewObject(lv) ?? objNameon the identical rung.where=object "NAME" › listViews.KEY(andobject "NAME" › listfor the default slot) — the card's Expected spelling, and the sibling's.path=objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.Serial-with-#14106 clause: discharged
Triage made this card serial with #14106 so the new
view/tree-without-parent-fieldrule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is onorigin/main; this branch is cut from currentmain, so the widened walk carries bothview/layout-without-bindingandview/tree-without-parent-fieldthrough the nested door with no extra wiring. Pinned by a test that reaches the tree rule throughobjects[].listViews.*and by the map-form self-lookup case that stays silent.Accept-set evidence — before/after on the shipped example apps
Probe entry point is the card's own repro path,
runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), countingview/*findings per app. CONTROL = the same stack with an object-nestedganttlist view injected onto its first object, stripped of itsganttblock.app-todoapp-crmapp-showcaseCONTROL finding on head, verbatim:
Zero new findings on the shipped apps besides CONTROL, so nothing in
examples/**needed fixing or suppressing.app-showcasecould not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js— its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all —Every
listViewsinexamples/sits in a top-leveldefineViewcontainer, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.Reverse verification
The fix was committed first, then the implementation file was restored from the base commit (
git checkout f35821044 -- <path>, restore leg pinned toHEADunder atrap … EXIT INT TERMwith an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (
git hash-object==git rev-parse HEAD:<path>,git status --porcelainempty,git diff HEADempty). Nodist/is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.Tests
Fixture pair in both object spellings plus the negative half, in
validate-functional-completeness.test.ts:listViews.planganttwith noganttblock — array-form objects →view/layout-without-binding@objects[0].listViews.plan.ganttlistslot →objects[0].list.timelineview/tree-without-parent-fieldthrough the nested door, plus the self-lookup case that stays silent and the ADR-0047data.objectretargetlist: null,listViews: 'nope',list: 7,listViews: [null, 'x']#14320 acceptanceblock driving both spellings throughrunAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reasonVerification readout
All commands run at head
050dd5716; exit codes captured by redirect-then-capture, never through a pipe.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2(whole package)Test Files 93 passed (93)·Tests 2788 passed | 5 skipped (2793)pnpm --filter @objectstack/lint typecheckscripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)The three NOT MEASURED are prerequisite refusals (exit 3), not reds:
pnpm check:dual-build-cjs-loads—PREREQUISITE NOT MET … Run pnpm build first; needs the whole workspacedist/.pnpm check:type-check-debt— refuses on an unbuilt closure by design (its own header measurespackages/lintat 19 built vs 147 unbuilt).node scripts/check-test-completeness.mjs— with no argument it has no savedturbo run testlog to parse; its own text says to record it as NOT MEASURED.typecheckscope:packages/lint/tsconfig.jsonexcludes**/*.test.ts, so the green above says nothing about the new test code —tsc --listFilesreports 0 occurrences of the edited test file. That exclusion is known, ledgered state (check:type-check-coveragepasses; the package carries aTEST_DEBTentry of 16). To make sure this PR does not push that ratchet up whilecheck:type-check-debtis unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them invalidate-functional-completeness.test.ts(the 22 are 16 ledgered plus 6TS6059that the ledger note itself attributes to a re-measure project's inheritedrootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.Repo-wide
pnpm lintwas not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.Changeset
patchon@objectstack/lint(a published package) —.changeset/eighty-pumas-shave.md. User-visible:os validate/os buildnow emit findings on metadata that previously reported clean.skip-changesetdoes not apply.Generated by Claude Code