fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(lint): resolve every field reference on a list view at validate and build - #14283

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs
Sep 1, 2026
Merged

fix(lint): resolve every field reference on a list view at validate and build#14283
baozhoutao merged 4 commits into
mainfrom
claude/issue-14107-listview-field-refs

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14107

A list view names fields in more than forty places and none of them was resolved against the bound object — not by os validate, and not by os build, which is the publish gate. This adds one rule, list-view-field-unknown (validateListViewFieldRefs), over every one of those positions, resolving through the shared object-graph seam that #14105 and #14148 landed.

Premise re-check on today's origin/main

Re-measured on this branch's merge base (0fb3044f6), not inferred from the card:

Seam reuse

Resolution is resolveFieldPath's and the message half is describeFieldPathVerdict's; the "did you mean" is the seam's suggestName. No second field-resolution implementation and no copied suggestion helper — the existing three-copy problem is carded separately as #14268 and gains no fourth copy here. The three standard skips arrive as the seam's unknowable verdicts (object not in this stack, no readable field map, registry-injected system column), and this surface adds a fourth of its own: a list view whose data.provider is not object binds to no object graph at all.

The dotted-path decision — recorded, not silent

The card (written before the seam existed) suggested skipping dotted paths "as the chart rule already does". The seam can now walk relationship hops, so this needed an explicit call. Decision: judge the HEAD segment; do not walk hops. The reason is what the runtime does, not effort:

  • A ListViewSchema declares no ADR-0021 include, so a list view compiles no joins — the declaration that makes hop-walking meaningful at a dataset position has no counterpart here.
  • All three query axes a list view reaches refuse a dotted reference by name. Projection: assertProjectionHasNoDottedPaths in packages/objectql/src/engine.ts (verified present on this base) and assertProjectionFieldsExist at the REST ingress. Filter: the dotted-head door. Sort: assertSortFieldsExist's unknown / dotted / unmaterializable ladder.

Walking hops would therefore blessowner.name in a list view's columns — a reference every runtime door refuses — and teach an AI author that a traversal works on a surface where nothing implements it. Judging the head is also what validate-sortable-fields already does on this same surface.

The result is strictly wider than the card's suggestion: ownr.name is reported (a skip would have passed it), and the finding says which segment was judged so the author reads back the string they typed. What stays unreported is a dotted path whose head does resolve — a larger accept-set narrowing whose failure mode is a loud 400 rather than the silent-empty class this card gates. Filed as #14282 rather than folded in, and both halves are pinned in tests so a later change that starts walking hops has to delete a test that states the reason.

Positions covered

Top level: columns[] (bare-string and { field } forms, plus summary.field and prefix.field), filter[] keys, tabs[].filter[] keys, grouping.fields[], rowColor.field, userFilters.fields[], userFilters.tabs[].filter[] keys, filterableFields[], hiddenFields[], fieldOrder[].

Layout blocks: every field binding on kanban, calendar, gantt (including tooltipFields[] and quickFilters[].field), timeline, gallery, map and tree. The rule's POSITIONS table was checked key-by-key against ListViewShapeSchema and each block schema in packages/spec/src/ui/view.zod.ts, so no walked key is a phantom and no field-naming key is missing.

Filter keys go through walkFilterFieldKeys, so all three authored filter shapes are judged rather than one being walked and the others silently skipped.

Deliberately excluded, with reasons

Severity — two tiers, the validateFlowTemplatePaths precedent

error where the miss changes what data the view returns or collapses the layout it configures (every position in the card's measured table); warning where the renderer drops one decoration and renders the rest (optional colour/title/tooltip/cover bindings, a stale hiddenFields or fieldOrder entry).

Acceptance — validate AND build, pinned end-to-end

Following the #14148 precedent, the acceptance block drives runAuthoringRules for both commands rather than inferring the answer from the registry entry: each of the card's five measured positions fails validate and fails build, the fully-bound list view passes both, and a warning-tier position advises rather than gates. The rule also joins the reference-integrity suite's view runtime type, so the standalone list view a Studio tenant or an MCP author writes through the per-write publish door is judged too — that crossing is argued in the suite's own granularity-wall test.

Verification

All readings below are from commit 910f9d26b, the branch head this PR opens on.

  • pnpm --filter @objectstack/lint test91 files, 2660 tests passed.
  • pnpm --filter @objectstack/lint run typecheck — clean. Note the package's tsconfig.json excludes *.test.ts, so that run says nothing about the test files; a throwaway config including them was run separately and produced zero errors naming any of the three test files this PR adds or edits (the 22 errors it did report are pre-existing, in seven sibling test files).
  • Gate families derived mechanically from the real change set (node scripts/pm/dispatch-gates.mjs, 33 families, harvested with --commands): 30 green, 3 NOT MEASURED because they refuse without inputs this worktree does not have — check-test-completeness (exit 3, needs a saved turbo run test log; the gate documents this branch as the local NOT-MEASURED case), check:dual-build-cjs-loads (exit 3, needs a full workspace build), check:type-check-debt (exit 3, same). check-half-states timed out at 300s making live GitHub calls and is likewise recorded as NOT MEASURED — none of the four is a red, and none is a finding about this diff.
  • Also green: check:ratchet-remedy-authority, check:declared-population-live, check:nul-bytes.
  • pnpm lint (repo-wide ESLint, eslint . --no-inline-config) — clean, run whole rather than narrowed.

Changeset: minor for @objectstack/lint — an accept-set narrowing, matching the family precedent set by #14105 and #14148.

This branch was recovered rather than restarted: the predecessor dev on this card died in a container restart with its work committed in the worktree but never pushed, so the remote branch read as empty. The recovered commit was re-verified here from the premise up before this PR was opened.


Generated by Claude Code

Every field-naming position on a list view — columns, filter keys,
grouping, rowColor, userFilters, filterableFields, hiddenFields,
fieldOrder and every binding inside the kanban / calendar / gantt /
timeline / gallery / map / tree blocks — was resolved by nothing, on
both `os validate` and `os build`.
Resolution goes through the shared object-graph seam, on the HEAD
segment: a list view compiles no joins and all three runtime doors
refuse a dotted reference, so walking relationship hops here would
bless what the runtime refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…time-gate doors
Suite membership plus the runtime publish-gate crossing (refusal + clean
pair) for validateListViewFieldRefs, and the changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The wider narrowing the module docblock defers — a dotted list-view
reference whose head resolves, refused by all three runtime doors — is
filed as #14282; name it so the next author can read the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 18 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/concepts/metadata-lifecycle.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/deployment/validating-metadata.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/protocol/objectui/index.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/forms.mdx(via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in validateListViewFieldRefs))
  • content/docs/ui/pages.mdx(via userFilters (symbol, a field of const object POSITIONS))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx(via userFilters (symbol, a field of const object POSITIONS))
  • content/docs/releases/v15.mdx(via userFilters (symbol, a field of const object POSITIONS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 15 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789packageMentionDocs.

Which tree this was computed on

This run read content/docs from f170075cc7645caf77aa29c0de92270082061b4f — the merge of head 910f9d26bdad38dd06395af1b23a03936dda3277 into base 8125673462b3c0a6d42ef5b002acd4f77c1a9789, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f170075cc7645caf77aa29c0de92270082061b4f && git checkout f170075cc7645caf77aa29c0de92270082061b4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8125673462b3c0a6d42ef5b002acd4f77c1a9789 910f9d26bdad38dd06395af1b23a03936dda3277 && git checkout -B drift-repro 8125673462b3c0a6d42ef5b002acd4f77c1a9789 && git merge --no-ff 910f9d26bdad38dd06395af1b23a03936dda3277
node scripts/docs-audit/affected-docs.mjs --json 8125673462b3c0a6d42ef5b002acd4f77c1a9789

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8125673462b3c0a6d42ef5b002acd4f77c1a9789 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@baozhoutao@claude