Skip to content

Clear three platform lint rule families (31 of 90 warnings), and report the rest with named reasons - #1605

Merged
os-steve merged 4 commits into
mainfrom
claude/issue-1596-zero-platform-lint-warnings
Sep 5, 2026
Merged

Clear three platform lint rule families (31 of 90 warnings), and report the rest with named reasons#1605
os-steve merged 4 commits into
mainfrom
claude/issue-1596-zero-platform-lint-warnings

Conversation

@os-steve

@os-steve os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1596

objectstack lint: 90 warnings to 59, one commit per rule id, exit 0 and pnpm verify green. No rule is suppressed, whitelisted or locally re-severitied, and no warning is bought by bending metadata around a rule.

⚠️ This is a partial zero. Three of the five warning families are cleared; the other two are left standing on purpose, each with a named reason and evidence, below. scripts/wow1-live-schema.sh is not deleted — the premise that nothing names it is false.

Both dispatched premises, measured

1. "90 warnings / 12 suggestions" — HOLDS. That figure came from the director seat's census on #1579 and nobody had re-run it (no node_modules in the checkout). Fresh install on this branch's base 81a79ee: 90 warnings, 12 suggestions, unchanged. The commit plan survives.

2. "scripts/wow1-live-schema.sh is wired to nothing" — FALSE. Not deleted; stopped and reported, as instructed. The card's narrow claim is true — no package.json script and no workflow names it — but git grep wow1 across the whole repo finds six published documentation pages that tell a reader to run it, in three locales:

page line
content/docs/ai-copilot/live-schema.mdx "Run the full demo with:" then ./scripts/wow1-live-schema.sh
content/docs/ai-copilot/live-schema.zh-Hans.mdx · .zh-Hant.mdx same block
content/docs/whats-new.mdx "Try it in 30 seconds with the script we ship:" then HOTCRM_TOKEN=... ./scripts/wow1-live-schema.sh
content/docs/whats-new.zh-Hans.mdx · .zh-Hant.mdx same block

Plus .changeset/header-position-prose-integers.md, whose prose asserts "the one .sh (scripts/wow1-live-schema.sh)". Deleting the file would leave six shipped pages instructing readers to run something that does not exist. Removing it is a docs decision, not a cleanup rider — it needs its own card.

What is fixed

translation-target-unknown — 4 warnings. _sections.sla_overview was translated in all four locales and named nothing on crm_case: not a fieldGroups[].key (the SLA group's key is sla), not a named form-view section (case.view.ts names case and how_can_we_help), not a named record:details section (case_detail.page.ts names info / status / description). The rule's suggested alternative, sla, is already present and already translated as the fieldGroup key, so the fix is deletion, not renaming. Grepping src/ finds sla_overview only in those four files. Not user-visible: an entry keyed to nothing rendered nowhere.

view/layout-without-binding — 1 warning. account_map declared type: 'map' with no map block, so it was bound to nothing: the renderer falls back to literal default field names, and the view draws no marker while authoring reports success. Now binds locationField: 'office_location'crm_account's one Field.location(...), and the only coordinate-carrying field on it; billing_address is an address, a postal shape, so it stays a column — with titleField: 'name' to match the view's own leading column. User-visible: the map can plot.

absolute-colspan-discouraged — 26 warnings, 11 files. A form's column count is derived per surface (mobile 1 / modal 2 / page 3-4), so an absolute colSpan only lines up at the one width its author imagined. Every converted site sits in a columns: 2 section, so neither intent is guessed at:

  • 24 colSpan: 2 sites — "span both columns", i.e. the whole row — become span: 'full', the same intent stated independently of the surface. At a 4-column page these were previously half a row.
  • 2 colSpan: 1 sites (salutation, first_name on the lead form) lose the key rather than gaining span: 'auto'. That is already the default, and materializing a default rewrites "the author said nothing" into "the author asked for the default".

span: 'auto' | 'full' is declared by the pinned @objectstack/spec@17.3.0 form-field schema and read by the published form renderer (resolveColSpan resolves 'full' to the full grid and treats colSpan as the legacy arm it clamps) — checked against the published artifact, not the source tree. The showcase docblock in lead.view.ts, which advertised colSpan to readers as a field-level control, is updated too: this file is what other authors copy from.

What is NOT fixed, and why

component-props-invalid — 16 warnings. Refused: the fix would delete every header button from four record pages.

The rule is right about the contract. PageHeaderProps.actions is z.array(z.string()) ("Action IDs to show in header") and these four pages author whole ActionDef objects. But the fix depends on a renderer that is not published:

  • objectui#6252 gives page:header the id-resolution path, and objectui#7182 rules that ids are the contract (maintainer, 2026-09-02, option C).
  • npm view @object-ui/components version = 17.6.0; objectui main HEAD's changelog head is also 17.6.0, and both changesets are still pending, unconsumed in its .changeset/.

⇒ Merged, not released — the same trap as --strict on #1581, and exactly the unlock predicate hotcrm#1279 already warns about ("a read of the published artefact"). On the renderer this repo's console runs, an id-authored array resolves nothing: Convert Lead, Generate Quote, Escalate Case and the activity trio would vanish, with no other entry point on a custom record page. Left in place; #1279 updated with the measurement and the now-narrowed unlock predicate. The four KNOWN_UNCONFORMING lines in test/metadata-references.test.ts are untouched.

component-props-unknown-key — 1 warning. Owned by #1216. sales_home_page/ai_briefing authors properties.description, which page:card does not declare, so the paragraph renders nowhere. The fix is to move the copy into an element:text child — but the copy is pinned in place by the #1002 persona guard, which reads properties.description, asserts it is a string, and encodes a maintainer ruling. Relocating it means rewriting a ruling-backed guard in test/**, which is #1216's job and outside this card's file surface.

flow-loop-body-uncontained — 42 warnings. Stopped on file surface; filed as #1604 with the fix already measured.

Ten scheduled sweeps abort on their first failing record. The fix is real, entirely inside src/flows/**, and was written and verified on this branch before being reverted: 42 to 0, lint exit 0, typecheck exit 0, token ratchet clean with 155 tokens of headroom (the literal 22-times spelling does not fit — its re-indentation alone put business semantics ~1,327 tokens over a ceiling that only moves on a maintainer ruling; a guarded() helper that leaves each body's indentation untouched does fit). try_catch region execution and the bare-assignment handler were both checked against the pinned 17.3.0 engine, not the platform source.

It is not in this PR because landing it reds 14 assertions across 8 files under test/** — hand-rolled flow-graph walkers that descend config.body.nodes and stop, plus one that builds a live-engine fixture by mutating loop.config.body.edges. This card's file surface is "whatever objectstack lint names", which never names a test file, and those same 8 files are what epic step 3 (#1582 to #1587) is queued to rework, each Blocked-by #1581. #1604 carries the full plan, the helper, the token measurements and the walker inventory so none of it is rediscovered.

Suggestions

12, unchanged and not addressed (the card reports them by count): approval-approvers-may-resolve-empty 4, rollup/missing-summary 2, relationship/line-items-inline-edit 2, relationship/line-item-should-be-master-detail 2, object/missing-name-field 2.

No upstream findings filed

Every one of the 59 remaining warnings is correct about this metadata. None is a rule that is wrong here, so there is nothing to file in objectstack-ai/objectstack; the blockers are a pending objectui release, a ruling-backed local guard, and a file-surface boundary. Filing a platform card for any of them would be noise.

Verification

pnpm verify                 exit 0
  objectstack validate      exit 0
  tsc --noEmit              exit 0
  objectstack lint          exit 0 — 59 warning(s), 12 suggestion(s)   (base: 90 / 12)
  lint:i18n-gate            exit 0 — 0 i18n/missing-* issues
  hygiene                   exit 0
  hygiene:tokens            exit 0 — "source token ratchet clean"
  objectstack build         exit 0
  vitest run                160 files passed, 3391 passed, 1 skipped

Base re-measured at 81a79ee after a fresh install. Changeset carries real frontmatter ('hotcrm': patch), not the empty declaration: two of the three families change what a user sees.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj


Generated by Claude Code

…-target-unknown)

Clears rule `translation-target-unknown` — 4 warnings, one per locale.

`crm_case` declares no `sla_overview` section anywhere: not as a
`fieldGroups[].key` (the SLA group's key is `sla`), not as a named form-view
section (`case.view.ts` names `case` and `how_can_we_help`), and not as a named
`record:details` section (`case_detail.page.ts` names `info` / `status` /
`description`). The four `_sections.sla_overview` entries therefore translated
nothing — the heading they were written for no longer exists.

The comment above each entry claimed the key was a form section name on
`case.view.ts`; it is not, and grepping `src/` finds `sla_overview` only in
these four files. Deleting the entries is the fix the rule prescribes: the
alternative it suggests (`sla`) is already present and already translated, as
the fieldGroup key.

No user-visible change: an entry keyed to nothing rendered nowhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
…ithout-binding)

Clears rule `view/layout-without-binding` — 1 warning.

`listViews.account_map` declared `type: 'map'` and no `map` block, so it was
bound to nothing: the renderer falls back to literal default field names, and
the view renders empty while authoring reports success. The rule's prescribed
spelling is `map: { locationField: '<location_field>' }` (or an explicit
latitude/longitude pair).

`crm_account.office_location` is the object's `Field.location(...)` and the one
field on it that carries coordinates, so it is the binding. `billing_address`
is an `address` field — a postal shape, not a coordinate one — and stays a
column. `titleField: 'name'` labels the marker with the same field the view's
own `columns` lead with.

User-visible: the map view can now plot its markers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
…-colspan-discouraged)

Clears rule `absolute-colspan-discouraged` — 26 warnings across 11 view files.

A form's column count is DERIVED per surface (mobile 1 / modal 2 / page 3-4),
so an absolute `colSpan` only lines up at the one width the author imagined and
is clamped everywhere else. The rule's prescribed primitive is the relative
`span: 'full'` (whole row at any column count), or omitting the key for auto
width — both declared by `@objectstack/spec@17.3.0`'s form-field schema
(`span: 'auto' | 'full'`) and both read by the published form renderer, which
resolves `span: 'full'` to the full grid and treats `colSpan` as the legacy
arm it clamps.

Every converted site sits in a `columns: 2` section, so the two intents map
cleanly and neither is guessed at:

  - `colSpan: 2` (24 sites) — "span both columns", i.e. the whole row at this
    density. Becomes `span: 'full'`, which is that same intent stated
    independently of the surface: still a whole row when the page renders 4
    columns, where `colSpan: 2` was only half of one.
  - `colSpan: 1` (2 sites, `salutation` / `first_name` on the lead form) — one
    cell, which is exactly what the section's own `columns: 2` already says.
    The key is dropped rather than translated: `span: 'auto'` is the default,
    and materializing a default rewrites "the author said nothing" into "the
    author asked for the default".

Also updates the showcase docblock in `lead.view.ts`, which advertised
`colSpan` to readers as a field-level control; this file is what other authors
copy from.

User-visible: fields that span a row now do so at every surface width, instead
of only where the derived column count happens to be 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
Real frontmatter, not the empty declaration: two of the three families change
what a user sees (the account map can plot; row-spanning form fields span at
every surface width). The third is invisible by construction — a translation
entry keyed to a section that does not exist rendered nowhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 5, 2026 10:58pm UTC

Request Review

@github-actions github-actions Bot added the metadata Declarative metadata — schema, security posture, UI surfaces label Sep 5, 2026
@os-steve
os-steve marked this pull request as ready for review September 5, 2026 23:03
@os-steve
os-steve added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 8223d0a Sep 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants