Repository navigation
feat(spec): a percent field declares its read-only face, a progress bar or the plain percentage (percentDisplay) - #28222
Conversation
…(bar or plain) A percent field had no declared key to choose its read-only face, so every host drew a progress bar even for a rate or a probability. Declare a flat, optional display hint beside useGrouping: absent = the renderer default (the bar), 'bar' pins it, 'plain' shows the percentage alone. Liveness row planned until the console's percent face reads it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyu6UsPddURvKQb3fE
…ss counts; changeset for percentDisplay Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyu6UsPddURvKQb3fE
📓 Docs Drift CheckThis PR changes 3 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 170 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1ba84e8f4c438bcca021d9540b46342dd9bde46e && git checkout 1ba84e8f4c438bcca021d9540b46342dd9bde46e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 32387500a601958826fe0b5ba72f53948c9dc5d2 b3ca10e1a83a30c221c4ccf69abb326a31610268 && git checkout -B drift-repro 32387500a601958826fe0b5ba72f53948c9dc5d2 && git merge --no-ff b3ca10e1a83a30c221c4ccf69abb326a31610268
node scripts/docs-audit/affected-docs.mjs --json 32387500a601958826fe0b5ba72f53948c9dc5d2
|
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Contract review lane (Fable): schema face at CONTRACT_REVIEW_TIER (packages/spec/src/data/field.zod.ts) per |
…t it on the served field schema The form reconciliation gate needs a root ledger row for a key the field form does not offer yet (the conditionalFormatting precedent), and the /meta/types control count for field moves 79 -> 80. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyu6UsPddURvKQb3fE
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Re-claim after the head moved from 2f944c8 (claim 6529528882): delta is the two test-only fixes for the Test Core red (metadata-form omit row for |
…(keep) FIELD_REFERENCE_POSITIONS must classify every FieldSchema key (ADR-0106 D1). percentDisplay names no field, so it is kept on a served object schema; unclassified, a value spelling a denied field's name would be scrubbed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyu6UsPddURvKQb3fE
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Re-claim after the head moved from 1990c7f (claim at 18:18Z): delta is one commit, |
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D Seat judgment before a re-run · 2026-10-30T18:46Z · head Test Core (5/8), job 124239486040 in run 41106510673, is judged a runner flake, not this PR's: the shard's vitest summary is all green ( |
FIELD_KEY_STORAGE_CLASS must classify every FieldSchema key; the DDL never reads a percent field's read-only face. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyu6UsPddURvKQb3fE
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D Correction to the seat's judgment 6530878086 · 2026-10-30T19:21Z That judgment was wrong: Test Core (5/8) was this PR's red, not a runner flake. The step runs several legs and exits with the first non-zero one, so the green cli tail hid an earlier red leg: |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #27973 body and its two comments (claim 6526095170, os-dev-report 6529040825); PR #28222 body, the 11-file list and the diff against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Should-fix, none blocking: when the ui card lands, the liveness row's Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D 维护者速读
席位核对 · ACCEPT ·
|
Resolve the #28222 overlap: keep percentDisplay and lookupAutofill side by side in the metadata-form omit ledger, and take the /meta/types field count to 81 (80 percentDisplay, 81 lookupAutofill). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyU9JHYh6atGVnEXr3
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
Requested by Tesla · project thread
Fixes #27973
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28256 (skills/objectstack-data/rules/field-types.md, to land with the renderer half #28255)
维护者速读
percent)新增一个可选键percentDisplay,取值bar(进度条)或plain(只显示百分数文字),决定列表单元格和记录页上怎么显示。crm_opportunity.probability);以前没有任何声明的键能关掉它。packages/spec/src/data/field.zod.ts中紧跟useGrouping的percentDisplay声明、packages/spec/liveness/field.json的percentDisplay行(planned)、packages/spec/src/data/field.test.ts的percentDisplay测试组。pnpm --filter @objectstack/spec exec vitest run src/data/field.test.ts -t percentDisplay,预期 7 条全绿。What changes
A
percentfield had no declared key to choose its read-only face, so the console's percent face (PercentCellRenderer, shared by list cells and the record page) draws a progress bar for every value, including a rate or a probability. This is the spec half of the card: the key, its describe text, its liveness grade and the regenerated references.packages/spec/src/data/field.zod.ts).FieldSchemadeclarespercentDisplay: z.enum(['bar', 'plain']).optional(), placed besideuseGrouping, the other flat numeric display hint. Absent = the renderer's default face (the bar today);barpins the bar;plainshows the percentage as text alone (a stored0.25reads25%, at the field'sscale). Display only: no stored value, validation or width changes. No default is declared and the key is not type-restricted at parse, the same postureuseGroupingtakes and its tests pin. The describe says the Studio UI does not read the key yet, so an author is not promised a face the console does not draw (Prime Directive 10).packages/spec/liveness/field.json). New rowpercentDisplay:planned, measured at main9f13ee81, with the flip condition written into the note (the console's percent face reads the key, measured underui/, and the describe's not-yet-read clause goes in the same change).state-counts/field.mdregenerated (planned 6 → 7).content/docs/references/data/field.mdx,data/object.mdx,system/migration.mdx(gen:docs, the field table is rendered in all three) andauthorable-surface/data.json(onedata/Field:percentDisplayline).packages/spec/src/system/metadata-form-zod-reconciliation.test.tsgains a root ledgeromitrow forfield.percentDisplaybeside theconditionalFormattingrow (planned, not yet rendered: the field form offers the key with the renderer half, asuseGroupingwas offered once enforced).packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts: the servedfieldschema's control count moves 79 → 80. Both were red on the first CI run (Test Core 2/8 and 7/8) and are the direct consequence of the new key.FieldSchemakey and their tests red on an unclassified one.packages/metadata-core/src/object-schema-fls-references.ts,FIELD_REFERENCE_POSITIONS(ADR-0106 D1):percentDisplay: keep, besideuseGrouping. It names no field, so a served object schema keeps an authored value whatever the caller may read; unclassified, a value that spelled a denied field's name would have been scrubbed.packages/drivers/driver-sql/src/builtin-column-collision.ts,FIELD_KEY_STORAGE_CLASS:percentDisplay: 'presentation', besideuseGrouping, since the DDL never reads it. These were red on CI as Test Core 6/8 and 5/8. In both shards the visible log tail was green, because the step runs the cli slice last and exits with the first red leg's status..changeset/27973-percent-display.md:@objectstack/spec,@objectstack/metadata-coreand@objectstack/driver-sql, each minor (theMetadata-protocol: addedlevel), with both declaration lines and one sentence per package on its classification.The shape chosen, and the alternatives not taken
useGrouping. The order asked whether a per-type display/options member already exists for numeric types that this should join. Measured: none does.useGroupingis the only numeric display hint and it is a flat sibling;currencyConfigis a currency-only member about WHICH currency, not how an amount is drawn. Joining it would put a percent key under a currency name. A flat key is the house shape for this class, and an enum (not a boolean likeshowBar) leaves room for a third face without a second key.percentConfig: { display }block. One member does not earn a container; it would add a strict object, a form composite and a liveness drill for a single choice.Field.formata percent vocabulary.formatis declared vocabulary-free on every type; teaching it one word on one type is the dialect the card asks to avoid.ui/view.zod.ts). A field-level key reaches list cells once the grid's relay sites copy it onto the cell's field metadata (part of the ui half, as foruseGrouping); a per-column override is only owed if a list needs a different face from the record page, which no producer has shown yet.authorWarnon the liveness row. It is a display hint with a harmless fallback (the bar it draws today), the same gradinguseGroupinghad whileplanned. A warnedfieldrow would also re-open the lint's field walk, outside this PR's file surface. The describe carries the not-yet-read clause instead.Tests
packages/spec/src/data/field.test.ts, new grouppercentDisplay — percent-field read-only face(7 cases): acceptsplainandbar; absent stays absent (no default materializes); a value outside the closed set (number,progress,true,null) is refused with an issue on the key's own path; the key is declared, not refused as unknown;Field.percent(...)threads it; the JSON Schema slot is the closed enum['bar', 'plain']with nodefaultand not required.Acceptance notes
domain:uicard, carried by the seat:PercentCellRenderer(ui/packages/fields/src/index.tsx) readspercentDisplayon every host, and the grid / detail relay sites that copyuseGroupingonto the cell's field metadata must copy this key too, orplainwill not reach the cell on those surfaces. That card also flips the liveness row toliveand drops the describe's not-yet-read clause.field.form.ts) does not offer the key yet, on purpose: the form shows a display hint once it is enforced (theuseGroupingrow's rule). The ui card adds the row.crm_opportunity.probabilityand the campaign rate fields can setpercentDisplay: 'plain'once the renderer reads it.Verification
spec tests, typecheck, ablation
b3ca10e1:@objectstack/driver-sqlvitest (under the verify lock): Test Files 236 passed, 12 skipped (248); Tests 3847 passed, 214 skipped. Before the row, the same run had 1 failed file, theFIELD_KEY_STORAGE_CLASScompleteness test. The other packages planned into Test Core 5/8 were green:plugin-webhooks25/25 files,service-job11/11,trigger-schedule11/11,connector-slack3/3,client-react3/3.2fdc1c19:@objectstack/metadata-corevitest: Test Files 21 passed (21); Tests 873 passed.1990c7fc: full@objectstack/specvitest (under the verify lock): Test Files 843 passed, 1 skipped (844); Tests 24004 passed.@objectstack/metadata-protocol:protocol.meta-types-degenerate-derivation.test.ts18/18 (the full suite before the fix: 1 failed, 21858 passed, that one count).2f944c8d,pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data/ scripts/liveness/(under the verify lock): Test Files 161 passed (161), Tests 4975 passed, 1 todo. (At0c8ff3e4,src/data/alone: 149/149 files, 4598 tests.)pnpm --filter @objectstack/spec typecheck: lock VERDICT command-exit 0.scripts/ablation-replace.mjs, the suite readssrc):z.enum(['bar', 'plain'])→z.enum(['bar']), anchor 1 → 0 on disk;-t percentDisplay→ 4 failed, 3 passed; restored, blob == HEAD andgit diff HEADempty.generated artefacts
pnpm --filter @objectstack/spec check:generatedafter a dts build: first run namedcontent/docs/references/**andliveness/state-counts/stale; regenerated withgen:docsandgen:liveness-counts; after mergingorigin/mainand rebuilding, 14/14 up to date (exit 0).derived gates
b3ca10e1: the derivation gives 106 families, which are the 105 below pluscheck:driver-conformance. The delta was run:check:driver-conformance,check:durability-log-level, the changeset family (check-changeset-no-major,check-empty-changeset,check-adr-0087-registration),check:type-check-debt,check:undeclared-dep-imports,check:issue-citationsandcheck:doc-authoring, all exit 0 (record:27973-gates-delta-b3ca10e1.list).2fdc1c19: 105 families derived and 105 run, all exit 0, 0 NOT-MEASURED (record:27973-gates-ran-2fdc1c19.list).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat2f944c8d(the head before the two test fixes, which touch only test files): 104 families, each run with stdin from/dev/null, exit code recorded before any pipe (record:27973-gates-ran-2f944c8d.listin the seat's out folder).--ranreconciliation: 104 derived, 104 run, 0 NOT-MEASURED, 0 unrun, 0 red.0c8ff3e4and at39dd0028(after the first merge oforigin/main), all exit 0. On the first passcheck-plugin-teardown-shape --self-testexited 3 until its pinned control commit621a4876was fetched into the shallow clone, then 0. On the final passspec check:skill-examplesexited 3 (PREREQUISITE NOT MET: the merge brought apackages/clientsource change, so its dist predated it); after a dts build of@objectstack/client-react...(38/38) it exited 0.pnpm turbo run buildof the non-ui workspace (dts on) before the gate passes: 77/77 tasks.Generated by Claude Code