Skip to content

fix(dashboard,charts): two declared-translatable dashboard surfaces now resolve (objectui#10132) - #10169

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-10132-dashboard-i18n-declared-not-resolved
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-10132-dashboard-i18n-declared-not-resolved

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #10132

Clause-②: yes — an exported type of @object-ui/core is retyped, and the value vocabulary of a published payload widens. Derived from THIS diff, not from the card. packages/core/src/index.ts re-exports both edited modules with export *, so everything they export is published. Measured on the built dist/ tree (the package ships files: ["dist", …]), with a negative control in the same command: DashboardFilterDef 13 hits, resolveDashboardFilterDefs 6, axisPresentation 8, and an invented symbol 0 — so the counts are measurements, not a silent instrument. The two moves are (a) DashboardFilterDef gains the optional member object, present in the shipped .d.ts as object?: string; (b) axisPresentation keeps its signature but may now emit title as 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.

⚠️ The claim comment deliberately carried Clause-②: pending and named a Clause-②-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:

half what is actually wrong where the fix lands
GlobalFilterSchema.object the key is never read — no consumer exists at any layer a carry-through in @object-ui/core plus two resolver calls in @object-ui/plugin-dashboard
ChartAxisSchema.title the value bypasses a resolver that is already downstream of it and already correct one expression in @object-ui/core

They 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 normalizeChartSchema has resolved an axis title through pickLocalized against 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 main

Nothing was taken on trust; the card's evidence was measured against a pinned published console, this branch re-derived both against origin/main at 80c54122e.

Half 1. A globalFilters[] entry declaring object: 'opportunity', field: 'type' and no label, rendered under a bundle that translates it, painted the raw field name type and left its option label in the authored English. resolveDashboardFilterDefs builds its DashboardFilterDef by naming the keys it copies and object was not among them, so the value could not reach a renderer even in principle; the only .object read anywhere on the filter path was optionsFrom.object, which names a different object on purpose.

Half 2. axisPresentation collapsed an I18nLabel axis title with labelText, a first-string-in-key-order pick. Both directions measured on one map: written en first it painted English to a zh-CN viewer; the en viewer 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.

  • Half 1, field label — useSafeFieldLabel().fieldLabel from @object-ui/i18n, the convention resolver lists and forms already call. It walks the discovered app namespaces for the fields.OBJECT.FIELD entry and returns its fallback on a miss. Precedence therefore follows the resolver's own signature — the translator's bundle wins, the authored label (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.
  • Half 1, option labels — translateOptions from the same object, keyed by option value under fieldOptions.OBJECT.FIELD.VALUE. ⚠️ Worth stating because the spec's describe text names the family by its FIELD-label key: in this repository the option arm of that same convention is spelled 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.
  • Half 2 — forwardedI18nLabel, the neighbour in the same module, which already carries a chart's own title / subtitle / description unresolved for precisely this reason (objectui#9038). The map now survives the lowering and normalizeChartSchema resolves it, as it always could.

The objectui#4020 ledger entry that moved, and the one that did not

chart-presentation.i18nLabel-9038.test.ts pinned the first-string pick for a series label and an axis title as 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, which DatasetWidget replaces 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. seriesPresentation keeps 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 raw type, and 新业务 vs the authored New business. Plus an opt-in control: a filter with no object, against the SAME bundle, must render exactly as today.
  • ChartRenderer.axisTitleLocale-10132.test.tsx — the same locale map at zh-CN and at en, through the real mergeAuthoredPresentation lowering, spread onto the schema exactly as DatasetWidget spreads it. Every map is written en FIRST so the zh-CN case 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).
  • Seam pins beside each: dashboard-filters.objectKey-10132.test.ts and chart-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-parity is 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-replace so the mutation is proven on disk (anchor count, replacement count and the git hash-object blob all move) and the restore is proven against HEAD rather than against an exit code. ⛔ The tests resolve @object-ui/core through the root vitest resolve.alias to packages/core/src, not to dist/, so no stale-build false green is available here.

leg ablated outcome
the object carry-through in resolveDashboardFilterDefs 6 failed / 2 passed — both seam and rendered pins red, both controls green
fieldLabel call in useFilterLabel 2 failed / 2 passed — the two field-label pins red, the option pin still green
translateOptions call in SelectFilter 1 failed / 3 passed — only the option pin red
forwardedI18nLabel back to labelText in axisPresentation 5 failed / 19 passed — axis seam, the moved ledger assertion and the zh-CN render red; the en render stayed GREEN, which is the defect's signature

Each leg restored with git checkout HEAD -- ABSOLUTE_PATH, verified blob-equal to HEAD with git diff HEAD empty. 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 tip bfa70403a, plus ONE merge of main at ff14e29b5 (f49ee23c3; no rebase, no force-push, 154 commits brought in, git merge-tree clean), plus one commit from the contract review (below). Updated by the domain:ui seat 1, which took over this PR's review and landing on the maintainer's instruction (the takeover record is on this conversation).

  • The five pin files: 5 files, 32 tests passed. 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 build over 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-dashboard and @object-ui/plugin-charts, and each test program includes its pins.
  • All four ablation legs re-run at the merged head. They match the table above exactly: (a) 6 red / 2 green, (b) 2/2, (c) 1/3, (d) 5/19. The controls stay green, and each restore is proven by blob.
  • What main brought in is disjoint from both halves: objectui#10400 appended chartTypeIgnoresCompareTo to chart-presentation.ts; objectui#10339 added the date-range default preset in dashboard-filters.ts; objectui#9909 and objectui#7495 do not touch the axis or series-label path.
  • Gates at this head: 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.
  • Governed guard over this diff's paths: NOT GOVERNED.

The contract-review commit (185e950d4) fixes the review's two FAIL items. (1) The changeset grades @object-ui/core minor, because Clause-②: yes takes at least minor. It was patch. (2) content/docs/guide/dashboard-filters.md documents globalFilters[].object and the i18n precedence it opts into, and packages/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:

  • labelText in chart-presentation.ts now 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.
  • The spec's describe text for GlobalFilterSchema.object says the option labels resolve "through the SAME fields.OBJECT.FIELD convention", while this repository's option arm of that convention is keyed fieldOptions.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.
  • DashboardWidgetInspector in @object-ui/app-shell is resolveDashboardFilterDefs' 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3032.8 KB 3104.5 KB
Main entry chunk (gzip) 146.9 KB 350 KB
Entry file index-DGpdXcbC.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 546.12KB 130.78KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 221.99KB 61.72KB
fields (index.js) 251.84KB 63.48KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.02KB 11.00KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 198.20KB 47.14KB
plugin-dashboard (index.js) 133.11KB 35.22KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 255.19KB 66.48KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 140.25KB 35.85KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 213.40KB 58.19KB
plugin-kanban (index.js) 48.71KB 15.17KB
plugin-list (index.js) 113.53KB 27.99KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.49KB 11.97KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

This was referenced Sep 24, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor

Takeover: the contract review and landing of this PR (card objectui#10132), per the maintainer's instruction
Session: session_01BA3nKVUwKQJf8DBxrSVtNC
Branch: claude/issue-10132-dashboard-i18n-declared-not-resolved (unchanged: the dev's branch, extended by merge commits only; ⛔ no rebase, no force-push)
Domain: domain:ui
Seat: domain:ui#1
File surface: PR objectui#10169's nine files as they stand, plus a merge of origin/main into the branch (stop on breach; explain in the report)
Clause-②: yes (the declaration of record is 5752205791, which this seat adopts)
Thread-read: 5752205791 (the card's newest comment)

⚠️ Why this record is on the PR, not the card. objectui#10132 answers GET /issues/10132 with 404 on both REST and GraphQL (「Could not resolve to an Issue」), and a comment POST to it returned 422 Validation Failed (relay run 36084133700). Its comments and timeline still read. The neighbours #10120, #10131 and #10133 answer 404 as well, while controls (#10000, #10140, #10404) answer 200. So the card cannot be written from any seat right now. This record lives here until it can; Fixes #10132 in this PR's body is unchanged.

Provenance: who — the maintainer; verbatim — 「10169 你接手契约复审并跟进到合并」; where — the maintainer's chat with this session (session_01BA3nKVUwKQJf8DBxrSVtNC), 2026-09-25T01:56Z.

State read before this act:

  • The PR has been a draft since 2026-09-20T19:40Z at bfa70403a, labelled needs:contract-review on this card since 2026-09-20T19:45Z. There has been no review, no ACCEPT and no activity since.
  • The branch is 153 commits behind main. git merge-tree against origin/main is clean (0 conflicts). main has since changed two of its sources (chart-presentation.ts, dashboard-filters.ts), so the review runs on a head that has main merged in, not on the five-day-old one.
  • The card was claimed by seat 2 (5751972709). ⛔ This seat leaves the assignee and seat 2's claim record untouched. It takes over only the two remaining steps the maintainer named: the contract review, then the gated landing.

Plan:

  1. A dev merges origin/main into the branch and re-runs the PR's pins and gates.
  2. A review-tier contract review runs at that head.
  3. ACCEPT (with the record), then the merge queue. needs:contract-review comes off in the same act as the ACCEPT.

domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · takeover · 2026-09-25T01:56Z

…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
@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3045.2 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-DVp1aJG3.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 543.20KB 129.69KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.22KB 14.56KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.59KB 35.39KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 261.04KB 67.97KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.25KB 41.46KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.21KB
plugin-map (index.js) 21.83KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.90KB 21.97KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/8114-detail-tab-activity-timeline.md

  • names plugin-dashboard/README.md → packages/plugin-dashboard/README.md — edited by this change

    README.md ships in this package's files, so the example went out in every tarball. DetailTabs renders a tab's content through ANGLE-BRACKETS(SchemaRenderer schema={toRenderableSchema(tab.content)} /), which makes content.type an SDUI node position judged by the component registry — so a reader copying the tab got the registry's Unknown component type panel (OBJUI-001) where the timeline should be. Same shape as the line-chart widget in plugin-dashboard/README.md (objectui#7896's census; fixed by objectui#7951) and the fourth known instance.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 0348bc9f1 (merge-base with origin/main): 10 file(s) changed outside .changeset/, read against 1390 pending declaration(s) that publish a body (1967 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3044.6 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-s6y10Eq3.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 543.20KB 129.69KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.22KB 14.56KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.59KB 35.39KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 231.66KB 61.23KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.25KB 41.46KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.37KB 28.24KB
plugin-map (index.js) 21.83KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.90KB 21.97KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor

✅ ACCEPT — PR objectui#10169 at 185e950d4 · contract review FAIL (2 items) → fixed in one commit, re-checked by the seat · entering the merge queue

domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The maintainer handed this seat the contract review and landing of this PR (「10169 你接手契约复审并跟进到合并」; the takeover record is above on this conversation). This ACCEPT is posted HERE, not on the card: objectui#10132 answers 404 on read and 422 on a comment write.

Round 1 → round 2.

  • The PR was brought up to main with one merge commit (f49ee23c3; clean; 154 commits in), and re-proved there: 5 pin files, 366 suite files, 5573 tests, the three type-checks, and all four ablation legs matching the PR's own table.
  • A review-tier contract review ran at f49ee23c3. Its record is below, verbatim. It passed every derived judgment on the code (①1–①4) and FAILED two items:
    • (② semver) the changeset graded @object-ui/core patch under Clause-②: yes;
    • (①5 docs) content/docs/guide/dashboard-filters.md said control labels come only from label, and nothing documented globalFilters[].object.
  • The fix commit 185e950d4 (a child of f49ee23c3; no rebase, no force) makes exactly those two changes:
    • '@object-ui/core': minor, with no prose change;
    • the object bullet in Step 3, the i18n precedence sentence, and one comment line in the plugin-dashboard README example.
  • The seat read the diff. The documented keys (fields.<object>.<field>, fieldOptions.<object>.<field>.<value>) are the resolver's own (packages/i18n/src/useObjectLabel.ts:217-226). The gates at 185e950d4: check:doc-snippets 673/673, check:doc-fences, doc links, changeset presence / no-major / claims, control bytes, new-line citations (0 new), the pins 32/32, and the 123 doc text readers 4383 passed.
  • ⇒ PASS. needs:contract-review comes off the PR in this landing; the card's copy stays until the card is writable again.

For the maintainer's veto window (from the record's ③; the seat does not hold landing for these):

  • Fixes #10132 may not auto-close the card while it answers 404; the seat will check after the merge.
  • The PR body argues the two halves should have been two cards. They are independently revertable.

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f49ee23c34bd877ec5b7f5e6df46ad92cf270e60

Inputs read in full: card objectui#10132 through GET /issues/10132/comments (claim 5751972709, os-dev-report 5752188151, Clause-②-correction 5752205791) and GET /issues/10132/timeline (8 events; needs:contract-review labelled on the card at 2026-09-20T19:45Z); the PR body (GET /pulls/10169, Clause-②: yes, draft, labels package: core · plugin · plugin: charts · tests · needs:contract-review, 0 reviews); the PR conversation (takeover 5825418740 at 2026-09-25T01:57Z, merge-main os-dev-report 5825580508 at 2026-09-25T02:16Z, two bot bundle reports); git diff ff14e29b5...f49ee23c3 and git diff 98178b206...bfa70403a (all 9 files, 728 insertions / 15 deletions); the head-tree sources packages/core/src/utils/chart-presentation.ts, packages/core/src/utils/dashboard-filters.ts, packages/core/src/index.ts, packages/core/package.json, packages/plugin-dashboard/src/DashboardFilterBar.tsx, packages/plugin-dashboard/src/DatasetWidget.tsx (lowering and spread sites), packages/plugin-charts/src/ChartRenderer.tsx, packages/plugin-charts/src/normalizeChartSchema.ts, packages/plugin-charts/src/AdvancedChartImpl.tsx (axis readers), packages/plugin-charts/src/index.tsx (registry), packages/plugin-charts/src/ObjectChart.tsx (delegation), packages/i18n/src/useObjectLabel.ts, packages/i18n/src/pickLocalized.ts; the spec surfaces packages/spec/src/ui/dashboard.zod.ts (GlobalFilterSchema) and packages/spec/src/ui/chart.zod.ts (ChartAxisSchema) in /home/user/objectstack; objectui AGENTS.md (all 592 lines) and objectstack AGENTS.md (Clause-② / semver lines 1083–1085 plus scripts/check-changeset-no-major.mjs' WHICH-LEVEL prose); content/docs/guide/dashboard-filters.md, content/docs/plugins/plugin-dashboard.mdx, packages/plugin-dashboard/README.md (the globalFilters sections); the head's check-runs (GET /commits/f49ee23c3/check-runs, read twice).

Diff identity. git diff ff14e29b5...f49ee23c3 and git diff 98178b206...bfa70403a are byte-identical after dropping index lines except two hunk headers in dashboard-filters.ts (@@ -64,6 +64,31 @@ vs @@ -36,6 +36,31 @@, @@ -522,6 +547,11 @@ vs @@ -490,6 +515,11 @@), which is main's objectui#10339 block above them shifting line numbers; every added and removed line is the same. git merge-tree --write-tree bfa70403a ff14e29b5 yields tree 4497997ab159d9dc9ff4bab07ba31b17a1e1d27c = git rev-parse f49ee23c3^{tree}, so the merge commit carries no hand edit. Merge base with main is ff14e29b5 (git merge-base ff14e29b5 f49ee23c3).

① Derived judgments

  1. Half 1 — DashboardFilterDef.object, its carry-through, its reachability, and the two resolver calls.

    • Type: packages/core/src/utils/dashboard-filters.ts:91 adds object?: string; to export interface DashboardFilterDef — the only member change; name, field, label, type, options, optionsFrom, defaultValue, targetWidgets, allowCustomRange are unchanged (git diff shows 30 insertions, 0 deletions in this file).
    • Carry-through only when authored: dashboard-filters.ts:554 ...(typeof f.object === 'string' && f.object ? { object: f.object } : {}), inside the globalFilters loop of resolveDashboardFilterDefs; the dateRange branch (:519–533) never sets it. The seam pin dashboard-filters.objectKey-10132.test.ts asserts hasOwnProperty(def, 'object') === false when none is authored, so no consumer sees a new own-property carrying undefined. No accept set is relaxed: the resolver copies a string it already validated at the spec (GlobalFilterSchema.object is z.string().optional(), packages/spec/src/ui/dashboard.zod.ts:1352) and refuses non-strings and ''.
    • Reachable: packages/core/src/index.ts:78 export * from './utils/dashboard-filters.js'; and :99 export * from './utils/chart-presentation.js';; packages/core/package.json exports["."].types is ./dist/index.d.ts, files ships dist. Additive only — no symbol removed or renamed in either module.
    • Resolver calls (packages/plugin-dashboard/src/DashboardFilterBar.tsx): :89–94 useFilterLabel reads const { fieldLabel } = useSafeFieldLabel(); const authored = pickLocalized(def.label, language); return def.object ? fieldLabel(def.object, def.field, authored) : authored;. fieldLabel (packages/i18n/src/useObjectLabel.ts:316–317) resolves fields.<object>.<field> (fieldSuffixes, :214–219) with the authored label as fallback — exactly the spec's describe text "Object whose fields.<object>.<field> translation-bundle entry resolves this filter's field label and option labels" (dashboard.zod.ts:1352) and its docblock's "the SAME … convention lists/forms already use" (:1337–1340). Option labels: DashboardFilterBar.tsx:377–379 def.object ? translateOptions(def.object, def.field, localizedOptions) : localizedOptions, where translateOptions (useObjectLabel.ts:331, keys fieldOptions.<object>.<field>.<value> via optionSuffixes :221–229) is the option arm of that same convention, each authored label kept as fallback. Fallbacks when object is absent are byte-for-byte the pre-PR expressions (pickLocalized(def.label, language) and the localizedOptions memo, which is the old options memo renamed); all three label readers (DateRangeFilter:127, SelectFilter:265, TextFilter:406) go through useFilterLabel, so no reader bypasses the rung. useSafeFieldLabel is provider-safe (useObjectLabel.ts:713–723, identity fallbacks at :696–706), so a bar rendered without I18nProvider degrades to the authored label. The new options value outside useMemo is read only by the render (:383, :397), not by any effect or memo key, so its per-render identity is inert (AGENTS.md [WIP] Enhance every detail of the designer #10 honoured, not merely cited).
      PASS
  2. Half 2 — axisPresentation forwards the title; is the widened vocabulary consumed safely?

    • Widening: packages/core/src/utils/chart-presentation.ts:241 const title = forwardedI18nLabel(raw.title); replaces labelText(raw.title). forwardedI18nLabel (:165–171) returns a non-empty string, or the record verbatim when it carries at least one usable string entry, else undefined; labelText (:101–108) returned the first string. So the published payload's title slot may now be an I18nLabel map where it was always a string — the signature axisPresentation(raw: unknown): Record<string, unknown> is unchanged, and the accept set (string, or record with one usable string) is the same domain as before; only the output vocabulary widens. The other axis keys are untouched (:243–253).
    • Every consumer: git grep axisPresentation on the head tree finds two callers, both inside mergeAuthoredPresentation (chart-presentation.ts:326, :336); git grep mergeAuthoredPresentation finds one non-test reader, packages/plugin-dashboard/src/DatasetWidget.tsx:1558, whose authoredAxes is spread once, onto the type: 'chart' schema at :1662 (git grep authoredAxes shows no other reader). plugin-report deliberately calls mergeAuthoredSeries (DatasetReportRenderer.tsx:1072; header chart-presentation.ts:50–59), so its axes never pass through axisPresentation. The chart schema resolves to ChartRenderer (packages/plugin-charts/src/index.tsx:92–94, which "owns the bare type: 'chart' schema keyword" per :71–74); the view:chart alias ObjectChartBlock (:79) itself delegates to <ChartRenderer … schema={finalSchemaWithColors}> (ObjectChart.tsx:1492) with { ...schema } (:1160–1161), so both routes reach normalizeChartSchema(schema, language) (ChartRenderer.tsx:134, language from useObjectTranslation() at :123). normalizeAxis (normalizeChartSchema.ts:308–330) resolves raw.title through label() → pickLocalized (:303–305), whose chain is exact tag → base → regional sibling → default → en → first string (pickLocalized.ts:57–65) and which returns a string on every path; AdvancedChartImpl receives only the normalized xAxis={props.spec.xAxis} / yAxes={props.spec.yAxes} (ChartRenderer.tsx:241–242) and reads axis.title / xAxisSpec.title off NormalizedAxis (AdvancedChartImpl.tsx:523, :1691; destructured at :1167–1168). No reader receives the raw map; [object Object] is unreachable for any value forwardedI18nLabel admits, and a map whose only string entry is under an unrequested tag still resolves through the first-string limb. Render pin ChartRenderer.axisTitleLocale-10132.test.tsx draws the same en-first maps at zh-CN and en and asserts the other language's strings are absent.
      PASS
  3. Interaction with what main brought in. git log 98178b206..ff14e29b5 -- <the PR's sources and their consumers> names three commits: 721d1e008 (objectui#10400) appends CHART_TYPES_IGNORING_COMPARE_TO / chartTypeIgnoresCompareTo at the end of chart-presentation.ts (git diff 98178b206 ff14e29b5 -- packages/core/src/utils/chart-presentation.ts is a pure + block after chartConfigPresentation; labelText, forwardedI18nLabel, seriesPresentation, axisPresentation untouched). 86982ace0 (objectui#10339) adds specDefaultDateRangePreset() and uses it in the dateRange branch of resolveDashboardFilterDefs (dashboard-filters.ts:519–533); the PR's spread is in the globalFilters branch (:554) — disjoint hunks, and a dateRange def never carries object. a78cd378c (objectui#9909) adds a required locale parameter to formatterFor in normalizeChartSchema.ts:606; normalizeAxis/label() are unchanged. objectui#7495's DatasetWidget change hoists chartType and gates compareTo (git diff 98178b206 ff14e29b5 -- packages/plugin-dashboard/src/DatasetWidget.tsx); the mergeAuthoredPresentation call and the ...authoredAxes spread are byte-unchanged. packages/i18n/src/useObjectLabel.ts and packages/plugin-charts/src/ChartRenderer.tsx are not in the main-side stat. Since the merge, main advanced 5 commits to d32824aba; on the PR's dependency path only packages/core/src/index.ts moved (+4 lines, a new export * for non-grid-row-ceiling, objectui#10426), and git merge-tree --write-tree f49ee23c3 origin/main is clean. No semantic entanglement with either half.
    PASS

  4. Pins and ablation — re-derived by this review, not taken from the reports. Run in a detached scratch worktree at f49ee23c3 (git worktree add --detach … f49ee23c34bd…, pnpm install --offline --frozen-lockfile, exit 0), repo-root form, under os-verify-lock.sh (slot objectui-pr-10169-review, held 47s on a shared box, finished at 2026-09-25T02:28Z), each mutation through objectstack's scripts/ablation-replace.mjs in wrap mode (anchor hit count declared and verified on disk, blob hash moved, restore verified blob-equal to HEAD); the worktree was removed afterwards. The root vitest resolve.alias maps every @object-ui/* package to packages/*/src (vitest.config.mts:501–538), so no stale dist could green a leg.

    • Pins at head: pnpm exec vitest run --maxWorkers=2 <5 pin files> → Test Files 5 passed (5), Tests 32 passed (32); identical after the four restores.
    • Leg a — delete the object spread in resolveDashboardFilterDefs (dashboard-filters.ts, anchor x1 → x0, blob 94bde5c4… → 3026b157…): 6 failed | 2 passed (8). RED: the three seam pins (copies / carries with label+options / not optionsFrom.object) and the three rendered pins (field-label lit control, bundle-over-authored, option lit control). GREEN, by subtraction from the two files' four-test inventories: "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" — exactly the two controls.
    • Leg b — useFilterLabel back to return pickLocalized(def.label, language); (DashboardFilterBar.tsx, blob 8739a020… → mutated, restored): 2 failed | 2 passed (4). RED: the two field-label pins only; the option pin and the opt-in control stay green.
    • Leg c — ? translateOptions(def.object, def.field, localizedOptions) → ? localizedOptions: 1 failed | 3 passed (4). RED: "resolves the OPTION label through the same convention — the second lit control" only.
    • Leg d — axisPresentation back to const title = labelText(raw.title); (chart-presentation.ts, blob e546a205… → mutated, restored): 5 failed | 19 passed (24). RED: the three forwarding seam pins (verbatim map, one-usable-entry map, both axes through mergeAuthoredPresentation), the moved ledger assertion "no longer holds the AXIS title to that pick — moved by objectui#10132, not quietly", and "draws the zh-CN axis titles to a zh-CN viewer". GREEN: "draws the en entries to an en viewer" — green AGAINST the defect because every map is written en first, which is the key-order signature the PR names — plus the plain-string and absence render controls, the seam's plain-string / refusal / no-other-key / series-label-still-picked cases, and the 9038 file's other twelve.
    • Every leg reddens only its own half; every count equals the PR body's ablation table and the merge-main report's; each restore printed diff-HEAD-bytes=0, blob-after = head-blob, empty porcelain. The two controls the item asks for — "no object authored" opt-in and the en-viewer non-regression — are both present and both stayed green under every leg.
      PASS
  5. Residual readers of the old behaviour; doc or skill text now false.

    • git grep -n "labelText(raw.title)" f49ee23c3 → no hits; labelText( has one caller left, seriesPresentation (chart-presentation.ts:196), as the changeset says. Filter-bar label path: git grep -n "def\.label" packages/plugin-dashboard/src/DashboardFilterBar.tsx → the single read at :92 inside useFilterLabel; the three controls read useFilterLabel(def) (:127, :265, :406). No residual reader of the old pick on either half. Skills: git grep -i -E "globalFilters|axis.*title" f49ee23c3 -- skills → no hits; nothing there to falsify.
    • Docs: git grep -n -i "globalFilters" f49ee23c3 -- content/docs 'packages/*/README.md' finds content/docs/guide/dashboard-filters.md:122–200, content/docs/plugins/plugin-dashboard.mdx:264, packages/plugin-dashboard/README.md:382–397; none mentions object. content/docs/guide/dashboard-filters.md:334–338 ("## i18n") states: "control labels come from each filter's label, so translate those in your schema metadata". After this diff a filter that declares object takes its field label from the fields.<object>.<field> bundle entry FIRST and its label only as fallback (DashboardFilterBar.tsx:93), so that sentence is now false as the rule it states, and the guide's key list at :145–149 (name, field, type) omits the key the guide would need to make it true. objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2 (line 94): "For every feature/refactor, update package README.md and content/docs/guide/*.md. Not done until docs reflect the code." No axis-title i18n statement exists in content/docs or the charts READMEs (git grep -i -E "xAxis|yAxis" restricted to title → no hits), so half 2 falsifies no prose.
      FAIL — one doc paragraph and one key list are now wrong; see Verdict for the minimal fix.

② Semver level

  • Declaration of record: Clause-②: yes — PR body line 1 ("an exported type of @object-ui/core is retyped, and the value vocabulary of a published payload widens"), card comment 5752205791 (Clause-②-correction: 5751972709, Clause-②: yes, 2026-09-20T19:45Z), adopted by the takeover 5825418740. Re-derived from the diff: correct — DashboardFilterDef gains a member and axisPresentation's title vocabulary widens (①.1, ①.2); both additive, neither narrowing, so the bare yes with no direction arm is the right spelling.
  • Changeset: .changeset/10132-dashboard-i18n-declared-not-resolved.md front matter declares '@object-ui/core': patch and '@object-ui/plugin-dashboard': patch. No major (objectui AGENTS.md 版本号策略 lines 258–263; check-changeset-no-major green on the head).
  • Rule: objectstack AGENTS.md lines 1083–1085 — "The declaration is Clause-②: yes|no … yes takes at least minor"; the same ruling in scripts/check-changeset-no-major.mjs (objectstack) lines 76–80 and 934–935: "a purely additive widening of a published package's public surface takes AT LEAST minor" and "A PR that declares clause-② yes must grade AT LEAST ONE package whose packages/**/src/** it moves at minor or above" (maintainer ruling 2026-09-04, decision batch [WIP] Evaluate which controls should be placed in plugins #35). @object-ui/core is the widened package and it is graded patch; no moved package is graded minor. objectui has no local gate for this leg (git grep Clause scripts/check-changeset-no-major.mjs → none), so nothing red caught it — the declaration and the level contradict each other inside one PR.
  • Prose, sentence by sentence against the diff: the quoted spec describe text matches dashboard.zod.ts:1352 verbatim; "names the keys it copies … and this one was not among them" matches the pre-diff byName.set({ name, field, label, type, options, optionsFrom, defaultValue, targetWidgets }); "useSafeFieldLabel().fieldLabel … translateOptions" matches DashboardFilterBar.tsx:91–93, :264, :377–379; "the translator's bundle wins, the authored label is the fallback" matches fieldLabel(object, field, fallback); "A filter that names no object never reaches it" matches the ternaries; "ChartAxisSchema.title … is I18nLabel" matches chart.zod.ts:215; "labelText, a first-string-in-KEY-ORDER pick" matches chart-presentation.ts:101–108; "normalizeChartSchema already resolves an axis title through pickLocalized" matches normalizeChartSchema.ts:327; "travels verbatim through forwardedI18nLabel" matches :241; "moves one entry of the objectui#4020 … ledger … seriesPresentation keeps the pick and is still pinned" matches chart-presentation.i18nLabel-9038.test.ts (series assertion kept, axis assertion rewritten to toBe(MAP)) and chart-presentation.axisTitleI18n-10132.test.ts' last case; "DatasetWidget replaces it from the locale bundle" is the builtinAggregateLabels(tt) path of buildChartSeries (DatasetWidget.tsx:1545–1553). The two closing "what an author sees" sentences are what the four pins assert. No sentence is false; the level is.
  • FAIL — Clause-②: yes with both entries at patch. Minimal fix: grade '@object-ui/core': minor in .changeset/10132-dashboard-i18n-declared-not-resolved.md (the widened package); '@object-ui/plugin-dashboard' may stay patch (its own published surface did not move; the fixed group publishes at the group's highest level either way). No prose change is needed.

③ Boundary flags

Non-blocking unless marked.

  1. PR body "Checks on this head" names the pre-merge head bfa70403a and "347 files, 5078 tests". At f49ee23c3 the merge-main report reads 366 files / 5573 tests; the body was left as written per the takeover order. The check of record is the head's CI: 43 check-runs, 40 success, 3 skipped, none failed or pending, read at 2026-09-25T02:27Z (an earlier read during this review still had 11 of them in progress). A one-line body edit naming f49ee23c3 would remove the stale figure; not required for landing.
  2. Card objectui#10132 answers 404 on GET /issues/10132 (comments and timeline read fine), so the takeover and merge-main records sit on the PR. needs:contract-review is on BOTH the PR (labels) and the card (timeline, 2026-09-20T19:45Z); the ACCEPT can clear the PR's label now, the card's only once the card is writable. Fixes #10132 in the body may not auto-close an unresolvable card — worth a manual check after landing.
  3. Two halves under one card: the PR body argues they should have been two cards; measured here, the halves share no file (half 1: dashboard-filters.ts + DashboardFilterBar.tsx; half 2: chart-presentation.ts), no function and no pin, and each ablation leg reddens only its own half (①.4), so either is independently revertable. No action on this PR; a dispatcher note for card-splitting is the maintainer's call.
  4. Spec wording (objectstack-owned, not this lane's): GlobalFilterSchema.object's describe text says option labels resolve through the fields.<object>.<field> convention, while this repository's option arm of that same convention is keyed fieldOptions.<object>.<field>.<value> (useObjectLabel.ts:221–229); the PR follows the operative clause ("the SAME … convention lists/forms already use"). Carrier: whoever next edits that describe text.
  5. DashboardWidgetInspector (packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.tsx:33, :159) is resolveDashboardFilterDefs' other caller; it imports the type only and does not resolve the bundle, so a designer preview keeps the authored label where the runtime now shows the translation — pre-existing for every convention-resolved label on that surface.
  6. labelText in chart-presentation.ts is down to one caller (seriesPresentation); still ledgered under objectui#4020 with its overriding caller. Shrinkage, not a defect.
  7. packages/types/src/__tests__/chart-series-keys-7546.test.ts carries a cross-file line address (core/utils/chart-presentation.ts:126-131) that was already false on main at ff14e29b5; check:new-line-citations counts 0 new on this diff (AGENTS.md [WIP] Update documentation for project #11: repair opportunistically, never as a sweep).
  8. main moved again after the merge (5 commits to d32824aba, only packages/core/src/index.ts on the PR's path, +4 unrelated lines); git merge-tree is clean and the merge queue rebuilds on current main, so no second merge commit is needed. At read time the PR is a draft with mergeable_state: unknown and auto_merge: null; ready + enqueue are the seat's acts after the fixes below land.
  9. The docs gap in ①.5 is a blocking item, listed in the Verdict rather than here.

Verdict

VERDICT: FAIL — two items, each with its minimal fix; everything else in ① and ② holds and needs no change.

  1. ② Semver level — .changeset/10132-dashboard-i18n-declared-not-resolved.md grades @object-ui/core patch while the PR declares Clause-②: yes and the diff widens core's published type and payload. Fix: change '@object-ui/core': patch to '@object-ui/core': minor (leave '@object-ui/plugin-dashboard': patch; no prose change).
  2. ①.5 Docs now false — content/docs/guide/dashboard-filters.md "## i18n" (:336–338) says control labels come from each filter's label; with this diff an object-declaring filter resolves its field label and option labels from the app's fields.<object>.<field> / fieldOptions.<object>.<field>.<value> bundle entries first, label second. Fix: add an object bullet to the Step 3 key list (:145–149) — "object — optional; the object field lives on, so the filter's field label and option labels resolve through the same translation-bundle convention lists and forms use, with label as the fallback" — and rewrite the "## i18n" sentence to say so; mirror one comment line in packages/plugin-dashboard/README.md's globalFilters example (:385), since AGENTS.md Add automated testing infrastructure and CI/CD workflows #2 names both surfaces. One commit on the branch covers both items; the pins and ablation need no re-run for either, and CI re-runs on the new head.

Implemented-by: claude/issue-10132-dashboard-i18n-declared-not-resolved
Reviewed-by: session_01BA3nKVUwKQJf8DBxrSVtNC

Seat's round-2 note

Both FAIL items are fixed at 185e950d4 and re-checked by the seat; the body's "Checks on this head" is patched to this head. The record's non-blocking flags are carried as follows:

  • the stale body section → fixed;
  • the card 404 → watched after the merge;
  • the rest → noted. 承接者:无.

domain:ui seat #1 · review · 2026-09-25T02:59Z

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 03:00
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 061f5e8 Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10132-dashboard-i18n-declared-not-resolved branch September 25, 2026 03:11
@objectstack-fleet

Copy link
Copy Markdown
Contributor

Landing close-out, written 2026-09-25T03:22Z.

PR objectui#10169 merged through the merge queue at 2026-09-25T03:11Z. The merge commit 061f5e829 is verified on main.

Card objectui#10132 closed through this PR's Fixes line at 2026-09-25T03:11Z (timeline closed event, actor objectstack-fleet[bot]).

⚠️ Card cleanup could not be written. The closed card still carries pm:dispatched and needs:contract-review. Its issue endpoint still answers 404, and a labels_remove for those two labels failed in relay run https://github.com/objectstack-ai/objectstack/actions/runs/36090077245. That matches the 422 on the comment POST recorded in the takeover above.

  • Whoever can reach the card should remove those two labels; pm:* status labels come off a closed card.
  • The assignee os-tesla and seat 2's claim 5751972709 are left untouched, as the takeover stated.

This closes the takeover: the contract review is done (ACCEPT 5825948755 above) and the PR is merged.

domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · landing close-out · 2026-09-25T03:22Z

akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants