Repository navigation
feat(spec): a dataset measure declares its polarity, so a lower-is-better KPI draws a rising delta as bad news - #26739
Conversation
…ction is good news Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
… docs and liveness counts Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
…in every locale Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 147 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 12f4ccbae018e4b55a2845280b25e1433e2a4827 && git checkout 12f4ccbae018e4b55a2845280b25e1433e2a4827
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6866e2e7c5a268b2c2e1ec0fb900f469adf5f551 1aa6a94b184b1ebe8c831adb5f71e109f55fe683 && git checkout -B drift-repro 6866e2e7c5a268b2c2e1ec0fb900f469adf5f551 && git merge --no-ff 1aa6a94b184b1ebe8c831adb5f71e109f55fe683
node scripts/docs-audit/affected-docs.mjs --json 6866e2e7c5a268b2c2e1ec0fb900f469adf5f551
|
Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
…descriptor Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
Resolves the positive-control count in object-lifecycle-panel-echo-decisions (686 from #26548 plus the measures repeater's `polarity` = 687) and regenerates authorable-surface/ui.json from the merged tree. Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read: card #26602 body and all four comments (triage 6440164879, claim 6442814260, claim amendment 6442830822, os-dev-report 6445682259); PR #26739 body and file list; the net diff of the head against ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged against the tree:
② Semver level
③ Boundary flags
④ Design judgmentEach
Must-fix: none. The remainder of the diff (service, renderer, tests, generated spec artifacts, translations, docs, changesets) is the lane seat's ACCEPT at Implemented-by: VERDICT: PASS |
dashboards.md: 6239 -> 6221 with its history line (ceil(24881 / 4)). analytics-inline-vs-dataset.json: eval 8's "(a rise draws red)" restated the prompt and the asserted value; trimmed, and the ceiling is pinned at the new measurement, 1348 -> 1361 (ceil(5443 / 4)). Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
Resolves two pinned counts by adding this branch's one to main's: the lifecycle-panel positive control 689 -> 690 and the ADR-0122 isomorphic pins 796 -> 797. Regenerates authorable-surface/ui.json and content/docs/references/index.mdx from the merged tree. Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
ui/.changeset is no longer read since @object-ui joined the root fixed group (2a61f08); content unchanged. Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
席位核对 · ACCEPT ·
|
Contract reviewServed-tier: Inputs read: card #26602 body and all four comments (triage 6440164879, claim 6442814260, claim amendment 6442830822, os-dev-report 6445682259); PR #26739 body, file list (33 paths, equal to ① Derived judgmentsEvery accept-set and public-surface change the diff implies, judged against the tree at
② Semver level
③ Boundary flags
④ Design judgmentEach
Must-fix: (1) The remainder of the diff (service, renderer, tests, generated spec artifacts, translations, docs) is the lane seat's ACCEPT at Implemented-by: VERDICT: FAIL Generated by Claude Code |
席位核对 · ACCEPT ·
|
Contract reviewServed-tier: Inputs read: card #26602 body and all four comments; PR #26739 body (the Changesets bullet names all four packages), its file list (33 paths, equal to ① Derived judgmentsThe delta from the PASSed head is exactly one first-parent commit, the merge
② Semver level
③ Boundary flags
④ Design judgmentEach
Must-fix: none. The remainder of the diff (service, renderer, tests, generated spec artifacts, translations, docs) is the lane seat's ACCEPT at Implemented-by: VERDICT: PASS Generated by Claude Code |
Resolves the ADR-0122 isomorphic pin count: main's HookRefusalSchema (796 -> 797) plus this branch's DatasetMeasurePolarity = 798. Regenerates content/docs/references/index.mdx from the merged tree. Claude-Session: https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR Co-Authored-By: Claude <noreply@anthropic.com>
席位核对 · ACCEPT (armed) ·
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Triage: triage's PASS is stale: head moved to
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, a review after landing under the maintainer's ruling (#27345): this PR landed at 09:59Z on Generated by Claude Code |
Contract reviewServed-tier: Inputs read: PR #26739 body, its 33-path file list and all ten PR comments (the four earlier contract records 6446021563 PASS on ① Derived judgmentsThe delta since the last PASS (
② Semver level
③ Boundary flags
④ Design judgmentEach
Must-fix: none. Should-fix, each written so it can be filed as its own card (nothing is fixed on the closed PR):
The remainder of the diff (service, renderer, tests, generated spec artifacts, translations, docs) is the lane seat's ACCEPT at Implemented-by: VERDICT: PASS Generated by Claude Code |
Requested by Yibo Wang · project thread
Fixes #26602
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: updated here — skills/objectstack-ui/rules/dashboards.md (the metric-widget
compareTobullet and the measure presentation line), skills/objectstack-ui/evals/analytics-inline-vs-dataset.json (eval 8 now asks for the polarity and assertspolarity: 'lower'), content/docs/ui/dashboards.mdx维护者速读
polarity(higher/lower/neutral),分析服务把它带到答案的列描述上,仪表盘 KPI 卡片和交叉表单元格按它给环比变化上色。polarity的度量颜色与现在完全一致(上升绿、下降红),纯新增;回滚即还原本 PR。packages/spec/src/ui/dataset.zod.ts(新键与取值)、ui/packages/plugin-dashboard/src/DatasetWidget.tsx的deltaToneClass、packages/services/service-analytics/src/analytics-service.ts的enrichResultColumns一行转发。ui/目录执行pnpm exec vitest run packages/plugin-dashboard/src/__tests__/DatasetWidget.polarity-26602.test.tsx,预期 9 个用例全部通过。skills/**(Tier H),合并需要你的批准;另请确认 eval 文件 token 上限按裁决 A 抬高 13。What changes
Where the key lives, and why. The card offered two seats: the widget's
compareToor the dataset measure. Measured atorigin/main4373103, the widget'scompareTocannot carry it:DatasetWidgetforwardswidget.compareToverbatim into the dataset selection.DatasetCompareToSchema(packages/spec/src/api/analytics.zod.ts) is astrictObject, so a new member there would be refused on every query.packages/spec/src/ui/dashboard-compareto.test.tspins "a parsed widgetcompareTois assignable toDatasetCompareTowith no mapping". The widget's slot is a thin projection of the executor's own directive, by design.So the key is declared on the dataset measure only, the semantic layer, where every widget over the measure agrees. A count whose meaning flips with a filter (overdue vs resolved records) is two measures, each with its own measure
filterand its own polarity. A widget-level override would be a later, pure widening if a consumer needs one.The values.
DatasetMeasurePolarity = z.enum(['higher', 'lower', 'neutral']). They name the better direction rather than a sign or a boolean, soneutral(no good/bad colour, for a measure where neither direction is news) is a peer value and not a third state of a flag. Undeclared keeps today's colours exactly (a rise green, a fall red). Nothing infers a polarity from the measure's name or unit.packages/spec/src/ui/dataset.zod.ts):DatasetMeasureSchema.polarity, optional, no default materialized. Near-miss keysbetterWhen(the card's own proposal),direction,goodDirection,trendDirectionare pointed atpolarity; the boolean spellingshigherIsBetter/lowerIsBetterare answered with the three values.packages/spec/src/api/analytics.zod.ts,packages/spec/src/contracts/analytics-service.ts): the column descriptor declaresfields[].polaritywith the same enum, mirrored inAnalyticsResultandAnalyticsResultResponseSchema(the existing compile-time drift guard binds the two).packages/services/service-analytics/src/analytics-service.ts#enrichResultColumns): one line relays the authored value, on the live query and the draft preview alike, the__comparecolumn included. Undeclared measures and dimension columns carry no key, so an answer over a dataset that declares none is byte-identical.ui/packages/plugin-dashboard/src/DatasetWidget.tsx): one helper,deltaToneClass(direction, polarity), colours both surfaces that colour a delta, the metric tile and the cross-tab cell.lowerdraws a rise red and a fall green;neutraldraws neither (the tile's muted text colour);higherand undeclared draw a rise green and a fall red. The arrow keeps the number's direction under every polarity, and a flat delta stays muted.packages/spec/src/ui/dataset.form.ts): the measures repeater offerspolarity, labelled inzh-CN/ja-JP/es-ES(packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts).packages/spec/liveness/dataset.json):measures.polaritylive, with the renderer as evidence andenrichResultColumnsas producer.skills/objectstack-ui/rules/dashboards.mdteaches the key (paid for inside the file, and the file's ceiling is lowered 6239 → 6221); eval 8 asks for it;content/docs/ui/dashboards.mdxgains a presentation-home row and a sentence on thecompareTorow..changeset/26602-measure-polarity.md(@objectstack/specminor,@objectstack/service-analyticsminor,@objectstack/platform-objectsminor for the four regeneratedmetadata-formstranslation catalogs it publishes under./apps),.changeset/26602-delta-polarity.md(@object-ui/plugin-dashboardminor; ui changesets live in the root directory since release(ui-merge): one release lane for both scopes — @object-ui joins the fixed group, @objectstack/console builds from ui/apps/console, the objectui pin is deleted (#25711) #25745).Why
A KPI's colour is read before its number. hotclm's average approval turnaround tile showed "↑ 23% vs previous period" in green for a turnaround that got 23% longer.
Tests
All at head
aef918b95, onorigin/main4373103 (main has not moved since the branch was cut).packages/spec/src/ui/dataset-measure-polarity.test.ts(17): the three values parse and keep their value; the closed vocabulary; an absent key materializes nothing;up/down/good/inverse/truerefused at the key (invalid_valueat['polarity']);betterWhenanddirectionpointed atpolarity; the boolean spellings answered with the values; the wire descriptor accepts exactly the measure's vocabulary and stays optional.packages/services/service-analytics/src/__tests__/measure-column-polarity.test.ts(3), each on the live path and the draft preview: each declared value relayed as written; no key on an undeclared measure or a dimension column; the__comparecolumn carries its measure's polarity.ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.polarity-26602.test.tsx(9): the metric tile forlower(rise bad, fall good, arrows kept),neutral(no tone, muted),higher, undeclared (class string byte-identical to today's), and a flat delta muted under every polarity; the cross-tab cell forlower,neutraland undeclared.scripts/ablation-replace.mjs, each restored toHEADby blob hash: deleting the relay line inenrichResultColumnsturns 2 of 3 service tests red (the two positive ones; the absence test stays green); replacingdeltaToneClasswith the old direction-only colouring turns 4 of 9 renderer tests red (both tilelowercases, the celllowerand the cellneutralcases).@objectstack/specTest Files 732 passed (732), Tests 21938 passed, 1 todo.@objectstack/service-analyticsTest Files 192 passed (192), Tests 4650 passed, 282 skipped.@objectstack/platform-objectsTest Files 96 passed (96), Tests 1361 passed.@object-ui/plugin-dashboardTest Files 190 passed (190), Tests 1864 passed, 6 skipped.@objectstack/spec,@objectstack/service-analytics,@objectstack/platform-objectsexit 0.@object-ui/plugin-dashboardtype-check NOT MEASURED locally: it exits 2 with 133 errors, every one aCannot find module '@object-ui/*'or an implicitanydownstream of one, because the sibling@object-ui/*packages have no built declarations in this container. CI'sType Check UIis the reading.Acceptance notes
skills/objectstack-ui/evals/analytics-inline-vs-dataset.jsongrows +29 tokens for the polarity in eval 8, part-funded by trimming two parentheticals, eval 2's note that restated its own expected output and eval 8's "(a rise draws red)". The file is at 5443 bytes. The balance (+13, 1348 → 1361) is raised under the maintainer ruling recorded beside that row inscripts/check-skills-token-ratchet.mjs, quoted verbatim: 「A:先削重复,余额按实测提高」. Seat: please confirm that ruling covers this eval; the alternative is dropping the eval change and declaring the skills halffollow-up owed.rules/dashboards.md464 → 462;evals/analytics-inline-vs-dataset.json87 → 87 (5389 → 5462 bytes); allSKILL.md4600 → 4600; wholeskills/tree 19192 → 19190.cnis tailwind-merge, so the tile's explicit muted class forneutralwould win over a tone class even without the helper'sneutralbranch. The cross-tab cell has no such class, which is why itsneutralpin is the one that goes red when the helper's branch is removed.ObjectMetricWidget/MetricWidgettrend path reads no dataset measure, so it has no polarity to read;DatasetReportRendererdraws no comparison delta.Verification
pnpm --filter @objectstack/spec check:generated --fixregenerated the 5 stale artifacts (api-surface, export-origins, declaration-map, references docs, liveness counts) from a rebuilt dist and re-checked them green; the other 9 were already current.pnpm --filter @objectstack/spec check:empty-state: all classified (no new permissive empty-state statement).pnpm check:i18n: OK, 10 packages in sync. The dataset form's measures repeater offerspolarity(the spec's form/Zod reconciliation pin required it), labelled 极性 / 極性 / Polaridad; the two pinned populations move by one (object-lifecycle-panel-echo-decisionspositive control 689 → 690 on today'smain,report-form-echo-decisionsdataset leaves 39 → 40).node scripts/check-skills-token-ratchet.mjsand--self-test: OK (79 cases).node scripts/pm/dispatch-gates.mjs --commands, 138 at this head) are running locally; their verdicts go in the os-dev report on Dashboard KPI: a compareTo delta is drawn green whenever the number rises — no way to declare lower-is-better, so a slower turnaround reads as good news #26602, and the PR's check-runs are the CI reading.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (eslint on the changed files; the repo-wide run is CI's).main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzfjMN88NYp5BcCRyGR
Generated by Claude Code