Repository navigation
fix(dashboard,charts): two declared-translatable dashboard surfaces now resolve (objectui#10132) - #10169
Conversation
…ow resolve (objectui#10132) `@objectstack/spec` types both of these translatable and the Console resolved neither. They are TWO defects, not one, and are fixed separately. 1. `GlobalFilterSchema.object` was never read. `resolveDashboardFilterDefs` names the keys it copies onto a `DashboardFilterDef` and this one was not among them, so the authored value could not reach a renderer even in principle. The definition now carries it, and `DashboardFilterBar` resolves the field label through `useSafeFieldLabel().fieldLabel` and the option labels through the same object's `translateOptions` — the convention resolver lists and forms already call. No second resolver was written. 2. `ChartAxisSchema.title` bypassed a resolver downstream in its own tree. `axisPresentation` collapsed an inline locale map with `labelText`, a first-string-in-key-order pick, while `normalizeChartSchema` already resolves an axis title through `pickLocalized` against the viewer's language. The map now travels verbatim through `forwardedI18nLabel`, the neighbour in the same module that already carries a chart's own heading keys for this reason. Both halves are pinned by a lit control — the same surface rendered with the translation available and without it, asserting the two differ — at the seam and in the DOM. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
✅ 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
|
|
Takeover: the contract review and landing of this PR (card objectui#10132), per the maintainer's instruction
Provenance: who — the maintainer; verbatim — 「10169 你接手契约复审并跟进到合并」; where — the maintainer's chat with this session ( State read before this act:
Plan:
|
…clared-not-resolved Brings PR objectui#10169 up to main so its contract review reads a current head. Clean merge: no conflict. main's side touched two of this branch's sources, chart-presentation.ts and dashboard-filters.ts, both auto-merged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
os-dev-report {
"issue": 10132,
"status": "done",
"branch": "claude/issue-10132-dashboard-i18n-declared-not-resolved",
"pr": "https://github.com/objectstack-ai/objectui/pull/10169",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (dispatched subagent of the domain:ui seat 1 session; this run is the same harness-stamped id)",
"premise_still_valid": true,
"summary": "PR objectui#10169 is now up to main with ONE merge commit, and it re-proves at the new head. NEW HEAD f49ee23c34bd877ec5b7f5e6df46ad92cf270e60 (short f49ee23c3), a merge commit whose parents are the PR's previous tip bfa70403a (confirmed the remote tip before cutting the worktree) and main ff14e29b5 (fetched and pinned by sha, never by the moving ref). It brought in 154 commits (98178b206..ff14e29b5; 98178b206 was the previous merge base). The NEW MERGE BASE with main is ff14e29b5. The merge was clean: no conflict, and main's side touched two of this PR's sources, chart-presentation.ts and dashboard-filters.ts, both auto-merged. Pushed as a fast-forward, bfa70403a..f49ee23c3, with no rebase, amend or force, and the remote tip reads back as f49ee23c3. The PR is still a draft, and needs:contract-review is still on it. STEP 3 READING: nothing main changed interacts with either half, and no pin or claim in the PR body became false. (1) chart-presentation.ts: main's only change is objectui#10400 (721d1e008), which appends CHART_TYPES_IGNORING_COMPARE_TO and chartTypeIgnoresCompareTo at the END of the file. It does not touch labelText, forwardedI18nLabel, seriesPresentation, axisPresentation or chartConfigPresentation. At the merged head, labelText has exactly one call site left (seriesPresentation's label), which is the PR's claim. axisPresentation's title goes through forwardedI18nLabel, alongside the three chartConfigPresentation heading keys. (2) dashboard-filters.ts: main's only change is objectui#10339 (86982ace0). It adds an import of SpecDashboardSchema, plus specDefaultDateRangePreset() used in the dateRange branch of resolveDashboardFilterDefs when defaultRange is omitted. The PR's object carry-through is in the globalFilters branch, and DashboardFilterDef gains object?: string. They are disjoint hunks and disjoint semantics: the dateRange def never carries object. (3) The half-2 resolver path is intact at the merged head. mergeAuthoredPresentation feeds authoredAxes, which DatasetWidget spreads onto the chart schema. ChartRenderer, byte-unchanged on main, hands useObjectTranslation().language to normalizeChartSchema. normalizeAxis resolves raw.title through label(), which is pickLocalized. Main's normalizeChartSchema change (objectui#9909) only adds a required locale parameter to formatterFor. The DatasetWidget change (objectui#7495) only moves chartType up and gates compareTo, with no axis or series-label effect. (4) The half-1 resolver path is intact. useObjectLabel.ts, where useSafeFieldLabel/fieldLabel/translateOptions live, is byte-unchanged on main. The i18n provider change (objectui#10382, translations loaded after mount now re-render readers) can only strengthen, not weaken, the bundle-label pins. The two runtime resolveDashboardFilterDefs callers are unchanged apart from a display-locale badge in DashboardRenderer, and the only non-runtime caller is still DashboardWidgetInspector. (5) The PR body's Clause-② reading still holds: core's rebuilt dist/utils/dashboard-filters.d.ts carries object?: string, and index.ts still re-exports both modules with export *. FOR THE SEAT (PR body untouched, per the order): its 'Checks on this head' section names bfa70403a and the old suite count (347 files / 5078 tests). At f49ee23c3 it would read: suite 366 files / 5573 tests, and closure build 12 tasks then three type-checks, all exit 0. MAIN MOVED AGAIN during this run: 4 commits, now 94436f42a (objectui#10417, #10421, #10426, #10444). None touches this PR's files or the dependency path read in step 3; the only overlap is two new, unrelated core tests (non-grid-row-ceiling), and git merge-tree HEAD vs 94436f42a is clean (exit 0). So GitHub shows mergeable_state behind. Not re-merged, because the order says ONE merge commit; the merge queue rebuilds on current main anyway. DEVIATIONS: none from the order. One precedence note: the harness attribution reminder for this session asks for a model-naming Co-Authored-By trailer, and the order requires model-free trailers. The merge commit carries 'Co-Authored-By: Claude' plus the Claude-Session line, per the order. The local branch was deleted after the worktree removal because it equalled the remote tip.",
"tests": "All at f49ee23c3, from the worktree ROOT (repo-root form per AGENTS.md), each under bash /home/user/objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=objectui-pr-10169m. The tree was clean before and after every run (git status --porcelain empty, HEAD f49ee23c3 asserted at the start of each script). Run A: VERDICT command-exit 0, held 144s. Run B: VERDICT command-exit 0, held 235s. Run C: VERDICT command-exit 0, held 80s. Run D: per-part EXIT lines all 0. All are shared-box seconds. PINS (the five files): Test Files 5 passed (5), Tests 32 passed (32); after the ablation restore, again 5 passed (5) and 32 passed (32). SUITE `pnpm exec vitest run --maxWorkers=3 packages/core/ packages/plugin-dashboard/ packages/plugin-charts/`: EXIT=0, Test Files 366 passed (366), Tests 5573 passed (5573). TEXT READERS: `git grep` for the three touched sources finds two tests that read them from disk, column-identity.ratchet.test.ts (core, keyed on core/src/utils/dashboard-filters.ts) and report-chart-query-spec-parity.test.ts (types, reads dashboard-filters.ts). No test reads chart-presentation.ts or DashboardFilterBar.tsx as text: chart-series-keys-7546 and dashboard-range-preset-labels-4984 only mention them in comments. Ran those plus the SUPERSET of every fs-reading test outside the three packages that names one of their directories (46 files, enumerated by grep over the 460 fs-reading test files): EXIT=0, Test Files 47 passed (47), Tests 2445 passed (2445). BUILD CLOSURE `turbo run build --concurrency=2 --filter='@object-ui/core^...' --filter='@object-ui/plugin-dashboard^...' --filter='@object-ui/plugin-charts^...'`: EXIT=0, Tasks 12 successful, 12 total (4 cached). The closure covered components, core, data-objectstack, fields, i18n, permissions, plugin-charts, providers, react-runtime, react, sdui-parser and types. TYPE-CHECK, each echoing its script `tsc --noEmit && tsc -p tsconfig.test.json`: @object-ui/core EXIT=0, @object-ui/plugin-dashboard EXIT=0, @object-ui/plugin-charts EXIT=0. Coverage counted, not assumed, with tsc -p tsconfig.test.json --listFiles: core has 3 of the pins in its program, plugin-dashboard 1 and plugin-charts 1, so all five are type-checked. The plugin-dashboard and plugin-charts test programs read packages/core/dist/utils/dashboard-filters.d.ts, which after the rebuild holds object?: string (1 hit). Its sibling chart-presentation.d.ts holds main's chartTypeIgnoresCompareTo, so the dist is the merged tree, not a stale one. GATES, all EXIT=0: check:control-bytes 'OK (scanned 8520 tracked text file(s); skipped 85 binary)'. check:new-line-citations 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'. check-changeset-presence 'Compared the working tree with ff14e29b5 (merge-base with origin/main): 9 file(s) changed, 8 of them published source ... 8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'. check-changeset-no-major 'No changeset declares a major bump'. check:changeset-claims: self-contradiction leg 'Every package declared across those 1 body(ies) is either not negated in its own prose, or negated only in an ASPECT of it', plus 'No pending changeset names a file this change touches'. RE-READ OF WHAT check:changeset-claims FLAGS: nothing is flagged. Its born-false leg reports 'Corpus: 1 body(ies) this change publishes about itself (not available on this event). Line addresses read in them: 0', and warns that this is 'Read, but nothing to judge ... Not the same answer as a clean one'. So the PR-body half of that leg is NOT MEASURED locally, because the PR body is not in the local event; CI reads it. check:i18n-keys: 'Every in-scope call-site key resolves against the en pack (3053 keys) ...'. ABLATION at the new head, all four of the PR's legs, each from the COMMITTED merge head, through objectstack's scripts/ablation-replace.mjs in WRAP mode (mutate, run, restore, with a restore trap on exit/INT/TERM against an absolute path). Before the run, each anchor was grep -cF counted at 1 and each replacement at 0. HALF 1: (a) delete the object carry-through spread in resolveDashboardFilterDefs: anchor x1 -> x0, blob 94bde5c48426 -> 3026b15772e9, EXIT=1, 6 failed | 2 passed (8). The six are the three seam pins in dashboard-filters.objectKey-10132 and the three bundle pins in DashboardFilterBar.objectBundleLabel-10132. The two survivors are exactly the two controls: 'leaves `object` absent when the author declared none — the live control' and 'leaves a filter that declares no `object` exactly as it renders today — the opt-in control'. (b) restore the pre-fix `return pickLocalized(def.label, language);` in useFilterLabel: blob 8739a020e1a6 -> db3cfd27b4a2, EXIT=1, 2 failed | 2 passed (4), the two field-label pins. (c) `? translateOptions(def.object, def.field, localizedOptions)` -> `? localizedOptions` in SelectFilter: blob 8739a020e1a6 -> f7b2afc18c8c, EXIT=1, 1 failed | 3 passed (4), only the option pin. HALF 2: (d) restore the pre-fix `const title = labelText(raw.title);` in axisPresentation: blob e546a205846a -> 4a8eb7c8896f, EXIT=1, 5 failed | 19 passed (24). The five are three axis seam pins, the moved ledger assertion 'no longer holds the AXIS title to that pick', and 'draws the zh-CN axis titles to a zh-CN viewer'. The en render is NOT among the failures, which is the defect's signature the PR describes. Every count equals the PR's own ablation table. RESTORE PROOF per leg, by blob: 'ok restored: blob == HEAD' with `git diff HEAD` empty (dashboard-filters.ts 94bde5c48426, DashboardFilterBar.tsx 8739a020e1a6 twice, chart-presentation.ts e546a205846a), cross-checked by the script's own git hash-object == git rev-parse HEAD:path, 0 diff bytes, empty porcelain. After all four legs: git diff HEAD 0 bytes, porcelain empty, then the pins re-ran green (32/32). The vitest root config aliases @object-ui/core, @object-ui/i18n and @object-ui/plugin-charts to src, so no stale-dist false green was possible. NOT MEASURED (CI's runs, declared narrowing): repo-wide pnpm lint, full pnpm test, @object-ui/app-shell's suite (DashboardWidgetInspector imports the type only; the member is optional), and ESLint on the changed files, which this order did not ask for. CI on f49ee23c3: not waited on, per the no-polling clause.",
"mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
"api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/10169/comments (this report, via scripts/pm/post-stamped.mjs). Plus 1 git push (fast-forward bfa70403a..f49ee23c3 of the PR branch; not a REST write). Zero label writes, zero PR-body writes, zero writes to the card. REST reads only: GET pulls/10169 (twice), GET issues/10132/comments, GET issues/10169/comments, GET pulls/10169/files.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed (not class a/b/c): packages/types/src/__tests__/chart-series-keys-7546.test.ts cites `core/utils/chart-presentation.ts:126-131` as 'all six on the dataset path'. That cross-file line address was already false on main ff14e29b5: those lines are inside forwardedI18nLabel's doc comment there. This PR's added doc lines shift it again, but do not make a true citation false, and check:new-line-citations counts 0 new. carrier: whoever next edits chart-series-keys-7546.test.ts (AGENTS.md #11, repair opportunistically, never as a sweep) · noted, not filed"
]
}Generated by Claude Code |
✅ 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
|
…obalFilters[].object (objectui#10132) Two items from the contract review of PR objectui#10169: 1. The changeset grades @object-ui/core minor instead of patch. The PR declares Clause-2 yes: DashboardFilterDef gains an optional object member, and axisPresentation's axis title may now be an inline locale map. A yes declaration takes at least minor. plugin-dashboard stays patch; the changeset prose is unchanged. 2. The dashboard filters guide and the plugin-dashboard README now document globalFilters[].object. The guide's Step 3 key list gains an object entry. Its i18n section states the precedence the code implements: a filter that declares object resolves its field label from the fields.OBJECT.FIELD bundle entry and its option labels from fieldOptions.OBJECT.FIELD.VALUE first, with the authored label as the fallback. The README's globalFilters example gains one comment line saying the same. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
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
|
✅ ACCEPT — PR objectui#10169 at
|
|
Landing close-out, written 2026-09-25T03:22Z. PR objectui#10169 merged through the merge queue at 2026-09-25T03:11Z. The merge commit Card objectui#10132 closed through this PR's
This closes the takeover: the contract review is done (ACCEPT
|
…ompareTo; the dashboard stops running the comparison query for pie / donut / funnel / scatter (objectstack-ai#7495) (objectstack-ai#10400) Fixes objectstack-ai#7495 Clause-②: yes ## What this does Ruling 5749199335 (letter A), ratified as 5809246443: one predicate in `@object-ui/core`'s `chart-presentation`, read by both `plugin-charts` and `plugin-dashboard`; the dashboard stops running the comparison query for pie, donut, funnel and scatter. - **`@object-ui/core`**: new export `chartTypeIgnoresCompareTo(chartType)` in `chart-presentation.ts`. It reaches the package entry through the existing `export * from './utils/chart-presentation.js'`, so `index.ts` is not edited. It answers `true` for `pie` / `donut` / `funnel` / `scatter`, and `false` for every other spec chart family, an unknown string, the empty string, a case variant, and `undefined`. The docblock states the rule, the reason per family and why it lives in core, and cites objectui#7495. - **`@object-ui/plugin-charts`**: ObjectChart's local `supportsCompareTo` is deleted. Both call sites (the comparison fetch and the overlay-series synthesis) read `!chartTypeIgnoresCompareTo(schema.chartType)`. The answers equal the retired predicate's for every input, so behaviour is unchanged. - **`@object-ui/plugin-dashboard`**: DatasetWidget's `chartIgnoresCompareTo = chartType === 'scatter'` copy is deleted. `compareTo` is now decided once, near the top of the component: it is `undefined` when the widget reaches the chart branch (`!isMetric && !isTable`) and its chart family (after `CHART_TYPE_MAP`: `pyramid` to `funnel`, `bubble` to `scatter`) ignores `compareTo`. Every downstream reader follows from that one value: no `compareTo` in the `queryDataset` selection, no window lowered into `timeDimensions` (the window stays in `runtimeFilter`, byte-identical to the same widget with no `compareTo`), an empty `comparedValues`, and no overlay series. `chartType` moved up from the chart branch to feed that decision, so it is still declared once. **Done-when (mechanical).** At HEAD, `git grep -nE "IgnoresCompareTo|supportsCompareTo|IGNORING_COMPARE_TO" -- 'packages/*/src/**' 'apps/*/src/**' ':!**/__tests__/**' ':!**/*.test.*'` lists ONE list declaration (`CHART_TYPES_IGNORING_COMPARE_TO` in core's chart-presentation) and its readers: the exported function, two call sites in ObjectChart, and one in DatasetWidget. No `=== 'scatter'` compare-to guard remains in the dashboard. ## Behaviour change and risk reading - **Server side:** a compare-to dataset chart widget of type pie, donut, funnel, pyramid, scatter or bubble now makes the executor run ONE pass instead of two. Nothing a user saw is removed. The renderer's pie and funnel arms draw only `series[0]`, and objectui#7402 had already suppressed the scatter overlay. - **One visible change, in the ruled direction:** the dataset executor refuses a `compareTo` that has no single dated window (objectstack `service-analytics` `dataset-executor.ts`, "compareTo needs a dated window to shift"). The widget used to render that refusal in place of the chart. A widget of an ignoring family now draws its chart. This is pinned both ways: the pie renders, and the bar control still shows the refusal. - **Reachability:** `compareTo` appears in 0 files under `examples/**` in this repo, re-measured at `3861a6a6d`, while the control `"type": "bar"` hits 5 `examples/**/*.json` files (the same reading as triage comment 5548747753). Tenant incidence is not measurable from here. - **Not changed:** - Metric, gauge, table and pivot widgets keep `compareTo`. - A `pie` widget with NO dimensions renders as a metric tile, which shows the comparison as a delta, so it keeps it. The gate is the chart branch, not the widget type. - Line, area, bar, horizontal-bar and combo keep the overlay. - Per the ruling, the renderer is not taught to consume a comparison series for pie. - **Revert:** one PR revert. ## Hunk positions (objectui#10169 overlap) `chart-presentation.ts`: this PR's only hunk is `@@ -440,3 +440,48 @@`. The predicate is appended after `chartConfigPresentation`, at the end of the file. objectstack-ai#10169 edits `@@ -84`, `@@ -105` and `@@ -217`. Measured: `git apply --check` of objectstack-ai#10169's three chart-presentation hunks against this branch head exits 0. `packages/core/src/index.ts` is not edited. ## Tests (HEAD `3861a6a6d`) New pins: - `packages/core/src/utils/__tests__/chart-presentation.compareTo-7495.test.ts`: the predicate over every `ChartTypeSchema.options` family from `@objectstack/spec/ui` (read at run time), plus `undefined`, an unknown string, the empty string, case variants, and `bubble` / `pyramid` (false, because the caller maps them to a family first). It also pins the package-entry export identity. - `packages/plugin-charts/src/__tests__/ObjectChart.compareTo.corePredicate-7495.test.tsx`: - pie, donut and funnel make one aggregate call and get no overlay. These were not pinned before; scatter already was. - The core predicate is wrapped in a spy whose answer the test can force. Forced true on a bar, the fetch and the overlay are suppressed. Forced false on a pie, both run. A truthful bar control keeps both. - `packages/plugin-dashboard/src/__tests__/DatasetWidget.compareTo.ignoredFamilies-7495.test.tsx`: - For the widget types pie, donut, funnel, pyramid, scatter and bubble: the selection has no `compareTo` and no `timeDimensions`, it equals the no-compareTo selection, and the chart gets one series only. - Controls: bar and line still forward the comparison, lower the window and get the overlay. - A dimensionless pie (metric tile) keeps the comparison. - An undated-filter pie renders, while the bar control shows the refusal. Runs, from the repo root through the verify lock, at `3861a6a6d`: - `pnpm exec vitest run packages/core/src/utils/ packages/plugin-charts/` plus the nine text-reading pins listed below: `Test Files 161 passed (161)`, `Tests 2484 passed (2484)`. - `pnpm exec vitest run packages/plugin-dashboard/`: `Test Files 124 passed (124)`, `Tests 1213 passed (1213)`. - Type-check, after `turbo run build --filter='@object-ui/plugin-dashboard^...'` (12 of 12 tasks successful): for core, plugin-charts and plugin-dashboard, `tsc --noEmit && tsc -p tsconfig.test.json` exits 0. The three new test files are in each package's test program (`--listFilesOnly`). Text-reading pins (from a repo-wide `git grep` for tests that read `ObjectChart.tsx`, `DatasetWidget.tsx` or `chart-presentation` as text), all green in the run above: - `residue-namespaces-3546` - `dashboard-widget-options-census` - `base-bind-declared` - `chart-data-model-7113` - `object-chart-undeclared-keys-8885` - `widget-schema-anchors-7946` - `zod-mirror-parity` - `check-control-bytes` - `unconsumed-widget-option-claim-6186` ## Ablations Each ablation ran after the fix was committed, with `scripts/ablation-replace.mjs` from objectstack. Each restore was proven on disk. 1. **Dashboard pointed back at `chartType === 'scatter'`** (anchor: the `chartTypeIgnoresCompareTo(chartType)` term in the `compareTo` decision). Result: 13 failed, 13 passed of 26. Every pie, donut, funnel and pyramid pin (3 each) went red, and so did the undated-filter pie pin. The scatter and bubble pins and every control stayed green. Blob `19d85e1ea29d` went to `aa355d627edc` and was restored to `19d85e1ea29d`, equal to HEAD, with an empty `git diff HEAD`. 2. **ObjectChart's fetch site pointed back at a local four-family list.** Result: the two forced-answer pins went red (2 failed, 4 passed). The restored blob equals HEAD and `git diff HEAD` is empty. No build was needed between the mutation and the run. The tests load the edited modules from `src`: DatasetWidget and ObjectChart by relative import, and `@object-ui/core` through the root vitest alias to `packages/core/src`. ## Gates - `pnpm check:control-bytes`: OK. - `pnpm check:new-line-citations`: 0 new, exit 0. - `node scripts/check-changeset-presence.mjs`: passes (6 source files of 3 released packages, 1 changeset). - `node scripts/check-changeset-no-major.mjs`: passes. - `pnpm check:changeset-claims`: exit 0, and the self-contradiction reading is clean. 5 pending changesets name `DatasetWidget.tsx` or `ObjectChart.tsx` (7125, 8650, 8885, humanize-label-5444, objectchart-schema-anchor-7946). Each paragraph was re-read: none describes a line this PR changes, and all still hold. - Hand-derived family, because this repo has no dispatch-gates deriver. All exit 0: - `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape` - `check:test-path-roots`, `check:pending-changeset-literals`, `check:self-import` - `check:phantom-deps`, `check:spec-symbols`, `check:esm-specifiers` - `check:unreferenced-sources`, `check:i18n-keys` - `check:readme-exports`: not run. No README is edited, and the gate judges only README imports. - ESLint, narrowed to the touched files (a measurement, not a skip): 1. Population: the root `eslint.config.js`. Every package's `lint` script is `eslint .`, and core, plugin-charts and plugin-dashboard have no package-level config, so that script uses the root config. 2. Count: `--format json` over the 6 touched TS/TSX files reports 6 files, 0 errors and 59 warnings. Base vs head for the three edited sources: `chart-presentation.ts` 0/0 to 0/0, `ObjectChart.tsx` 0/36 to 0/36, `DatasetWidget.tsx` 0/11 to 0/11. 3. Invariance: the config enables no type-aware linting (no `parserOptions.project`, no `projectService`), and no rule in `eslint-rules/` reads the filesystem. So this diff cannot move the verdict for any untouched file. ## Changeset `.changeset/7495-compareto-one-predicate.md` declares `minor` on `@object-ui/core` (a new export) and on `@object-ui/plugin-dashboard` (a query stops running). `@object-ui/plugin-charts` is not declared: its behaviour is byte-for-byte unchanged (the same answers at both call sites), and the body says so. ## Acceptance notes - `packages/plugin-dashboard/SKILL.md` already says "Pie, donut, funnel and scatter charts ignore `compareTo`". That was true of the drawing and is now true of the query as well. The file is not edited; it is outside the claim's file surface. - The declared union `ObjectChartSchema['chartType']` omits `funnel`, yet DashboardRenderer's object-chart path passes `funnel` through at run time. The new charts pin casts at one call and says why. Noted, not filed: nothing shows that the narrower prop type rejects or drops authored metadata. - The header of `DatasetWidget.compareTo.scatter.test.tsx` says the executor "DID return" the `__compare` column. Its double still returns that column unconditionally, so the sentence and its assertions remain true. Left as is. --- _Generated by [Claude Code](https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #10132
Clause-②: yes — an exported type of
@object-ui/coreis retyped, and the value vocabulary of a published payload widens. Derived from THIS diff, not from the card.packages/core/src/index.tsre-exports both edited modules withexport *, so everything they export is published. Measured on the builtdist/tree (the package shipsfiles: ["dist", …]), with a negative control in the same command:DashboardFilterDef13 hits,resolveDashboardFilterDefs6,axisPresentation8, and an invented symbol 0 — so the counts are measurements, not a silent instrument. The two moves are (a)DashboardFilterDefgains the optional memberobject, present in the shipped.d.tsasobject?: string; (b)axisPresentationkeeps its signature but may now emittitleas an inline locale map where it previously always emitted a string. Both are additive: no symbol is removed or renamed, no accept set is relaxed, and no key moves off a payload.Clause-②: pendingand named aClause-②-correction:as the protocol's second carrier — there was no diff yet. This body is the declaration that correction points at, and its value is yes.Two defects, not one — and they should have been two cards
The card's title joins them and its ⭐ section calls them "one resolver-family fix". Re-derived here, they are not:
GlobalFilterSchema.object@object-ui/coreplus two resolver calls in@object-ui/plugin-dashboardChartAxisSchema.title@object-ui/coreThey share no file, no function and no failure mode. Half 1 needed a consumer built from nothing across two packages; half 2 needed one call swapped for its neighbour in the same module, because
normalizeChartSchemahas resolved an axis title throughpickLocalizedagainst the viewer's language since objectui#8943 — the resolver was never missing, the value just never reached it. ⇒ they should have been two cards, and the card's own instinct that "the two halves may not share a fix" was right. They are shipped together here only because they were dispatched together and are individually small; each half's pins, ablation and acceptance stand alone, and either could be reverted without touching the other.Both halves reproduce on today's
mainNothing was taken on trust; the card's evidence was measured against a pinned published console, this branch re-derived both against
origin/mainat80c54122e.Half 1. A
globalFilters[]entry declaringobject: 'opportunity',field: 'type'and nolabel, rendered under a bundle that translates it, painted the raw field nametypeand left its option label in the authored English.resolveDashboardFilterDefsbuilds itsDashboardFilterDefby naming the keys it copies andobjectwas not among them, so the value could not reach a renderer even in principle; the only.objectread anywhere on the filter path wasoptionsFrom.object, which names a different object on purpose.Half 2.
axisPresentationcollapsed anI18nLabelaxis title withlabelText, a first-string-in-key-order pick. Both directions measured on one map: writtenenfirst it painted English to azh-CNviewer; theenviewer case was green against the defect, which is exactly why a single-language assertion cannot catch this class.The resolvers reused — no second one was written
⭐ Both halves call a rung that already existed. Neither fix introduces a resolver, a key convention or a fallback.
useSafeFieldLabel().fieldLabelfrom@object-ui/i18n, the convention resolver lists and forms already call. It walks the discovered app namespaces for thefields.OBJECT.FIELDentry and returns itsfallbackon a miss. Precedence therefore follows the resolver's own signature — the translator's bundle wins, the authoredlabel(resolved first, since it may itself be an inline map) is the fallback — so an opted-in filter behaves like every other field label on the console instead of becoming a second precedence rule.translateOptionsfrom the same object, keyed by option value underfieldOptions.OBJECT.FIELD.VALUE.fieldOptions.…, and reusing it is what the key's own wording ("zero new i18n vocabulary, one resolver path") asks for. Inventing a second spelling here would have been the second path it forbids.forwardedI18nLabel, the neighbour in the same module, which already carries a chart's owntitle/subtitle/descriptionunresolved for precisely this reason (objectui#9038). The map now survives the lowering andnormalizeChartSchemaresolves it, as it always could.The objectui#4020 ledger entry that moved, and the one that did not
chart-presentation.i18nLabel-9038.test.tspinned the first-string pick for a serieslabeland an axistitleas deliberate, "so a later edit cannot quietly fold them in under this card's banner". This is not quiet. That ledger's justification is "a locale-unaware CHOICE a caller can OVERRIDE" — and it holds for the series label, whichDatasetWidgetreplaces from the locale bundle, while nothing overrides an axis title: it is spread onto the chart schema and drawn. So the axis arm was never covered by the reason the pick rests on.seriesPresentationkeeps the pick and is re-pinned; the moved assertion stays in that same file, rewritten to say which card moved it.Lit controls, not absence assertions
"the key is not read" is not observable from one render — a missing translation and an ignored key paint the same pixels. Every pin renders the same surface twice and asserts the two differ.
DashboardFilterBar.objectBundleLabel-10132.test.tsx— the same filter with the bundle entry available and without it:类型vs the rawtype, and新业务vs the authoredNew business. Plus an opt-in control: a filter with noobject, against the SAME bundle, must render exactly as today.ChartRenderer.axisTitleLocale-10132.test.tsx— the same locale map atzh-CNand aten, through the realmergeAuthoredPresentationlowering, spread onto the schema exactly asDatasetWidgetspreads it. Every map is writtenenFIRST so thezh-CNcase cannot be satisfied by the first-string pick. Plus a plain-string control and an absence control (an axis with no title draws none, so objectui#9038's erasure mode cannot be mistaken for a pass).dashboard-filters.objectKey-10132.test.tsandchart-presentation.axisTitleI18n-10132.test.ts. Neither half stands alone — a correct seam feeding no consumer is the state this card reports, and a green renderer over a seam that drops the value is the other.all-locales-key-parityis untouched: no key was added to any locale pack. Both halves resolve through existing convention resolvers against an app-supplied bundle, so there is nothing for that test to hold to parity.Reverse verification
Run from the committed fix, one leg per rung, through
ablation-replaceso the mutation is proven on disk (anchor count, replacement count and thegit hash-objectblob all move) and the restore is proven againstHEADrather than against an exit code. ⛔ The tests resolve@object-ui/corethrough the root vitestresolve.aliastopackages/core/src, not todist/, so no stale-build false green is available here.objectcarry-through inresolveDashboardFilterDefsfieldLabelcall inuseFilterLabeltranslateOptionscall inSelectFilterforwardedI18nLabelback tolabelTextinaxisPresentationzh-CNrender red; theenrender stayed GREEN, which is the defect's signatureEach leg restored with
git checkout HEAD -- ABSOLUTE_PATH, verified blob-equal toHEADwithgit diff HEADempty. Working tree clean afterwards, and all pins re-run green from the restored tree.Checks on this head
Head
185e950d4. It is the PR's own tipbfa70403a, plus ONE merge ofmainatff14e29b5(f49ee23c3; no rebase, no force-push, 154 commits brought in,git merge-treeclean), plus one commit from the contract review (below). Updated by thedomain:uiseat 1, which took over this PR's review and landing on the maintainer's instruction (the takeover record is on this conversation).pnpm exec vitest run packages/core/ packages/plugin-dashboard/ packages/plugin-charts/: 366 files, 5573 tests, all passed. The 47 tests that read the touched sources, or those packages' directories, as text: 2445 passed.turbo run buildover the three packages' closure: 12/12. The type-check (tsc --noEmit && tsc -p tsconfig.test.json) exits 0 for@object-ui/core,@object-ui/plugin-dashboardand@object-ui/plugin-charts, and each test program includes its pins.mainbrought in is disjoint from both halves: objectui#10400 appendedchartTypeIgnoresCompareTotochart-presentation.ts; objectui#10339 added the date-range default preset indashboard-filters.ts; objectui#9909 and objectui#7495 do not touch the axis or series-label path.check:control-bytes,check:new-line-citations(0 new),check-changeset-presence,check-changeset-no-major,check:changeset-claims,check:i18n-keys, and the doc gates run for the contract-review commit. All exit 0.The contract-review commit (
185e950d4) fixes the review's two FAIL items. (1) The changeset grades@object-ui/coreminor, becauseClause-②: yestakes at leastminor. It waspatch. (2)content/docs/guide/dashboard-filters.mddocumentsglobalFilters[].objectand the i18n precedence it opts into, andpackages/plugin-dashboard/README.md's example mirrors it.Acceptance notes
Out-of-scope observations, noted and not filed — none is a reproducible defect, a breached declared contract, or a trap that makes an author write metadata the runtime refuses:
labelTextinchart-presentation.tsnow has exactly one caller left (seriesPresentation). Its first-string pick is still ledgered under objectui#4020 and still has the overriding caller that justifies it, so this is an observation about shrinkage, not a defect. Who would meet it: the next card that touches objectui#4020's ledger. noted, not filed.GlobalFilterSchema.objectsays the option labels resolve "through the SAMEfields.OBJECT.FIELDconvention", while this repository's option arm of that convention is keyedfieldOptions.OBJECT.FIELD.VALUE. The two agree on the family and disagree on the spelling of one arm. That is a wording question on a contract owned by another repository, ⛔ not this lane's to change, and the behaviour here matches what lists and forms do. Carrier: whoever next edits that describe text. noted, not filed.DashboardWidgetInspectorin@object-ui/app-shellisresolveDashboardFilterDefs' other caller and renders filter labels of its own. It is unaffected (the new member is optional and additive) but it does not resolve the bundle either, so a designer preview will keep showing the authored label where the runtime now shows the translation. Not filed: it is a preview surface, the divergence is pre-existing for every other convention-resolved label there, and widening this card to cover it was not dispatched. Carrier: a designer-preview i18n card, if one is ever opened. noted, not filed.Generated by Claude Code