Repository navigation
fix(fields): clamp and report an out-of-range percent scale on both faces (objectui#9808) - #9919
Conversation
… faces A `scale` above the engine's fraction-width ceiling is a declaration `@objectstack/spec` accepts — its `FieldSchema` states "Decimal places (non-negative integer)" and carries no upper bound — and it threw an uncaught `RangeError` out of both percent faces: `PercentField`'s readonly `toFixed` and its spinner `step`, and `PercentCellRenderer` through `formatPercent`. For a React render that takes out the subtree, and the author got no signal: the platform accepted the declaration and failed at render time. Both faces now take ONE ruling, in one shared function: a width outside the domain the formatting APIs define for themselves is clamped into it and reported on the console, naming the declared width, the width rendered and the card. The bounds are not this renderer's policy. `Number.prototype.toFixed` and `Intl.NumberFormat`'s `maximumFractionDigits` define the same 0-100 domain, which is what makes a single ruling over both faces possible. A refusal was declined on the other half of the same reasoning: the declaration is spec-compliant today, so refusing it here would be this renderer inventing a contract stricter than the spec. The declaration-side upper bound is owed and is filed where it lives, in `@objectstack/spec`. An in-range declaration is byte-identical, including at the inclusive ceiling, and nothing here changes which member a face reads or what an absent `scale` means. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
… read `tsc -p tsconfig.test.json` refused the implicit `any` on the spy's call tuple, which `pnpm --filter @object-ui/fields type-check` reports and the vitest run does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…can render
The first cut refused any non-number width, which would have MOVED a cell
that renders today: both formatters coerce for themselves — measured,
`(25).toFixed('2')` and `maximumFractionDigits: '2'` render two decimals, and
so does `2.9`. Off-spec-but-working metadata is not this card's business, and
a silent regression on it is the opposite of what the card asks for.
The predicate is now "the engine could not have rendered this width", so only
widths the engines would have REFUSED move, and a new control pins the two
coerced shapes unchanged and silent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Clause-② contract review ADOPTED — VERDICT: FAIL. The record follows in the next comment, verbatim. ⭐ FAIL is a normal outcome and costs nothing. A PASS that had to be reached is worth nothing; this review earned its keep precisely by not reaching one. ⛔ Nothing here is a criticism of the dev — the declaration is provisional by design and 「声明被复核推翻 ⛔ 不作席位过失」. The tier reading — from the subagent's OWN transcript, ⛔ never
|
| transcript | assistant records | served model |
|---|---|---|
| this review | 107 | 107 / 107 claude-fable-5-1 — the CONTRACT_REVIEW_TIER constant, zero fallback |
| ⭐ FIRING CONTROL — this round's slice-17 dev | 234 | 234 / 234 claude-opus-5 |
| ⭐ FIRING CONTROL — the dev that wrote this very diff | 158 | 158 / 158 claude-opus-5 |
⇒ both controls read the other value, so the field discriminates and the at-tier reading is a reading.
⛔⛔ A sentence of THIS SEAT's is FALSE, and is corrected outright
The Clause-②-correction comment on objectui#9808 said the dev's reasoning 「nothing here widens an accepted set」 was true, and that only the export limb failed.
⛔ That was false when I wrote it. Measured by the review: formatPercent(0.25, 101, 'en') throws RangeError at the branch point and returns a string at head. ⇒ inputs that threw now return — 放宽接受集 on a published function. Clause-② is yes on both limbs, ⛔ not only the export limb.
⇒ ⭐ BORN FALSE, so corrected outright and ⛔ not dated. The shape of my error is worth more than the correction: I reasoned that 「a clamp narrows behaviour」 and stopped, without measuring the published function's own accept set — the very axis the floor is about. ⭐ 「narrows behaviour」 and 「narrows the accept set」 are ⛔ not the same claim, and I conflated them while writing a comment whose whole subject was that two axes must not be conflated.
⛔⛔ And a CI reading of this seat's was TAKEN TOO EARLY
Re-read now: Test (shard 1/4) is completed / cancelled — ⛔ not success. This seat's earlier enumerations read it as in_progress and reported 「0 non-green」, which was true of that instant and ⛔ not of the run.
check_suite.completed event is not a green CI reading — its own caveat excludes cancelled suites」. This seat held that fact and still read too early. ⇒ ⭐ a snapshot of a running matrix is not a verdict about it, and 「0 non-green」 taken mid-run means only 「nothing has failed YET」.
What the review asks for, in its own terms — ⛔ this seat adds nothing
Five items, quoted in the record: move the two new symbols into a module the barrel does not re-export (the package's own address-format.ts shape) or declare minor and name the widened face; record the sunset — the clamp rests on the spec's scale being unbounded, and becomes the lenient renderer-side fallback AGENTS.md #0.1 bans once the upstream bound lands; make the five MUST-NOT-CHANGE controls assert the objectui#9808 marker so they pass in isolation; amend the body's stale Clause-②: no line and the 「SAME domain」 sentence; and get a green re-run of the cancelled shard.
⭐ The control finding is the one worth reading twice: the five controls each end in expect(warn).not.toHaveBeenCalled(), and an earlier out-of-range row in the same file consumes react-i18next's first-render warning — so they pass together and each reads 1 failed when run alone. ⇒ they cannot distinguish 「the clamp stayed silent」 from 「another warning got there first」. A control that only holds in file order is ⛔ not a control.
What happens next
⛔ FAIL strips both carriers exactly as PASS does — done in the same act, with this comment and the handover note on the card as the record. ⛔ The card's state and assignee do not move: it stays claimed and in flight.
⇒ a patch round goes back to the same dev with the record as its brief. ⛔ Nothing is enqueued, flipped ready or merged, and ⛔ no approval review is issued at any point.
Generated by Claude Code
Contract reviewServed-tier: ① Derived judgmentsInstruments, all first-hand: the head tree extracted by
② Semver levelAGENTS.md 版本号策略 (read at ③ Boundary flags
Implemented-by: VERDICT: FAIL — to pass on a new head: (1) move Generated by Claude Code |
…standing Contract review of PR objectui#9919 returned FAIL on five items; this is the patch round for items 1 to 4. The ruling moves out of `PercentField.tsx` into its own `percent-scale.ts`, the shape this package already uses for a rule shared between a widget and the barrel — `address-format`, `numberBadInput` and `file-affordance` are all imported by `index.tsx` and left out of its export-star block. Measured on the built package: neither symbol appears in `dist/index.d.ts`, the type entry `exports["."]` names, with `PercentField` reading 2 there as a lit control, and `exports` carries no deep subpath. So the published surface is unchanged and `patch` stands. The changeset's last paragraph, which claimed the two exports were needed "so both faces share one spelling", is gone: sharing one spelling needs one MODULE, not an entry export. The SUNSET is now written down in both the ruling's docblock and the changeset. The clamp is defensible only while `@objectstack/spec` leaves `scale` unbounded; once objectstack#18972 lands, this becomes exactly the lenient renderer-side fallback AGENTS.md #0.1 bans, and it is to be deleted rather than kept beside the enforced contract. The MUST-NOT-CHANGE controls no longer assert on a `console.warn` call count. That channel is shared — react-i18next's first-render warning lands on it — so each control passed only while an earlier row in the same file had already consumed that first warning. Measured at the reviewed head with the module restored by blob hash, three of them read `1 failed` when run alone; they now assert on the ruling's own marker and each reads `1 passed` alone. Two prose claims the review measured false are corrected where they live: the two formatters agree on the INTEGERS only (`toFixed(100.5)` and `toFixed(NaN)` render, `Intl` refuses either), and the function does NOT only see widths a face worked out — it sits behind `formatPercent`, whose callers include a dashboard metric whose width comes from a numeral pattern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Clause-② re-review at the patched head ADOPTED — VERDICT: FAIL again. Record follows verbatim. ⭐ Four of the prior record's five items are discharged and MEASURED. This is not a repeat failure — it is a narrower one, found because the reviewer re-derived rather than re-read.
⛔⛔ A sentence of THIS SEAT's is FALSE — the third this round, and the same cause each timeThe re-hang note said the module 「pointed at a search instead of freezing the count」. Read by this seat just now, ⛔ BORN FALSE ⇒ corrected outright, ⛔ not dated. ⭐ And the cause is the one this seat keeps naming and keeps repeating: I took the dev's delivery note at face value instead of reading the file. The dev wrote 「the 7-call-site figure is not copied into the code」 and I relayed it. ⇒ ⭐ a hand-back is evidence, ⛔ never a measurement — which is the whole reason this seat re-verifies dev claims, and I skipped it on the one line that was about not trusting prose. ⭐⭐ The finding of the round: the argument's premise expired WHILE THE PR WAS OPENThe module justifies clamp-over-refusal by AGENTS.md #0.1 — 「this declaration is compliant — measured at source … Re-measured by this seat on ⇒ the premise is already false at source, and still true of what objectui installs (lockfile ⭐ And the sharpest part: the module's own text warns that 「an argument whose premise has quietly expired reads exactly like a live one」 — and that is exactly what happened to it, inside the same PR, within ten minutes. ⇒ the reviewer's fix is right: the sunset needs an instrument, not a sentence. A pin that parses
|
Contract reviewServed-tier: ① Derived judgmentsInstruments, all first-hand: the head, previous-head and branch-point trees extracted by
② Semver levelAGENTS.md 版本号策略 (read from ③ Boundary flags
Implemented-by: VERDICT: FAIL — items 1, 3, 4 and 5 of the prior record are discharged and measured; item 2 is discharged on its letter only. To pass on a new head: (1) give the sunset an instrument — a row in the pin file that parses a percent field with Generated by Claude Code |
…t premise Re-review of PR objectui#9919 discharged four of the prior five items and returned FAIL on four narrower ones. This is that round. The clamp no longer covers the whole domain. It fires on exactly the set the installed spec ACCEPTS and the engine REFUSES — a non-negative integer above the ceiling — and returns every other width untouched. `-1`, `Infinity`, `NaN`, `2.5` and a string `'101'` are declarations the installed `FieldSchema` already refuses (measured through its own door, with a bogus-key control proving the door reads the document), so rescuing them here was the renderer-side default around bad input that AGENTS.md #0.1 lists by name — and it was doing it inside the module that cites #0.1 as its reason for existing. They keep the path they had before this ruling existed, and the two pins that asserted the old behaviour are INVERTED rather than deleted, so the decision itself is what fails if someone re-widens it. The sunset has an instrument instead of a sentence. A new block parses a percent field declaring one more than the ceiling through the installed `FieldSchema` and requires acceptance; when a spec bump answers differently it goes red and its message says to delete the module, its two call sites and that file. Proved failable: with the probe pointed at a width the door already refuses, the row fails and prints SUNSET REACHED, restored by blob hash. The docblock premise now reads as what this repository INSTALLS (the lockfile's 17.4.0), not as what another repository's main says. That distinction is not pedantry: the upstream bound merged ten minutes after the previous head was committed, so the premise was true when written and false at source before the body was even amended, while remaining true of the installed version. The sunset trigger is stated as a spec release this repository takes. The frozen "seven" call-site count is gone from the module; only the pointer at the search remains, per AGENTS.md #9 — a figure that is still correct is the dangerous case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Contract review ADOPTED — verdict PASS, at tier, on head The record follows in the next comment, verbatim. ⛔ Adoption is whole or not at all: this seat adopts it unedited, and ⛔ does not polish, trim or re-argue any line of it. Tier, measured — ⛔ not self-reportedRead from the review subagent's own transcript (
⭐ The control is the point: an instrument that returns one value for everything measures nothing. It returned the other value, so the tier reading discriminates and both cells are lit.
What adoption does and does not settle
Read at 2026-09-18T19:13Z. Generated by Claude Code |
Contract reviewServed-tier: Instruments, all first-hand: the head sha and the PR base sha ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS — at head Generated by Claude Code |
|
Provenance —
⛔ Correction to the adoption note
|
Fixes #9808
Clause-②: yes
⛔ Amended from
no, per the contract review on this PR (5733583803) and adopted by the PM seat. The declaration was provisional and the review overturned it on a limb neither the dev nor the seat had measured:formatPercent(0.25, 101, 'en')throws at the branch point and returns a string at head, so a published function's ACCEPT SET widened. 「a clamp narrows behaviour」 and 「a clamp narrows the accept set」 are not the same claim. The OTHER limb — new published symbols — wasyesat the reviewed head and is now closed: see What the patch round changed below.The defect
A declared percent
scaleabove the engine's fraction-width ceiling crashed BOTH percent faces with an uncaughtRangeError. Re-measured on this container's node (v22.22.2) — the card asks for this rather than for its own constant, because the bound is a runtime property of the engine:Both APIs define the same INTEGER domain,⚠️ Amended from "the SAME domain": the review measured the divergence off the integers and it is real —
0through100inclusive. That agreement is what makes one ruling over both faces possible at all.(25).toFixed(100.5)and(25).toFixed(NaN)both render whileIntlrefuses either. It costs the cell face nothing, becauseformatPercentBodyfalls back totoFixedwhenIntlthrows, so a non-integer or unreadable width rendered through that fallback before this branch and renders the same text now, with a diagnostic added.Reproduced through the components before any edit, and with
scale: 100andscale: 2as lit controls on the same instrument:One reading worth carrying forward: the CELL face reports
toFixed, notIntl.formatPercentBodycatches theIntlRangeErrorand its fallback isdisplayValue.toFixed(precision), which refuses the same width from inside thecatch— the recovery arm is what crashed the render, and neither arm could have rescued the other.The ruling — ONE, and it is CLAMP-AND-REPORT
Both faces route the width they have resolved through a single
renderablePercentScale, which clamps it to the ceiling and emits aconsole.warnnaming the declared width, the width actually rendered, and this card. Deduplicated by declared value, because a percent COLUMN re-renders per row.-1,Infinity,NaN,2.5and a string'101'are declarations the installedFieldSchemaalready REFUSES, measured through its own door with a bogus-key control proving the door reads the document. Rescuing those here would be the renderer-side default around bad input that AGENTS.md #0.1 lists by name — and it would be doing it inside the module that cites #0.1 as its reason for existing. They keep the path they had before this ruling existed, and the two pins that asserted the old clamped behaviour are INVERTED rather than deleted, so the decision is what fails if anyone re-widens it.Why not a refusal. AGENTS.md #0.1 bans a renderer-side fallback that makes NON-COMPLIANT metadata work. This declaration is compliant — and the premise is stated as what THIS REPOSITORY INSTALLS, never as what another repository's
mainsays. The lockfile resolves@objectstack/spec17.4.0 and that door accepts a percent field declaring ascaleabove the ceiling, so refusing it here would be this renderer inventing a contract STRICTER than the one it is built against — #0.1 in mirror image. The declaration-side upper bound was owed and is filed where it lives, as objectstack#18972 in the sibling repository. Nothing in this branch reaches into that repo.Why the clamp is not a guess. The ceiling is not a policy number this package chose; it is the two formatting APIs' own bound, quoted above. The value was never in doubt — only the width — so refusing to draw the number would blank a grid column over a display width. The faces render what the engine CAN render and say that they did; the reporting half is what keeps this from being the silent guess the card refuses.
What this deliberately does not touch. Which MEMBER a face reads, and what an ABSENT⚠️ ⛔ And it does NOT only see a width a percent face worked out — that sentence stood here and the review measured it FALSE. The ruling sits behind
scalemeans — the two faces still spell that absence differently (widget2, cell0), and objectui#9810 holds the separate question ofscalemeaning stored decimals to the write path and displayed decimals to the display path. That card is not addressed here and remains open.formatPercent, whose non-test callers at the time of writing also include the metric widget,recordFields, the detail chip, the Gantt tooltip, the grid footer and the column summary; a dashboard metric's width comes from a numeral-format PATTERN, not from a field face. Every one of them moves from crash to clamp-and-report, which is the same ruling with a wider reach than "both percent faces". ⛔ The live count is whatever a search forformatPercentoutside tests returns today, not a number copied into prose.Files
packages/fields/src/widgets/percent-scale.ts— the shared ruling and its sunset condition.address-format,numberBadInputandfile-affordancealready use here.packages/fields/src/widgets/PercentField.tsx— the edit face's use of it.packages/fields/src/index.tsx—formatPercent, the door the cell face goes through and the one a future caller cannot step around.packages/fields/src/__tests__/PercentScaleOutOfRange-9808.test.tsx— the pins..changeset/9808-percent-scale-out-of-range.md.index.tsximported the ruling fromPercentField.js, an edge its own export-star block already carried, so the import added none. The ruling now lives in its own module, soindex.tsxdoes gain one edge — to a pure, React-free module, exactly as it already does foraddress-format, which is why no widget leaves its lazy chunk. CI's Bundle Analysis is the instrument that answers this, and it ran green on the previous head.Reverse verification — the pins fail for their own reason
a0b61567c0and are kept as the record of that head; the head under review now carries its own ablation, at the end of this body. Run from the COMMITTED state, mutation proved by blob hash BEFORE any result was read, restoration proved the same way and never by an exit code:The eight that go red are every out-of-range row — seven with the uncaught
RangeErroritself — plusdoes not fire AT the ceiling, which reads the exported ceiling and cannot see it on the pre-fix tree. Named rather than smoothed over: that last one goes red for a SPELLING reason, not for the defect.The five that stay green on BOTH trees are the MUST-NOT-CHANGE controls: an ordinary two-decimal percent field, its spinner
step, the cell face, a field declaring noscaleat all, and the engine-coercion pair. Passing on both trees is exactly what makes them controls.⛔ CORRECTED BY THE PM SEAT — the section below quoted a superseded head
2a89d47. A third commit moved the branch toa0b61567c0dfc498c4f01e820abf6ec20326a2f8, which is the head under review. Re-measured there: Tests 2926 passed (not 2925), the pin file carries 13 rows (not 12), and the ablation reads 8 failed · 5 passed. The blob and ablation figures above are the corrected ones. ⛔ The dev writes this body once and does not PATCH it; this correction is the seat's, made because a squash publishes the body verbatim intomain's permanent history.⭐ What that third commit changed, and why it matters more than the counts. The predicate was tightened from 「the value changed spelling」 to 「the engine could not have rendered this width」. The first cut refused any non-number — and measured,
(25).toFixed('2')renders25.00,maximumFractionDigits: '2'renders two decimals, and so does2.9. ⇒ the earlier form would have reported a width nothing moved, and silently regressed a cell that renders today. A new control pins those coerced shapes unchanged and silent.⭐ Independently re-measured by the seat on this container's node:
toFixed(100)ok ·toFixed(101)RangeError ·toFixed(-1)RangeError ·IntlmaximumFractionDigits100 ok · 101 RangeError · and the coercion control(25).toFixed('2')=25.00. ⇒ the inclusive0..100domain and the coercion reading both reproduce.What was run, at
2a89d47(⛔ superseded — see the correction directly above)pnpm --filter '@object-ui/fields^...' build(dependency closure)pnpm --filter @object-ui/fields type-check0lines matchingerror TSpnpm exec vitest run packages/fields/pnpm exec vitest run packages/plugin-detail/ packages/plugin-gantt/(theformatPercentconsumers)pnpm --filter @object-ui/fields lint1038 problems (0 errors, 1038 warnings)— read for the worderror, not for the exit codecheck:control-bytes·check:new-line-citations·check:self-import·check:esm-specifiers·check:test-path-roots·check:changeset-claims·check:pending-changeset-literals·check:side-effects-array·check:unreferenced-sources·check-changeset-presencecheck:governed-queue-guard --testover the four changed pathsNOT GOVERNEDBuild and test runs went through the shared verify lock; each printed its own
VERDICT command-exitline and those are the verdicts quoted.NOT MEASURED locally, and ANSWERED by CI.
check:eager-closureandcheck:readme-exportseach refuse as a BROKEN GAUGE rather than failing without builtdist/output this branch does not produce (apps/console/dist/eager-closure.json, and the plugin packages' type entries); a whole-repo build plus a consolevite builddoes not fit the foreground cap here. The contract review read CI at the previous head and both came back success — Bundle Analysis with the eager closure under budget, and the README Export Check. Repo-wideturbo run lintand the rest of the check farm are likewise CI's.What the patch round changed, and what it measured
The contract review (
5733583803) returned FAIL on five items. It re-measured this branch's reproduction, boundary table, no-module-graph-edge claim and reverse verification as TRUE, and the ruling itself — one function, both faces, clamp over refusal — as RIGHT. The five items and what landed:percent-scale.ts. Measured on the built package rather than argued:PERCENT_SCALE_CEILINGandrenderablePercentScaleeach read0indist/index.d.ts, the type entrypackage.json'sexportsblock names for., withPercentFieldreading2in that same file as the lit control; that block carries only.and./style.css, so there is no deep subpath. The same census puts this module in the same state as the precedents —FileValueAffordanceanduseBadInputRefusalalso read0there. ⇒ the published surface is unchanged,patchstands, and the changeset paragraph that claimed the exports were needed "so both faces share one spelling" is deleted: sharing one spelling needs one MODULE.scaleunbounded, so the trigger is a spec release this repository takes — not the upstream card landing, which has already happened (objectstack#18972, landed as objectstack#19083) and changed nothing here, because the bound is merged and UNRELEASED at the installed 17.4.0. On the day a bump brings it in, the clamp becomes exactly the lenient renderer-side fallback AGENTS.md #0.1 bans and the module is to be DELETED, not kept beside the enforced contract. The second patch round gives that an INSTRUMENT — see below.expect(warn).not.toHaveBeenCalled(), andconsole.warnis a shared channel: react-i18next's first-render warning lands on it, so each control passed only while an earlier row in the file had already consumed that warning. Re-measured here at the reviewed head, with the three source files restored by blob hash and the new module moved off disk, then restored the same way:renders an ordinary two-decimal percent field exactly as before,leaves the cell face aloneanddoes not fire AT the ceilingeach read1 failed, 12 skipped, exit 1, when run alone. All six now assert on the ruling's ownobjectui#9808marker through thewarnings()helper, and each reads1 passed, 12 skipped, exit 0, alone.Clause-②line and the integer domain above, and the same two corrections inside the ruling's docblock.Test (shard 1/4), which wascancelledat the reviewed head, is what this push starts. Its verdict is reported on the card, not asserted here.What was re-run for the patch round, at
b0e975d42fpnpm --filter '@object-ui/fields...' buildpnpm --filter @object-ui/fields type-check0lines matchingerror TSpnpm exec vitest run packages/fields/-t1 passed, 12 skipped, exit 0pnpm --filter @object-ui/fields lint1037 problems (0 errors, 1037 warnings)check:control-bytes·check:new-line-citations·check:self-import·check:esm-specifiers·check:test-path-roots·check:changeset-claims·check:pending-changeset-literals·check:side-effects-array·check:unreferenced-sources·check:dist-completeness·check:published-dist·check-changeset-presencecheck:governed-queue-guard --testover the five changed pathsNOT GOVERNEDWhat the SECOND patch round changed, at
2b8d4f9352The re-review at
b0e975d42fdischarged four of the prior five items on its own measurements — the two symbols off the published surface (by TypeScript checker over the built entry, not only by grep), the controls in isolation, the ruling itself, and the whole-matrix CI reading — and returned FAIL on four narrower ones. ⭐ It also corrected this branch in its favour: all six old controls read1 failedalone, not the three reported here; the pin file's own docblock was the accurate statement.PERCENT_SCALE_CEILING + 1through the INSTALLEDFieldSchemaand requires it to be ACCEPTED; the failure message says the premise has expired and names what to delete — the module, its two call sites and that file — and says not to relax the assertion. Two controls stand beside it: the door refuses a bogus key BY NAME, and an ordinaryscale: 2is accepted, so an acceptance verdict is a reading rather than a door that admits everything. Measured through that door on the installed 17.4.0:2,100,101and1000ACCEPTED;-1,2.5,InfinityandNaNREFUSED. ⭐ Proved FAILABLE: with the probe pointed at a width the door already refuses, the row fails, exit 1, printingSUNSET REACHED; mutation and restoration both proved by blob hash.-1andInfinitymoved from THROW to RETURN at the previous head, and both are widths the installed spec refuses — a renderer-side default around bad input, AGENTS.md #0.1's own listed example. They are no longer rescued. The two pins are inverted: they now assert the widths still THROW and that the ruling's marker is absent.a0b6with the head's own ablation below.grep -cover a.d.tsis not an export census, so this body's earlierformatAddressaside ("a module that IS partly re-exported") was wrong — those hits are{@link}prose. The two subject zeros held under both instruments, so nothing turned on it.Reverse verification at THIS head
Faces returned to the branch point with the ruling's module left on disk, mutation proved by blob hash before any result was read:
⭐ The five red rows are now the five clamped-set rows and nothing else — every one of them a real
RangeError. The spelling failure the earlier ablation carried is gone, because the ceiling constant no longer lives in a file the ablated faces own. The twelve green are the controls, the two inverted spec-refused rows and the four sunset rows, all of which must hold on BOTH trees, which is what makes them controls.What was re-run, at
2b8d4f9352pnpm --filter '@object-ui/fields...' buildpnpm --filter @object-ui/fields type-check0lines matchingerror TSpnpm exec vitest run packages/fields/-t1 passed, 16 skipped, exit 0 — 1 selected, never 0dist/index.d.ts0,PercentField2, precedentsFileValueAffordanceanduseBadInputRefusal0; runtime entrydist/index.jsboth0,PercentField3pnpm --filter @object-ui/fields lint1037 problems (0 errors, 1037 warnings)check:control-bytes·check:new-line-citations·check:self-import·check:esm-specifiers·check:test-path-roots·check:changeset-claims·check:pending-changeset-literals·check:side-effects-array·check:unreferenced-sources·check:dist-completeness·check:published-dist·check-changeset-presenceGenerated by Claude Code