Skip to content

fix(lint): one-line fix lines for the 11 validate-widget-bindings rule ids (slice 17); os explain RULE_ID carries what they explained - #23581

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22161-s3-lint-slice-17-widget-hints
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22161-s3-lint-slice-17-widget-hints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22161
Clause-②: no

Slice 17 of the card: the over-long fix: lines, continued in slices 11 to 16's shape, for the whole-file slice packages/lint/src/validate-widget-bindings.ts. Each finding's hint (the CLI's fix: line, the runtime issue's hint) of the 11 rule ids in that file whose hint ran past 200 characters is now one remedy line of at most 200 characters, and what the lines used to explain is each id's RULE_EXPLANATIONS entry behind os explain RULE_ID. The card stays open: other author-time ids still carry a hint over 200 characters (the last section).

Claim: 6180782552 (newest Claim: on the card, naming this branch; verified before the first edit).

What changes

  • One remedy line per finding. The 11 ids, every hint the lint suite fires, before → after (count of distinct hints · length range), all measured with the same census preload as slices 15 and 16, at the base 086fa4640 and at this head:
rule id lint suite, before → after runtime door (dashboard write)
dashboard-filter-field-unprovisioned 3 · 551–561 → 2 · 175–177 2xx advisory
chart-field-unknown 5 · 299–415 → 5 · 125–152 2xx advisory
dashboard-filter-field-unknown 10 · 185–403 → 6 · 139–148 422
chart-measures-missing 2 · 252–369 → 3 · 100–144 2xx advisory
widget-legacy-analytics-shape 1 · 338 → 2 · 112–113 2xx advisory
widget-measures-missing 8 · 307–330 → 9 · 131–154 2xx advisory
widget-filter-field-unknown 3 · 211–322 → 1 · 133 422
widget-legacy-analytics-unrenderable 1 · 320 → 1 · 105 422
table-count-only 1 · 311 → 1 · 136 2xx advisory
chart-dimensions-missing 1 · 291 → 1 · 161 2xx advisory
dashboard-filter-field-not-included 1 · 218 → 1 · 144 422

The base maxima equal the claim's lengths id for id (561, 415, 403, 369, 338, 330, 322, 320, 311, 291, 218). A count that falls is a list that no longer makes each hint distinct; a count that rises is a case this slice added (a five-measure dataset, a dataset with no measures, a one-key legacy widget).

  • Lists are no longer echoed (slices 11, 12, 14 and 16's rule):
    • dashboard-filter-field-unknown and widget-filter-field-unknown no longer append the object's field list (account.detail on the dotted arms, Object fields: on the bare arms). The verdict's "Did you mean" names the nearest field on the dotted arms; the bare dashboard-filter arms keep their own "Did you mean" after the remedy.
    • dashboard-filter-field-not-included no longer lists the dataset's declared include paths: the remedy names the one to add.
    • One bounded clause stays: the three empty-selection ids (chart-measures-missing, widget-measures-missing, chart-dimensions-missing) have no "Did you mean" to point at, so they still name what the dataset declares, bounded to the first three names and then an ellipsis (a new local declaredFew()), or "it declares no measures yet". This is a deviation from a bare remedy, stated so the reviewer can rule on it.
  • The reasoning moves, the remedy stays.
    • The suppression clause leaves every warning's fix line; each entry already carried it, and the two new entries carry it too (slice 14's unconsumed-widget-option precedent).
    • dashboard-filter-field-unprovisioned now prints the shared one-line unprovisionedAnchorFix() the other anchor ids print, and its entry gains the shared UNPROVISIONED_ANCHOR_REMEDY paragraph (columnMap, ownership: 'none', systemFields: { audit: false }); the widget's filterBindings opt-out and the suppression were already in the entry.
    • The two field-existence ids' entries gain a "to fix it" paragraph: a dotted re-target or filter key must traverse relationship fields (lookup, master_detail, user or tree), its prefix must be in include, and the verdict's "Did you mean" names the nearest field.
    • chart-field-unknown's entry gains what replaces each refused key (x-axis from the first dimension, y-axis bindings one per mark from values, series membership from values and a second dimension to split) and the house os migrate meta --from 17 sentence, which left the x-axis hint.
    • The three empty-selection entries say the fix: line names up to three declared names.
  • Two ids gain an os explain entry, so their rule: line now carries the pointer: table-count-only (why a count-only, dimensionless table renders one summary row; that a flat record listing is an object-bound ListView, ADR-0017; the suppression) and widget-legacy-analytics-shape (the cutover's removed keys, that DatasetWidget reads only the dataset, that strict parse preempts the rule on compile / validate, the suppression). Both ids already had a rule id constant on the root barrel (TABLE_COUNT_ONLY, WIDGET_LEGACY_ANALYTICS_SHAPE), so no published name is added and Clause-②: no holds. Seven existing entries gain text; none is removed.
  • Nothing else moves. Every message, rule id, severity and path is unchanged, and so is what the rule accepts or refuses. Every hunk in the rule file is a hint expression, a comment, the new declaredFew() helper, the removed datasetHint / suppressHint / fixHint / shapeHint locals, and the import swap unprovisionedAnchorHint → unprovisionedAnchorFix (the long form is still used by two other rule files).

File surface: the claim's

The claim (6180782552) names validate-widget-bindings.ts and its tests, rule-explanations.ts (entries for these ids only), the consumer tests that pin these hints' text, and .changeset/22161-s17-*.md. The diff is exactly four paths: those two source files, the one test file, and .changeset/22161-s17-widget-binding-fix-lines.md. The one shared-text edit outside the eleven entries is the docblock of UNPROVISIONED_ANCHOR_REMEDY, which now lists dashboard-filter-field-unprovisioned among its users.

Pin sweep, repo-wide (examples and docs included): git grep for each id, its constant and every distinctive old hint phrase. The only files pinning these hints' text are in validate-widget-bindings.test.ts; examples/app-showcase/test/dashboard-filter-vocabulary.test.ts, runtime-gate.test.ts, validate-chart-bindings.test.ts, validate-react-page-props.test.ts and metadata-protocol's protocol.runtime-gate-stored-universe.test.ts name the ids but read no hint of them; no docs transcript quotes one. So no packages/cli or packages/metadata-protocol rider was needed. Each flipped pin asserts the new text exactly, or the fact it still carries (the dimension clause on a measureless chart, the 'metric' or 'kpi' alternative, the declared names), plus the absence of the moved list.

The runtime wire

All 11 run at the runtime publish gate: validateWidgetBindings is CLI_AND_RUNTIME with runtimeTypes: ['dashboard']. Measured at the door with runRuntimeAuthoringRules, a scratch probe of 15 dashboard writes run against the base rule sources and the branch's:

  • 422 issues[].hint: widget-legacy-analytics-unrenderable 320 → 105; dashboard-filter-field-unknown 196 / 195 / 290 → 148 / 147 / 137 (an inherited bare field, a re-targeted bare field, a dotted path); dashboard-filter-field-not-included 213 → 139; widget-filter-field-unknown 220 → 126.
  • 2xx advisories[].hint: widget-legacy-analytics-shape 338 → 112; dashboard-filter-field-unprovisioned 550 → 173; chart-field-unknown 413 / 294 / 324 → 150 / 124 / 127 (x-axis, y-axis, series); chart-measures-missing 240 → 96; widget-measures-missing 310 → 134; chart-dimensions-missing 297 → 156; table-count-only 311 → 136.

Readers of hint, read-only against the local objectui checkout at b434f5f47: saveAdvisoryToast.ts prints an advisory's hint after its message, so the seven advisories shorten in Studio's save toast; DraftChangesPanel.tsx reads hint only for its in-browser security-posture pass; no source reads a 422 issue's hint. REST forwards issues / advisories as JSON. No reader in either repository matches on hint text. The changeset states FROM → TO.

Tests

Every heavy run went through scripts/pm/os-verify-lock.sh (slot issue-22161-s17).

  • The rule's own suite pins the new shape. validate-widget-bindings.test.ts gains a slice-17 block. It records every finding of the 11 ids the file fires, and holds every recorded fix: line to one line of at most 200 characters with none of the moved clauses (suppressWarnings, Object fields:, Fields on ", Declared include paths, the old declared-list spellings). A coverage control names every id and every multi-arm hint (dashboard filter dotted / explicit / inherited; chart x-axis / y-axis / series / declared-but-unselected; measures with and without dimensions). Exact pins read the three-name bound with its ellipsis, the no-measures wording, and both legacy lines; a check per id asserts that explainRule(id) holds what the line stopped saying.
  • Lint suite: base 086fa4640 Test Files 139 passed (139), Tests 6,738 passed, 5 skipped; branch efc9582c3 139 / 6,754 passed, 5 skipped; VERDICT command-exit 0 both; at the final head 33460e98b (after the merge and the fix below, lint rebuilt) Test Files 139 passed (139), Tests 6,759 passed, VERDICT 0.
  • Census over the whole lint suite: base 231 ids fire, 44 with a hint over 200; this head 231 ids, 33 over 200. The difference is exactly these 11.
  • Typecheck and build: the CLI closure build at efc9582c3, and again a lint build at the final head 33460e98b, and pnpm --filter @objectstack/lint run typecheck (tsc --noEmit + check:test-typecheck OK, 2 files / 6 errors / 2 pinned signatures held), VERDICT command-exit 0.
  • CLI unit tier (--project unit): Test Files 289 passed (289), Tests 4,317 passed, VERDICT 0; none of the 11 ids fires there.
  • @objectstack/metadata-protocol: Test Files 236 passed, 3 skipped (239), Tests 28,409 / after the merge 28,412 passed, 19 skipped, VERDICT 0; only dashboard-filter-field-unknown fires there (140 characters after; the probe above carries FROM).
  • @objectstack/spec test:repo: its first run at the merge head turned retired-key-migrate-sentence.test.ts red (3 failed of 1,033). The os migrate meta sentence moved into chart-field-unknown's entry was split across two literals and was not the house sentence. It is now the house sentence in one plain-quoted literal, and that test file passes (15 / 15). At the final head 33460e98b the whole project: Test Files 61 passed (61), Tests 1,033 passed, VERDICT 0.
  • os validate (built CLI) on app-crm, app-todo, app-showcase and app-multi-package: exit 0 each, 9 / 7 / 68 / 3 rule: lines, and none of the 11 ids fires on any of them, so the apps' output is unaffected by this slice. app-showcase first exited 1 before any rule ran (Cannot find module …connector-slack/dist: an unbuilt dependency, not a verdict); it passed after a locked build of its closure.
  • Ablation: one-shot, from the committed head baa3efa02, through scripts/ablation-replace.mjs under the lock, with a shell trap restoring HEAD by absolute path. The rule suite imports the rule source, so there is no dist leg.
    • Mutation: table-count-only's hint gains back its suppression and ADR-0017 tail (anchor x1 → x0, replacement x0 → x1, blob d5b1b5debf34 → f9431ca5f49a).
    • Predicted before the run: exactly two cases red (the table-count-only exact-line pin, and the slice-17 bound case).
    • Observed: Tests 2 failed, 176 passed (178), exactly those two.
    • Restored: blob d5b1b5debf34 == HEAD, git diff HEAD empty, git status --porcelain empty.
  • ESLint, narrowed: npx eslint --no-inline-config --format json over the three changed .ts files: 3 files, 0 errors, 0 warnings. The config enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move. A control-character scan of every changed file found no match.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 62 derived at the final head 33460e98b (4 paths vs merge base 252775d1d), each run with its exit code captured before any pipe: all 62 exit 0, check:type-check-debt included. --ran: "62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED (a DERIVED zero — all 62 recorded an exit code and none of them is 3)". The artifact-roster and PR-context families are CI's. At baa3efa02 all 62 exited 0, check:dual-build-cjs-loads after a first refusal (exit 3, PREREQUISITE NOT MET: unbuilt dists, while another locked build was running) and a rerun.
  • Merge: origin/main merged once through scripts/pm/os-regen-merge.sh, without conflict, no regeneration owed (the branch edits no generated artifact; main brought packages/cli, packages/metadata-protocol and packages/spec test changes, no packages/lint file).

Acceptance notes

  • The bounded declared-name clause on the three empty-selection ids is the one place this slice keeps a list. No "Did you mean" covers it, and the names are the remedy's argument. If the ruling is a bare remedy, dropping it is a one-line change per id.
  • A long name can still lengthen a line. The names in a hint are the author's: the dashboard filter, the object, the dataset, the relationship prefix, the field, the widget type. The closest measured case is dashboard-filter-field-unprovisioned at 177 (the shared anchor fix with the object and column names).
  • Some hints lose a clue a short list gave. Where no near miss exists, the old field-existence lines listed what exists and the new ones name the remedy only. That is the earlier slices' rule.
  • Census blind spot: the census preload records finding-shaped objects pushed onto arrays. Three data-model-rules.ts ids slice 16 listed (object/missing-name-field, rollup/non-numeric-aggregand, field/select-missing-options) do not appear in this census at the base or at the head, so their current lengths are NOT MEASURED here.

Remaining for this card

The lint census at this head has 33 ids with a hint over 200, longest first, by file. Of slice 16's remaining author-time list, still over 200 here:

  • lint-flow-patterns.ts (2): flow-double-brace-interpolation 1413, flow-bare-dollar-reference 1234
  • data-model-rules.ts (3 measured): unique/legacy-organization-composite 578, unique/unscoped-declared-index 380, unique/double-declaration 279 (the other three NOT MEASURED, above)
  • validate-capability-references.ts (1): capability-reference-unknown 241
  • lint-liveness-properties.ts (1): liveness-planned-property 230
  • validate-page-field-bindings.ts (2): page-section-count-not-multi-value 228, page-field-unknown 206
  • validate-list-view-mode.ts (1): list-view-filters-in-views-mode 219

The other 23 over 200 at this head are the 3 repo-gate ids (startup-open-vocabulary-verdict, startup-verdict-assertive-wording, startup-source-unparseable) and 20 ids outside slice 16's author-time list, which this slice did not reclassify against PR #22448's fenced list (slice 16 counted 21 fenced): the lint-flow-patterns.ts flow ids flow-loop-body-uncontained, flow-multi-write-unfiltered, flow-try-catch-without-catch, flow-decision-inclusive-overlap, flow-approval-revise-target-not-service-owned, flow-time-relative-antipattern, flow-date-equality-filter, flow-runas-unscoped, flow-decision-mode-invalid, flow-decision-unconditional-branch, flow-error-label-not-fault, flow-branch-label-unmatched, plus page-field-unprovisioned, print-page-block-unprintable, flow-template-field-unprovisioned, flow-template-lookup-traversal, page-section-group-unknown, form-section-group-unknown, filter-token-unknown and component-props-invalid. Each next slice should reclassify against PR #22448's own list before claiming.


Generated by Claude Code

… (slice 17) (WIP)

Each hint of the eleven ids is one remedy line of at most 200 characters;
what the lines explained moves into RULE_EXPLANATIONS, behind os explain.
table-count-only and widget-legacy-analytics-shape gain an entry.

Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
Co-authored-by: Claude <noreply@anthropic.com>
…rate meta sentence (WIP)

spec's repo-tier pin judges every os migrate meta prescription in packages/lint/src;
the moved sentence is now the house sentence, in one plain-quoted literal.

Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 6 documentable anchor(s).

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

  • content/docs/releases/v16.mdx (via validateWidgetBindings (symbol, a top-level function))
  • content/docs/releases/v17/17-1.mdx (via validateWidgetBindings (symbol, a top-level function))

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

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 4 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 f3fd209b34c6c0c5f815ddc195ec70b6a64abaed → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c6cfd50a88191d540d36559709c1abac5a86609d — the merge of head 33460e98bd632fe274a136c348ded90bedc415f3 into base f3fd209b34c6c0c5f815ddc195ec70b6a64abaed, 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 c6cfd50a88191d540d36559709c1abac5a86609d && git checkout c6cfd50a88191d540d36559709c1abac5a86609d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f3fd209b34c6c0c5f815ddc195ec70b6a64abaed 33460e98bd632fe274a136c348ded90bedc415f3 && git checkout -B drift-repro f3fd209b34c6c0c5f815ddc195ec70b6a64abaed && git merge --no-ff 33460e98bd632fe274a136c348ded90bedc415f3

node scripts/docs-audit/affected-docs.mjs --json f3fd209b34c6c0c5f815ddc195ec70b6a64abaed

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

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

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants