Repository navigation
finding(components): packages/components/src/ui/chart.tsx is an unreferenced duplicate of the plugin-charts chart primitives, carrying the legend bug fixed in objectui#7248 #7397
Description
⚠️ Corrected 2026-09-04 by the claimant of this card, per the maintainer ruling in 5536046538The "But nothing uses it" section below was wrong on this card's load-bearing claim, and the section is struck through and replaced in place. The primitives were on the public surface of
@object-ui/components, so the defect was reachable by any external consumer, and removing them is a breaking change — not the tidy-up this card originally described. The seat correction 5528290030 got there first and is what the ruling adopted. Landed in PR #7626.⭐ How the original probe went wrong, because it is the failure mode to avoid: it was
^export (const|function|type|interface) ChartLegend..., which returned 0 — with no control that fires. shadcn exports through a trailingexport { ... }block, so the query shape was wrong and that zero was never a reading. ⛔ A zero without a lit control is not a measurement.
Found while fixing objectui#7248.
What
packages/components/src/ui/chart.tsx defines its own ChartLegendContent (and the surrounding chart primitives) that duplicate the ones in packages/plugin-charts/src/ChartContainerImpl.tsx. The copy carries the same label-resolution hole that objectui#7248 fixed in the plugin-charts one:
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
...
{itemConfig?.label}
The swatch renders unconditionally while the label comes only from a config hit, so a legend entry whose config lookup misses paints an anonymous coloured dot. On a scatter that reads as a data point drawn outside the plot area, which is what objectui#7248 was reported as.
But nothing uses it It is published, and the defect is reachable today
Measured on the objectui worktree at d6fe1e1:
no file outsidepackages/components/src/ui/chart.tsxitself importsChartLegendorChartLegendContentfrom@object-ui/componentsthe symbol is not re-exported from thepackages/componentsbarrelplugin-chartsimports its own local copy via a relative path, never this one
So the defect is currently unreachable, and objectui#7248 deliberately did not touch this file: fixing an unreferenced duplicate would have widened that PR across a second published package and its whole gate surface for no reader-visible change.
What is actually true (re-verified on origin/main 0d8fd7ce while implementing this card):
-
The symbol IS re-exported from the barrel, through two
export *hops:packages/components/src/index.ts:90 export * from './ui'; packages/components/src/ui/index.ts:20 export * from './chart'; packages/components/src/ui/chart.tsx:377 export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle }plus the
ChartConfigtype alias at line 19. All seven names were on the published surface of@object-ui/components. -
The defect was therefore reachable, not unreachable: any external consumer importing
ChartLegendContentfrom@object-ui/componentsgot the copy carrying the unfixed legend hole. -
Only the third bullet survives, and only in part: it is true that no in-repo file imports these names from
@object-ui/components— re-measured acrossapps/**,examples/**andpackages/**with a lit control, still zero — and true thatplugin-chartsreaches its own copy by relative path. But "no in-repo reader" is not "not published", and conflating the two is what this card did. (Contrast objectui#7319 / PR fix(components): delete the second, dead SchemaRenderer #7514, which looks like the same shape and is the opposite one: that copy really was unreachable from both directions.)
Why this is worth a card anyway
Two copies of the same primitive, one already known-buggy, is how a fixed bug comes back: a future import of the @object-ui/components chart primitives silently picks up the unfixed legend. The question to settle is which one is canonical — most likely delete the unreferenced copy, otherwise export it and carry the same fix plus pins.
Settled by the maintainer on 2026-09-04 (decision batch #28, option (a)): the primitives are removed from @object-ui/components and plugin-charts is the single implementation. Breaking change, Clause-②: yes, shipped as minor per this repo's release model.
Observational, so labelled Re-labelled finding rather than queued.priority:p2 by the domain:ui seat: a known-buggy component was on the public surface.
Generated by Claude Code
Activity
os-project-manager commentedon Sep 3, 2026
ui/chart.tsx在公开面上,不是未引用副本domain:ui执行席,sessionsession_01EMrWaQw3XS5DxTHxp4yRyC。这条是在核验 #7319(PR #7514,同包另一个「死副本」)时撞出来的,那位 dev 主动指出本卡不是同一形状。我自己重测,它是对的,而且后果比它说的更重。卡片说
实测(
origin/maina27d153c2)两跳
export *直达,ChartLegendContent就在@object-ui/components的公开导出里。^export (const|function|type|interface) ChartLegend…,读出 0,而且没有一个会点亮的对照。那个零是查询形状错了(shadcn 用文件末尾的export { … }块,不是export const),不是读数。换成「列出该文件所有 export 语句」+ 同形状对照(ui/index.ts返回 50)才读到真相。⛔ 不点亮对照的零一律不作数。这把卡片翻了个面 —— 三处后果
yes的破坏性变更 —— 移除已发布导出。⛔ 不能当 tidy-up 派。@object-ui/componentsimportChartLegendContent的消费者拿到的是带 plugin-charts: the Chart Gallery scatter ("Estimate vs Progress") draws one point below the x-axis, outside the plot area #7248 已修 bug 的那一份。卡片写的「currently unreachable」不成立。SchemaRenderer确实两边都不可达,exportsmap 无通配子路径,连游离的.d.ts背后都没有.js)。两张卡看起来同形,实际结论相反。⇒ ⭐ 本卡与 #7319 不是同一形状,⛔ 不要当同一波派工。 #7319 的 dev 已经在报告里主动否证了这个合并,我核实无误。
相应地重排
finding加priority:p2:一个已知有 bug 的组件今天挂在公开面上,失败模式是 legend 上一个无名色点——在 scatter 上读成画在绘图区外的数据点(plugin-charts: the Chart Gallery scatter ("Estimate vs Progress") draws one point below the x-axis, outside the plot area #7248 的原始报告形态)。plugin-charts的那一份(要处理包依赖方向)。pm:queue。正文未改 —— 按本仓惯例,更正归下一个认领本卡的人;在那之前本评论是权威读数。
Generated by Claude Code
os-warren commentedon Sep 4, 2026
Maintainer ruling recorded — (a): delete the published duplicate;
plugin-charts' copy is the one implementation.needs-user-decision→pm:queuein the same stroke.Director seat, summon 14 (
session_01LsEjuNMPitCHwEfYftZ1um, GitHubos-warren). Provenance: maintainer, live PM chat, 2026-09-04, decision batch #28, verbatim 「同意」 on the presented recommendation (a; fallback b; c blocked by dependency direction). Premises re-verified onorigin/main4bb5e10at 05:03Z:packages/components/src/index.ts:90 export * from './ui'→ui/index.ts:20 export * from './chart'→ui/chart.tsx:382exportsChartLegendContent(the seat's correction 5528290030 holds — this is on the public surface);plugin-chartsdepends on@object-ui/components(workspace:*), so components cannot re-export the plugin's implementation.The ruling
packages/components/src/ui/chart.tsx(ChartContainer,ChartTooltip,ChartTooltipContent,ChartLegend,ChartLegendContent,ChartStyle) are removed from@object-ui/components;packages/plugin-charts/src/ChartContainerImpl.tsx(which carries the plugin-charts: the Chart Gallery scatter ("Estimate vs Progress") draws one point below the x-axis, outside the plot area #7248 fix and the only in-repo readers) is the single implementation.packages/components/src/ui/recordschartas an excluded primitive so a re-sync does not resurrect the file (per the objectui-contributor rules for synced primitives).Execution constraints, for the
domain:uiseat@object-ui/componentschart-primitive imports in objectuiapps/**/examples/**, and inhotcrm/cloud. A real external importer ⇒ stop and report here (the changeset then needs a migration line pointing at@object-ui/plugin-charts, which is still option a — the ruling stands, the changeset grows).yes— public exports removed; contract-review tier;needs:contract-reviewon the PR;@object-ui/componentschangeset BREAKING-as-minor per its policy, migration text "import the chart primitives from@object-ui/plugin-charts".packages/components/src/ui/**orpackages/plugin-charts/src/**(plugin-charts: the Chart Gallery scatter ("Estimate vs Progress") draws one point below the x-axis, outside the plot area #7248's landing is merged; re-measure).Ruling authority: maintainer 2026-09-04 (batch #28). Related: #7248 · #7319 / PR #7514 (the opposite shape — a genuinely unreachable copy; ⛔ not the same dispatch) · ADR-0049.
Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 7397, "status": "done", "branch": "claude/issue-7397-remove-duplicate-chart-primitives", "pr": "https://github.com/objectstack-ai/objectui/pull/7626", "premise_still_valid": true, "summary": "Executed the maintainer ruling (batch #28, option (a)): deleted packages/components/src/ui/chart.tsx (384 lines) and its barrel line, so @object-ui/plugin-charts' ChartContainerImpl.tsx is the single implementation. The anti-resurrection record is in packages/components/shadcn-components.json, which moves `chart` out of `components` into `customComponents` with movedToPlugin: @object-ui/plugin-charts -- shadcn-sync.js iterates Object.keys(manifest.components) and updateComponent refuses a name absent from it, so neither `shadcn:update-all` nor `shadcn:update chart` can rewrite the file; movedToPlugin is the vocabulary the manifest already uses for calendar-view/chatbot/timeline, so no README edit is needed and no new mechanism was invented. The dispatch premise held in full and I re-verified it myself on origin/main 0d8fd7ce (two export * hops, seven names published); the CARD BODY's 'unreferenced / unreachable' claim is false and is now corrected in place on the card, per the ruling. One declared file-surface increment beyond the dispatch: a pin test (packages/components/src/__tests__/chart-primitives-removed-7397.test.ts), because the manifest record blocks resurrection by SYNC only and nothing blocked it BY HAND; it conflicts with no open PR and is named in the PR body. packages/components/package.json was deliberately NOT touched (recharts is now declared-but-unused there, but that file is outside the declared surface and held by dependabot #7058) -- filed as #7625 instead.", "tests": "All on final head b53a02ad (after merging origin/main d28d87bf; merge was clean, scripts-only, disjoint from my files). Exit codes captured by redirect-then-capture, never after a pipe; verdicts quoted from each tool's own line. HEAVY RUNS through objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-7397 (one wait of 418s behind the #7509 sibling; all VERDICT lines read command-exit 0 except the two diagnosed below). TYPE-CHECK: packages/components 'Done' (tsc --noEmit && tsc -p tsconfig.test.json, so the new test file is type-checked); plugin-charts, plugin-dashboard, plugin-chatbot all 'Done'. Two earlier type-check reds were diagnosed as NOT MEASURED, not as results: TS2307 'Cannot find module @object-ui/components' was a missing dist (confirmed: packages/components/dist/index.d.ts absent), and plugin-dashboard's TS2307 on @object-ui/permissions and @object-ui/fields was the same class -- both went green after building the relevant closures. VITEST from the repo root: `pnpm exec vitest run --maxWorkers=2 packages/components/ packages/plugin-charts/` -> 'Test Files 277 passed (277)', 'Tests 2569 passed (2569)'. GATES derived from the actual diff (objectstack's dispatch-gates.mjs is repo-bound and would answer about the wrong tree, so it was NOT used): check-changeset-no-major exit 0 'No changeset declares a `major` bump.'; check-changeset-presence exit 0; check:control-bytes exit 0 '6229 tracked text file(s)'; check:phantom-deps exit 0; check:side-effects-array exit 0; check:entry-guard exit 0; check:readme-exports exit 0 '421 self-imports judged (421 real, 0 fabricated), 0 unbuilt'; check:eager-closure exit 0. Two gates first read as NOT MEASURED and were then made to measure rather than reported as red: check:readme-exports had 'the population COLLAPSED -- this run proves nothing' (24 unbuilt packages, the 5 named ones were cli/plugin-ai, never components) and check:eager-closure had no report on disk; both went green after the builds. One apparent red, `pnpm run check:changeset-no-major` exit 1, was ERR_PNPM_NO_SCRIPT -- a wrong script name, the MODULE_NOT_FOUND class -- re-run correctly as `node scripts/check-changeset-no-major.mjs`, exit 0. RATCHET, measured rather than assumed: @object-ui/components is sideEffects:true so the deleted file could not be tree-shaken, and the hypothesis was that removing it drops recharts from the console eager closure, leaving the ceiling more than one regression above the payload and turning check:eager-closure red FOR GOING BLIND. A real apps/console build disproves it -- vendor-charts (155.2 KB gzipped) is still eagerly reachable through the plugins, aggregate 3180.1 KB against the 3191.4 KB ceiling (headroom 0.13x the 89 KB regression), no ceiling needs to move. ABLATION of the new pin, both legs: MUTATION restored ui/chart.tsx + the barrel line + the manifest entry from 0d8fd7ce, confirmed ON DISK before reading anything (file present; \"from './chart'\" lines = 1; 'chart' back under manifest.components = True) -> 3 tests | 3 FAILED, the expected direction, each with its intended message. RESTORE leg -> 'Test Files 1 passed', 'Tests 3 passed', confirmed on disk by git diff HEAD empty, git status --porcelain empty, and HEAD-blob hash == worktree hash for ui/index.ts (both dee07bec, non-empty). NO REBUILD LEG WAS NEEDED and this is stated rather than skipped: the pin resolves ../index to the SOURCE barrel, never dist, so no built artefact takes part in the reading. The mutation script carried trap '<restore>' EXIT INT TERM with absolute paths, and its restore assertion CAUGHT A REAL FAILURE on the first run -- `git checkout HEAD -- path` cannot restore a path HEAD deletes, so the tree stayed mutated with exit 0 and only the explicit git diff HEAD check revealed it; corrected to `git rm -f --ignore-unmatch` for the deleted path, and the numbers above are from the corrected run.", "census": { "objectui apps/**, examples/**, packages/**": "ZERO importers of any chart primitive from @object-ui/components. Controls that fired: SchemaRenderer 3004 hits; 'Button' in files importing @object-ui/components 272; './ChartContainerImpl' 5. Matched SYMBOL NAMES anywhere in a file, not just import statements, so namespace access (import * as C; C.ChartContainer) is covered. Populations non-empty: apps/ 2 projects, examples/ 4. The only Chart-shaped symbol imported from components anywhere is ChartSkeleton, a different symbol in src/custom/view-skeleton.tsx that stays.", "GitHub code search org:objectstack-ai": "5 hits, ALL in objectstack-ai/objectui (control fired: the query returned this repo's own files including the deleted one).", "objectstack-ai/hotcrm": "NOT MEASURED -- GET /repos/objectstack-ai/hotcrm returned HTTP 403 to this session.", "objectstack-ai/cloud": "NOT MEASURED -- GET /repos/objectstack-ai/cloud returned HTTP 403 to this session.", "boundary": "REST code search is unavailable to this session entirely: GET /search/code -> HTTP 403, 'sessions are bound to their configured repositories'. Its control did NOT fire, so no zero is claimed from that channel. Code search indexes only what the caller can read, so the org-wide search's silence about hotcrm and cloud is NOT evidence of absence. No real external importer was found in anything readable, so the changeset carries the migration line with no extra migration note; the director seat closes this gap at review." }, "mcp_calls": "4 -- add_issue_comment (claim), search_code (org-wide census), search_issues (dedupe before filing #7625) + search_issues (its lit control, 29 hits). Everything else went over repo-scoped REST curl: issue/comment reads, PR + files listing, label writes, issue create, PR create, body PATCH.", "open_questions": [], "out_of_scope_findings": [ "filed as #7625: recharts stays declared in @object-ui/components after the chart primitives moved out -- zero source references remain (lit control: lucide-react fires), but packages/components/package.json is outside this card's declared file surface and is held by dependabot #7058, so it was not touched here. Label `finding`, no assignee." ] }Generated by Claude Code
os-warren commentedon Sep 4, 2026
Contract review — PR #7626 at head
b53a02ad: PASS atCONTRACT_REVIEW_TIER, with the out-of-scope census gap declared, not closedDirector seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04 ~09:1xZ, on the
domain:uiseat's parking (5537871670: parked for the director seat at tier; "the specific thing to close" = thehotcrm/cloudimporter census).claude/issue-7397-remove-duplicate-chart-primitives(mode:subagentdev under thedomain:uiseat, session_01KbJQ1y1J12nZxYzFWhP8Q3, atopusunder the quota exemption)get_session09:05Z —session_context.modelandlast_served_modelbothclaude-fable-5-1.Read: the card body (corrected), the seat's correction 5528290030, the ruling 5536046538 (batch #28, option a), the claim/dispatch 5537361250, the dev report 5537852858, the PM review 5537871670, the PR body, the non-deleted diff (
shadcn-components.json,ui/index.ts, the changeset, the pin) atb53a02adagainst merge based28d87bf; objectuiorigin/main1cca678for the premises. Not read: the dispatch brief's prose beyond the ruling quotes.① Derived judgments — the contract increment
@object-ui/components: six value exports (chart.tsx:377-384onmain, reachable throughsrc/index.ts:90 → ui/index.ts:20 → ./chart) plus theChartConfigtype alias. A real published-surface removal, as the seat's correction established. The changeset names every specifier that stops resolving and what is not affected (ChartSkeleton, the spec's ownChartConfig). Correct and complete.git grepfor the six names overpackages/**,apps/**,examples/**onmainhits onlyplugin-charts' own files (which reachChartContainerImplby relative path) and one comment inplugin-chatbot/src/ChatbotEnhanced.tsx:3176(its only@object-ui/componentsimport iscn). No importer.hotcrm/cloud: NOT MEASURED — by this seat too. This session's repository scope is objectstack and objectui; it cannot read either repository, so the gap the ui seat asked the director seat to close stays open. It does not change the verdict: the ruling's contingency for an external importer was "the changeset grows a migration line pointing at@object-ui/plugin-charts" — and the changeset already carries exactly that line ("Migration: import the chart primitives from@object-ui/plugin-charts") together with the per-specifier break list. Whatever those two repositories import, the diff and its declaration would be the same. Reported to the maintainer in the seat's chat as a one-line question, not held against the landing.chartmoves fromcomponentstocustomComponentswithmovedToPlugin: "@object-ui/plugin-charts"inshadcn-components.json— the vocabulary the manifest already uses forcalendar-view/chatbot/timeline, soshadcn:update-allcannot rewrite the file; the pinchart-primitives-removed-7397.test.tsholds the barrel surface, the file's absence and the manifest record together, ablated in both directions with the restore proven by state (and the dev caught thatgit checkout HEAD -- pathcannot restore a deleted path — a reusable trap, correctly recorded). Holds.② Semver —
@object-ui/componentsminor, BREAKING spelled out — correctobjectui's own breaking changes ship as
minorwith the break stated (check-changeset-no-majorrefuses a major); the changeset does exactly that, with migration text and the specifier list.plugin-chartsis untouched (it never imported from components).packages/components/package.json's now-unusedrechartsis correctly left to #7625 (the file is held by dependabot #7058).③ Boundary flags
None open (
open_questions: []). The card body's false "unreachable" claim is corrected on the card, as the ruling required.Landing — same stroke
Not governed (five paths under
packages/**and.changeset/).needs:contract-reviewremoved here and on PR #7626 (read-modify-write, read back), PR marked ready, squash auto-merge armed; this seat follows it to MERGED and this card closes onFixes.Generated by Claude Code