Skip to content

fix(lint): name the metadata-editing surface, not a *.form.ts filename - #8183

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8042-name-the-surface-not-the-file
Aug 12, 2026
Merged

fix(lint): name the metadata-editing surface, not a *.form.ts filename#8183
os-zhuang merged 1 commit into
mainfrom
claude/issue-8042-name-the-surface-not-the-file

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8042

The metadata-layer arm of the visibility diagnostics explained the layer by naming *.form.ts — a file a Studio / REST /meta / MCP author of a schema-bound view does not have and cannot open. This names the surface instead: a metadata-editing form, the row under edit.

Prose only. No rule id, severity, prescribed root or firing condition moves; the actionable half of every message and hint is byte-stable.

The premise, measured rather than assumed

Before touching anything, the publish-gate path was probed with the gate's own call shape — validateVisibilityPredicates(stack), no options, which is exactly how authoring-rules.ts calls it. A form view declaring data: { provider: 'schema', schemaId } emitted both graded strings, each naming a file:

visibility-root-mislayered (warning)
"...not a metadata-editing form. A `*.form.ts` predicate that binds `record.` never matches..."
visibility-bare-identifier (error)
hint: "Write `data.status` instead of `status` (a `*.form.ts` metadata-editing form binds the row under edit as `data`)."

Both reachable, both file-naming, prescriptions already correct. Premise holds.

What changed, and the discrimination behind each site

The rule file carried 8*.form.ts occurrences (counted, not eyeballed). The question applied per site was: is this string read by an author of the metadata, who may hold no source file — or is it explaining the caller contract to someone reading or maintaining the linter, where the filename is accurate and is the point?

Rewritten — emitted prose an author reads (3):

sitewhy
MISLAYER_BY_LAYER.metadata.messagegraded target; read at the publish door by an author with no file
visibility-bare-identifier hint, metadata armgraded target; same audience, and this one gates
MISLAYER_BY_LAYER.runtime.messagethe mirror sentence, same defect — see below

Broadened — module and type docs that DEFINE the layer (3): the module note, the VisibilityLayer doc, and the CANONICAL_ROOT_BY_LAYER doc each defined the metadata layer as *.form.ts files. Since #7815 that definition is incomplete rather than merely surface-specific: a schema-bound form view is on this layer with no file at all. Each now names both routes.

Retained deliberately (2): the opts.layer option doc ("...which is what a file-aware caller linting a *.form.ts does") and the validateVisibilityPredicates doc ("pass opts.layer = 'metadata' when linting a *.form.ts metadata-editing form"). Both address a caller that is genuinely linting a file — the option exists precisely for that caller — so the filename is both correct and the useful word. A blanket find-and-replace would have deleted these, which is why one pin asserts they are still present.

Net: 8 occurrences to 6, and zero remaining inside any emitted string. All 6 survivors are comments.

One judgment call beyond the graded scope, flagged for review

The graded targets are the two metadata-arm strings. I also rewrote the runtime arm, which explained its forbidden data. root as "a *.form.ts row under edit" and named the runtime surface as "a *.view.ts / *.page.ts live record".

The reasoning: a view or page published through Studio has no *.view.ts either, so the same finding applies, and the graded metadata message names *.view.ts / *.page.tsin the same sentence it names *.form.ts. Fixing only the graded half would have left one sentence half-pointing at files. Happy to narrow this back if review reads it as out of scope — it is isolated to one message string.

Tests

Six pins in a new #8042 block, all on the no-options call (the gate's shape):

  • the mis-layered advisory and the bare-identifier hint name no source file, still fire, and keep their exact prescription
  • the runtime arm is file-free too
  • a mechanical sweep: one case per rule id per layer (8 rows), asserting no emitted message or hint matches a *.form.ts / *.view.ts / *.page.ts spelling, with a non-vacuity guard per row so a case that goes blind fails instead of passing
  • the file-aware caller path emits the same prose (the strings are keyed by layer, not by how the layer was decided — pinned so a later "fork the wording by caller" is visible; one condition, one wording)
  • the retained caller-contract mentions are still in the source

Reverse verification — direction predicted before running: revert the source, keep the tests, and the prose pins go red while the caller-contract pin stays green (origin/main still carries that wording). Measured exactly that: 4 failed, 113 passed, and the 4 are precisely the prose pins. That asymmetry is what separates "changed the right thing" from "changed everything".

Note on the suggested negative control: the emitted strings cannot be forked by caller path, because MISLAYER_BY_LAYER is keyed by layer, not by how the layer was derived. So "the file-aware path still produces its retained wording" is not an available assertion — the file-aware caller reads the same prose. That is pinned as an equality instead, and the retained-wording control moved to the source-level pin.

Verification

  • pnpm --filter @objectstack/lint test — 72 files, 1954 passed, 4 skipped. runtime-gate.test.ts untouched and green.
  • validate-visibility-predicates.test.ts — 117 passed (was 113 pass + 4 red under reverse verification).
  • pnpm --filter @objectstack/lint typecheck — clean. eslint on both changed files — clean.
  • check:nul-bytes OK (7428 files). check:spec-parsed-alias OK.
  • Gate set re-derived against the real diff: check:changeset-gate-self-tests, check:cross-package-test-inputs, check:objectui-changeset all green. check:objectui-pin-fresh is red and pre-existing — it matched only because this PR adds a changeset, my diff touches no .objectui-sha, and it fails identically on a pristine tree (the pin has drifted behind upstream objectui since a0151e9).

Changeset added: @objectstack/lint is published, so prose that changes what an author reads still ships as a patch.


Generated by Claude Code

…ame (#8042)
Since #7815 the metadata layer is reachable at the runtime publish gate for a
form view declaring `data: { provider: 'schema', schemaId }`. That door's
audience is a Studio / REST `/meta` / MCP author who holds no `*.form.ts`, so
`visibility-root-mislayered` and `visibility-bare-identifier` prescribed the
right root and then justified it by naming a file the reader cannot open.
Prose only — no rule id, severity, prescribed root or firing condition moves,
and the actionable half of every message and hint is byte-stable. The mirror
(runtime) arm named `*.view.ts` / `*.page.ts` the same way and is fixed with
it. The `*.form.ts` mentions addressed to a file-aware CALLER (the `opts.layer`
contract) are kept: there the filename is accurate and is the point.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WocN37om5bw81JDoEEMA2e
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 6:54pm

Request Review

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx(via @objectstack/lint)
  • content/docs/deployment/validating-metadata.mdx(via packages/lint)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint)

1 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/v17.mdx(via @objectstack/lint)

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

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 12, 2026 19:12
@os-zhuang
os-zhuang added this pull request to the merge queueAug 12, 2026
Merged via the queue into main with commit df72328Aug 12, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8042-name-the-surface-not-the-file branch August 12, 2026 19:30
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

2 participants

@os-zhuang@claude