test(dashboard): measure which analytics starvation path produces the objectui#7696 symptom - #9498
Merged
os-tesla merged 3 commits intoSep 14, 2026
Conversation
…ectui#7696's symptom Drives all five starvation paths through the real DatasetWidget and records a four-axis fingerprint per path (chart categories, neighbouring list cell, server-resolved chrome, app-bundle chrome, metadata-channel trace), plus a healthy baseline. No production code changes. Part of objectui#7696 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
…tion paths A `vite demo` page that mounts the shipped DatasetWidget with the real chart renderer, so the pie legend is PAINTED and can be read off the DOM — the one step the jsdom pin argues rather than measures. `?path=` selects the starvation. Declares an empty-frontmatter changeset: nothing published moves. Part of objectui#7696 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
`no-await-in-loop` is not enabled for this file, so the directive was itself the package's only lint error (`reportUnusedDisableDirectives`). Part of objectui#7696 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-tesla
marked this pull request as ready for review
September 14, 2026 11:48
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Sep 14, 2026
os-tesla
deleted the
claude/issue-7696-analytics-starvation-path-measurement
branch
September 14, 2026 12:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of objectui#7696 — the card was re-scoped by triage to a MEASUREMENT, and this is that measurement. It changes no production code: two test-surface files and an empty-frontmatter changeset.
Placeholders below are spelled as UPPERCASE WORDS rather than in angle-bracket form, because this body's bytes are rewritten on save and bracket-shaped spans do not survive.
What was asked, and what came back
The card's original 2x2 claim was falsified in an earlier round and the card was deliberately kept open, because the SYMPTOM was not falsified: a console paints
One-off / Recurring / Standingin a pie legend under a Chinese title, description and axis labels, while the neighbouring LIST VIEW of the same field is correct. Triage's question: which of the five starvation paths is live?All five were driven. The two nominated as likely were not driven alone — the other three were driven as negative controls, so "it is (3)" could actually fail.
The instrument — a four-axis fingerprint
Every one of the five paths turns the chart English, so the chart alone discriminates nothing. What the reporter could see was a combination, and the combination is what was recorded:
chartlistserverChromefields[]bundleChromemetaReadsserverChromeandbundleChromeare separated on purpose. The reporter saw Chinese chrome and the chrome has two independent sources: axis and series labels ride the analytics response (the card measuredfields[].labelswitching withaccept-languagewhilerows[]stayed byte-identical), while title and description come out of the app's i18n bundle. A path that kills the bundle kills the second and not the first — and that is exactly what excludes two of the five.The measured matrix
Read in a real Chromium against the constructed boot (
?path=selects the starvation), and identically in jsdom by the pin:duly_duty→ 200objectduly_duty→ 401duly_duty→ 200duly_duty→ 200duly_duty→ 200duly_duty→ 200The decision
The reported fingerprint admits (1), (2) and (3b). It excludes (3a), (4) and (5).
getAppNamespaces(), which every resolver inuseObjectLabelfunnels through — so the widget title and description degrade to their authored English along with the legend. The reporter's title and description were Chinese. The server-resolved axis labels stay Chinese in both, which is why reading the axis alone would have cleared them wrongly.GET /api/v1/meta/object/OBJECTNAME— so a document with no inlineoptionsstarves both, and the list cell falls back to humanizing the stored value (recurring→Recurring). That is (3a), and it is not what was reported. (3) only reproduces the report in an install whose list COLUMN declares its own option list, which the grid prefers over the object document — (3b). ⇒ (3) is live for this report only as a property of the VIEW, not of the net.options.⇒ Which one is live in the reporter's install is answered by one reading on their box, not by more code reading: open the dashboard with devtools' network panel and look at
GET /api/v1/meta/object/duly_duty— absent ⇒ (1); present and 401/403 ⇒ (2); present and 200 ⇒ inspectfields.form.optionsin the response body for (3b).What the constructed boot adds over the pin
jsdom lays out no SVG and paints no legend, so the pin can only read the categories the widget hands the chart renderer; the step from "these are the categories" to "this is what the legend SAYS" is an argument there. The boot mounts the shipped
@object-ui/plugin-chartsrenderer in a real Chromium, so the legend is painted by the chart library and read off the DOM. Both readings agree on all seven rows.App driven: the constructed boot fixture in this PR (
pnpm --dir packages/plugin-dashboard exec vite demo), not the showcase or CRM example app — see Acceptance notes for why.Evidence
Pin, at
66b794d:pnpm exec vitest run packages/plugin-dashboard/src/__tests__/DatasetWidget.starvationFingerprints-7696.test.tsx→Test Files 1 passed (1) / Tests 8 passed (8), exit 0; stable over four separate runs.A RED reading for every leg. Eight ablations, each removing exactly one starvation (or the production seam), each proven on disk by an anchored count before/after and restored BY STATE (
git diff HEADempty, HEAD blob equal to disk blob), withtrap restore EXIT INT TERMand absolute paths throughout:omitObject: true→falsein the PATH (1) legTests 1 failed | 7 passed (8)chartDoc: null→DUTYTests 1 failed | 7 passed (8)DUTY_NO_OPTIONS→DUTYin the (3a) legTests 1 failed | 7 passed (8)listColumnOptions: FORM_OPTIONS→undefinedTests 1 failed | 7 passed (8)ZH_BUNDLE_UNDISCOVERABLE→ZH_BUNDLETests 1 failed | 7 passed (8)provider: false→trueTests 1 failed | 7 passed (8)omitObject: true→falsein the DECISION run listTests 1 failed | 7 passed (8)deriveDimensionLabelMapscallTests 1 failed | 7 passed (8)The last one is the one that matters most: it proves the pin observes the real net rather than echoing its own fixture. No build was needed for it — the root vitest config aliases every
@object-ui/*specifier to that package'ssrc, which its own header states is what makes a separate.dist.spec.tsxproject necessary for dist questions.A race was found and fixed inside the instrument. The first settle was
waitFor(three categories exist), which the FIRST paint satisfies — the widget paints the server's rows before the metadata read lands, so a starved path and a healthy one are indistinguishable at that moment. It was caught by a BASELINE that reddened once inside a batched ablation while passing in isolation. Replaced with a fixed number ofact-wrapped flush rounds: nothing in the fixture is unbounded (fetchis a mock resolving an already-settled promise), so this is a drain, not a timeout, and it does not tell the instrument the answer. After the fix, the same ablation batch reddens exactly one test per leg.Affected-package verify at
66b794d:turbo run build --filter=@object-ui/plugin-dashboard... --filter=@object-ui/plugin-charts... --concurrency=2→Tasks: 13 successful, 13 total.turbo run type-check --filter=@object-ui/plugin-dashboard --concurrency=2→Tasks: 14 successful, 14 total; the new pin IS inside the typechecked program —tsc -p packages/plugin-dashboard/tsconfig.test.json --listFilesnames it (measured, not assumed).pnpm exec vitest run --maxWorkers=2 packages/plugin-dashboard/→Test Files 119 passed (119) / Tests 1129 passed (1129). Both underscripts/pm/os-verify-lock.shwithOS_VERIFY_LOCK_SLOT=objectui-7696; one queue-timeout (exit 99, 540s) was spent on lock-free gates and re-acquired on the same slot.Gates (lock-free), all exit 0 at
66b794don a clean tree:check:changeset-presence,check:control-bytes,check:new-line-citations,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:unreferenced-sources,check:phantom-deps,check:unused-deps,check:self-import,check-changeset-no-major.check-governed-queue-guard.mjs --teston all four changed paths: NOT GOVERNED. Control-byte self-scan of the changed files: zero hits, with a positive control containing0x01that DID fire.Declared narrowing of the repo-wide lint. CI runs
pnpm lint=turbo run lint= each package's owneslint .; the diff touches one package, so only that package's verdict can move.pnpm --filter @object-ui/plugin-dashboard lint→ exit 0,496 problems (0 errors, 496 warnings); warnings cannot fail because.github/workflows/lint.ymldeliberately sets no--max-warnings, as its own header states. The three narrowing readings: (i) the population is eslint's own — a rooteslint . --no-inline-config --format jsonreports it linted 4977 files; (ii) of those, 2 are added by this branch (the changeset.mdis not linted); (iii) invariance —eslint.config.jsconfigures no type-aware program (noparserOptions.project, noprojectService), andgit grepfinds zero references to either new file anywhere in the tree, so neither can move any untouched file's verdict. The remaining repo-wide errors are all in packages this branch does not touch (packages/components,app-shell,core,types,cli,data-objectstack,apps/console,layout). Everything else is left to CI.One fix caught by the package lint and applied: an
eslint-disable-next-line no-await-in-loopthat the rule never fired for, whichreportUnusedDisableDirectivesscored as the package's only error.The census was re-derived from disk, not inherited
Triage recorded on the card that its own
localizeFieldOptionscall-site list was already incomplete at the very commit it cited. Re-derived at this branch's base6d5db7b, the non-test call sites are exactly four: the definition in@object-ui/core'schart-series.ts, and one call each inplugin-charts'ObjectChart.tsx,plugin-dashboard'sDatasetWidget.tsxandplugin-report'sDatasetReportRenderer.tsx(plugin-report'suseDatasetDimensionLabels.tsnames the seam in prose but does not call it).This is why the pin lives in one package and still answers for all three. Every one of the five starvations happens ABOVE the three producers, in code all of them share: (1) and (2) starve inside
@object-ui/react'suseDatasetDimensionMeta, (3) inside@object-ui/core'sresolveDimensionFieldMeta— which emits an entry only for a terminal field that actually carriesoptions— and (4) and (5) inside@object-ui/i18n'sgetAppNamespacesgate. None of them is a property of the dashboard.Acceptance notes
objectstackcheckout in this container has nonode_modules(neither at its root nor underexamples/app-showcase), so driving a stock example app would mean installing and building a second framework monorepo inside a shared checkout to serve one page. Recorded as a declared departure rather than glossed. Two things follow, and both are honest limits: the showcase's own nearest fixture (showcase_task_metrics, a LOCALstatusselect dimension charted on the Chart Gallery dashboard, withshowcase_task.status.optionstranslated in the zh bundle) was identified but not driven; andshowcase_invoice.status— the other local select dimension on a dashboard — carries a zhlabelbut nooptionsblock in the shipped bundle, so the Revenue Pulse chart would have starved for a sixth reason (a bundle with no key for those options) that is none of the five. Noted, not filed: it is example-app content, not a defect in the net.packages/plugin-dashboard/tsconfig.jsonincludes onlysrc, so the newdemo/directory is outside both the build and the typecheck programs — the same shapepackages/plugin-grid/demoalready has. Carrier: none today; recorded because a future reader may expectdemo/to be typechecked and it is not.Generated by Claude Code