fix(lint): resolve app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

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 app navigation viewName against the target object's list views - #14286

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution
Sep 2, 2026
Merged

fix(lint): resolve app navigation viewName against the target object's list views#14286
baozhoutao merged 2 commits into
mainfrom
claude/issue-14108-nav-viewname-resolution

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#14108

App navigation's viewName was resolved by nothing at author time. ObjectNavItemSchema
documents it as "Default list view to open. Defaults to 'all'", so an unresolvable name never
failed — it fell back. The nav entry kept its authored label and icon and opened a different
view, which is why the failure is invisible in review: a "Schedule" entry that opens the plain
grid still reads correctly in the diff.

This extends #2554's existing lintViewRefs to the navigation door of the same listViews
namespace. It is not a new rule class — the file already guarded the type:'form' action-target
door into that namespace, and navigation is the other, more travelled one.

Premise checks (run first, on origin/main @ aca23aba4)

1. The gap is real — confirmed. A probe stack whose nav carries
viewName: 'A4_no_such_view', run through runAuthoringRules on both commands:

[validate] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[build] errors: [["security-owd-unset","object \"duly_task\""]] <- unrelated
[positive control] [["action-no-placement"],["view-ref-form-target-missing"],…]

Zero findings about the bogus viewName on either command. The positive control shows
lintViewRefs itself is wired and live — a bogus type:'form' target on the same stack is
reported. The three rules the family landed since the card was written (#14105 / #14148 /
#14107) cover datasets, widgets and list-view field positions; none of them reaches nav
viewName.

2. objectName resolution — the card's hedge, answered: it IS already covered, so this PR is
the viewName half only.
Two live checks own it, and between them they leave no hole:

  • packages/spec/src/stack.zod.ts's validateCrossReferences errors on
    App 'APP' navigation references object 'OBJECT_NAME' which is not defined in objects. Unlike the
    sibling dashboard/page/report arms it carries nosize > 0 gate, so it does not
    switch itself off on a stack that declares no objects.
  • packages/lint/src/validate-object-references.ts covers the case that check exempts — a nav
    item carrying requiresObject.

runAction is covered too (the same stack.zod.ts block, plus
validate-action-name-refs, which walks app navigation explicitly). See "variants" below.

3. The default view's real key is default, not 'all'. The dispatch asked me not to trust
either the card's 'all' or a paraphrase, and the paraphrase turns out to be the wrong reading:
expandViewContainer keys a bare default list as OBJECT.default, and objectui's
resolveViewId has no special case for 'all' either. The schema's "Defaults to 'all'"
describes the convention of declaring a listViews.allexamples/app-crm declares exactly
that on activity / lead / opportunity, and it resolves normally as a declared key. So the
rule needs no 'all' special case, and a test pins both halves of that.

Resolution mirrors the runtime matcher, deliberately

The authority is objectui's resolveViewId (@object-ui/core, objectstack#2217), which
ObjectView calls on every nav landing. It accepts three directions — exact id, short name
retried as OBJECT.NAME, and a qualified name retried with the OBJECT. prefix stripped —
and on a miss console.warns and falls back to defaultViewId || views[0]. That browser-console
warning is the only existing signal, which is why the check belongs at author time.

The lint reimplements those three directions and no others. A stricter match would red names that
work at runtime; a looser one would bless names that do not.

Severity: error, and what makes that safe

The sibling view-ref-form-target-missing is a warning because a miss might be a view the lint
failed to collect. That vector is closed by construction here: the rule fires only when it has
already collected a non-empty list-view namespace for that object out of this stack. Every
other case is skipped rather than guessed at:

skipped whenwhy
viewName is interpolatedresolved at render time, not statically
recordId is setthe schema documents viewName as ignored in that pairing
the item carries requiresObjectexplicit "another package provides this object"
this stack contributed no list view for that objectindistinguishable from a cross-package object

What stays outside its knowledge is a runtime-saved view (savedViews in ObjectView), which
no author-time pass over declared metadata can see — the same boundary every rule in this suite
has. It is stated in the file header rather than left implicit.

Variants — covered, or why not

  • runAction — already covered; adding it here would double-report. stack.zod.ts errors on
    an unresolved runAction (size-gated), and validate-action-name-refs speaks when that gate is
    off.
  • recordId — deliberately not a lint target. It is record data, carrying template
    variables ({current_user_id}, {current_org_id}) resolved by the shell at render time; there
    is no author-time set to resolve an id against.
  • filters — needs nothing: the schema already makes it mutually exclusive with
    recordId/viewName, so the ambiguous combination is unrepresentable rather than linted.

Acceptance

Pinned end-to-end on validate AND build, following the #14148 / #14107 precedent rather
than inferring it from the registry entry — the card measured os validate --json returning
valid: true and os build green, so a validate-only fix was not acceptable, and nothing else in
the file would notice if the suite entry's commands were narrowed later.

Reverse verification: with the finding's severity ablated error to warning, exactly 3 of the
34 tests go red — the unit severity assertion and both acceptance limbs — and the other 31 stay
green, so the acceptance block is what carries the gating claim. The mutation was confirmed on
disk (git diff --stat non-empty plus a match count on the injected text) before the run, and the
tree was restored from HEAD afterwards, proven by git hash-object equalling the HEAD blob.
The subject is a same-package relative import, so vitest resolves it from src and the ablation
needed no rebuild.

Downstream stopgap — a follow-up in that repo, not this PR

objectstack-ai/duly's test/views.test.ts asserts every nav viewName resolves against the
declared listViews, and the reporter wrote it to be deleted when this ships. Deleting it is a
follow-up in objectstack-ai/duly, out of scope for this repo's PR — recorded here rather
than left silent, so a stale stopgap asserting a now-gated invariant is not forgotten. It should
be removed only after this lands and duly picks up the release.

Verification

Run on the final commit 43f68b243.

  • pnpm --filter @objectstack/lint test91 files, 2676 passed (5 skipped)
  • pnpm --filter @objectstack/lint typecheck — clean. ⚠️ Recorded honestly: that script reads
    no test filepackages/lint/tsconfig.json excludes **/*.test.ts, and --listFiles
    returns 0 hits for the edited test. The test edit was type-checked through a program that does
    include it: my files clean, 0 errors.
  • pnpm lint (full repo eslint . --no-inline-config) — clean, 92s. No narrowing claimed.
  • All 33 gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (path-derived + convention-triggered, harvested runnably
    so neither section nor spelling could be dropped): 32 green, 1 NOT MEASURED —
    check-test-completeness exits 3 (PREREQUISITE NOT MET) because it grades a saved
    turbo run test log that only CI produces; its own text says this is not a red.
  • Also run as named in dispatch: check:ratchet-remedy-authority (183 scripts swept) and
    check:declared-population-live (156 of 200 families reach this tree). Both green.
  • check:dual-build-cjs-loads and check:type-check-debt needed the workspace closure built
    (70/70 tasks) and are green on it; check:type-check-debt --re-measure re-measured 27 ledger
    entries with none above its recorded number.

Generated by Claude Code

…views
An app navigation entry's `viewName` was resolved by nothing at author time.
The schema documents it as "Default list view to open", so an unresolvable
name fell back rather than failing: the entry kept its authored label and icon
and opened a different view, with `os validate --json` reporting valid: true
and `os build` green.
Extends #2554's `lintViewRefs` to the navigation door of the same `listViews`
namespace. Resolution mirrors objectui's `resolveViewId` in all three
directions so a name that works at runtime is never reported.
Part of #14108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️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.

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
  • 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 aca23aba40883b2c181053229fc6748d6f7ad0d5packageMentionDocs.

Which tree this was computed on

This run read content/docs from e3ca88f05ecf0f4581811fca4fc1a019a0fe945c — the merge of head 43f68b243c5a0c29073c193fb6705d1bf774669a into base aca23aba40883b2c181053229fc6748d6f7ad0d5, 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 e3ca88f05ecf0f4581811fca4fc1a019a0fe945c && git checkout e3ca88f05ecf0f4581811fca4fc1a019a0fe945c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca23aba40883b2c181053229fc6748d6f7ad0d5 43f68b243c5a0c29073c193fb6705d1bf774669a && git checkout -B drift-repro aca23aba40883b2c181053229fc6748d6f7ad0d5 && git merge --no-ff 43f68b243c5a0c29073c193fb6705d1bf774669a
node scripts/docs-audit/affected-docs.mjs --json aca23aba40883b2c181053229fc6748d6f7ad0d5

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

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App navigation viewName is never resolved against the target object's listViews — a typo silently lands the user on the default view

2 participants

@baozhoutao@claude