spec(data): field-scale.ts states that a server sum / avg over a no-fixed-width number does not yet carry its width, and the ruled end-state (#21183) - #21449
Conversation
…and the ruled end-state The A-prime paragraph promised that a computed result over a no-fixed-width `number` column rounds to the widest decimal count among its inputs. That holds only where the renderer sees the inputs (the grid summary footer, the object-metric tile's min/max). A server-side sum/avg carries no width: the analytics column metadata has format/currency/percentScale and no width member. The docblock now says so, records the ruled end-state (the analytics result reports the width) and its trigger, and names the two declared channels an author has today (a field scale, or a format). No schema, key, export or runtime change. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…ships in no published file Measured after `pnpm --filter @objectstack/spec build`: the module docblock of `src/data/field-scale.ts` reaches none of the paths in spec's `files[]` (`dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `api-surface`, `spec-changes.json`). The new text, and the pre-existing module docblock text, both hit 0 files; the positive control, the TSDoc on `resolveFieldScale` itself, hits `dist/data/index.d.ts` and `index.d.mts`. A diff that publishes nothing takes `skip-changeset`. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewServed-tier: PR #21449 (card #21183). Net diff ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #21183
Clause-②: no
This PR writes the protocol-text half of the maintainer's ruling A (deferred) on objectstack-ai/objectui#11254 (ruling record 5933283974, batch 261 item 4, 「其他四张同意」).
The A′ paragraph of
packages/spec/src/data/field-scale.tsstated a rule as protocol: "a computed result over a no-fixed-width column rounds to the widest decimal count among the values that entered it". A server-sidesum/avgdoes not deliver that. The diff changes one comment and nothing else: ⛔ no schema, key, export or runtime change.What changes
One paragraph changes in place, in the
numberbullet of the module docblock and in the same voice. It now says four things.object-metrictile'smin/max, whose answer is itself one of the inputs.sum/avgover anumberwith no declaredscaleshows instead. It does not get that width yet. The tile receives one number, andAnalyticsResultResponseSchema's column metadata carriesformat/currency/percentScaleand no width. The paragraph also says what each face shows at the objectui pin.scalewhen there is one. Trigger: the first first-partyobject-metrictile or dataset measure doingsum/avgover such anumberwith noformat. A is then built in one go.scale, which theobject-metrictile reads throughresolveFieldScale. The other is aformat: the tile's own, or the dataset measure's on a dataset-bound tile.Readings (each measured before writing)
d7d5b4f96a. The paragraph stated the rule with no server-aggregate exception.AnalyticsResultResponseSchema'sdata.fields[]declaresname,type,label,format,currency,percentScaleandbuiltinAggregate, and no width member. The schema now sits at aboutanalytics.zod.ts:94-130, not the card's:94-125. Its content is as the card says..objectui-shapin89cad75d5570.ObjectMetricWidget.tsx,inferredFormat: an authoredformatwins. Otherwise a declaredscalegoes throughwidthPattern. With no width,min/maxtake the answer's own decimal count (ownFractionDigits), andsum/avgfall through to'0,0'. That is line 354, the same line the card cites on8001068b9c. The whole-number reading is confirmed.useColumnSummary.ts,widestFractionDigits: the footer rounds a no-fixed-widthnumberresult to the widest decimal count among the values it read. Confirmed.b149617e6f) is an ancestor of the pin. The objectui sibling clone is shallow, and its first reading answered exit 1, with the merge commit below the shallow boundary. After the pin's history was deepened into a private ref,git merge-base --is-ancestoranswered exit 0, which proves ancestry on its own.sum/avg"still shows a whole number". That is true of theobject-metrictile only.DatasetWidget, thenformatMeasurein@object-ui/coredataset-format.ts) behaves differently when the measure has noformat. It prints an integer as it is and rounds any other value to at most two decimals (maximumFractionDigits: 2).scalenever reaches that tile.service-analyticscopies only the measure'sformatinto the column metadata (analytics-service.ts:if (f.format == null && m.format) f.format = m.format).check:docsis green. The reference pages come fromFieldSchema.scale's.describe(), which is unchanged and makes no claim about computed results..objectui-shapin spelling.check:objectui-pin-citationsis green: 49 asserting citations match the pin, and 73 historical ones are recorded.Changeset: none,
skip-changeset(a measured deviation from the dispatch)The card and the dispatch asked for one
@objectstack/specpatchchangeset, "since the docblock ships in the published types". Measured afterpnpm --filter @objectstack/spec build, it does not ship. A module-level docblock is attached to no exported declaration, so it reaches none of the paths in spec'sfiles[]:dist,json-schema,liveness,prompts,llms.txt,README.md,src/**/*.zod.ts,api-surfaceandspec-changes.json. (field-scale.tsis not a*.zod.tsfile.)binds TODAY) is in 0 files.What an ABSENT,Why a resolver and not a Zod default) is in 0 files.resolveFieldScale's own TSDoc (The decimal places this field displays) is in 2 files,dist/data/index.d.tsanddist/data/index.d.mts.A diff that publishes nothing takes
skip-changeset(AGENTS.md, Post-Task Checklist step 3). The changeset was written first, in commit80ae4f4cb3, and commit97a757c216removed it, with this measurement in its message. To reverse: restore.changeset/21183-field-scale-server-aggregate-width.mdfrom80ae4f4cb3and remove the label.Tests and gates (head
97a757c216)pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 600 files, 17684 passed, 1 todo.src/data/field-scale.test.tsalone: 13 passed.pnpm --filter @objectstack/spec run typecheck: exit 0 (tsc, the scripts typecheck andcheck:test-typecheck).pnpm --filter @objectstack/spec check:generated: all 15 artifacts are up to date. This was taken on80ae4f4cb3;97a757c216changes only.changeset/, andcheck:docs/check:api-surface/check:authorable-surfacewere re-run green on97a757c216.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, with no paths, derived 73 commands. All 73 ran and 72 exited 0.--ranreconciliation:73 derived, 72 run, 1 NOT-MEASURED, 0 UNRUN.check:doc-formula-expressionsandcheck:lean-entry-closurefirst refused with exit 3, because their prerequisites were not built. After@objectstack/formula,@objectstack/lintand@objectstack/objectqlwere built, both re-ran green.isPathIgnored: falsefor the one changed file.eslint --no-inline-config --format jsonon it: 1 file, 0 errors, 0 warnings. Invariance: the config resolved for this file setsparserOptionstoecmaVersion/sourceTypeonly, with noproject, so no type-aware linting runs and this diff cannot move a verdict on an untouched file. The repo-widepnpm lintis CI's run.NOT MEASURED
check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET. It needs every workspace package built (pnpm build), which does not fit this container's foreground budget. Invariance: the diff is a comment that reaches no emitted file (measured above), so no package's CJS output differs from base. CI runs it.Acceptance notes
min/max. Over a no-fixed-widthnumberwith no measureformat, they also round to at most two decimals, through the sameformatMeasurearm. So "amin/maxkeeps its own decimal count" holds on theobject-metrictile only, and the docblock names only that tile'smin/maxas bound. Not filed: there is no producer. No example dataset or metric in this repo aggregatesmin/max(grep ond7d5b4f96a), and no carrier touchesformatMeasure. When A is built, a width on the analytics column would cover this face too.d7d5b4f96a. All 11 datasetsum/avgmeasures inexamples/carry aformat. The oneobject-metricsumKPI (app-showcasecommand-center.page.ts,cc_k6) carries'0.0a'. Within these spellings, no first-party producer has fired the trigger.refs/os-dev/issue-21183/pin, for the ancestry reading, and the ref was deleted afterwards. objectui's working tree and its branch refs were not touched.Generated by Claude Code