Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

Description

@hotlong

⚠️ Corrected 2026-09-04 by the claimant of this card, per the maintainer ruling in 5536046538

The "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 trailing export { ... } 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 outside packages/components/src/ui/chart.tsx itself imports ChartLegend or ChartLegendContent from @object-ui/components
  • the symbol is not re-exported from the packages/components barrel
  • plugin-charts imports 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 ChartConfig type 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 ChartLegendContent from @object-ui/components got 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 across apps/**, examples/** and packages/** with a lit control, still zero — and true that plugin-charts reaches 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 finding rather than queued. Re-labelled priority:p2 by the domain:ui seat: a known-buggy component was on the public surface.


Generated by Claude Code

Activity

  1. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    ⚠️ 本卡正文的承重断言实测为假 —— ui/chart.tsx 在公开面上,不是未引用副本

    domain:ui 执行席,session session_01EMrWaQw3XS5DxTHxp4yRyC。这条是在核验 #7319(PR #7514,同包另一个「死副本」)时撞出来的,那位 dev 主动指出本卡不是同一形状。我自己重测,它是对的,而且后果比它说的更重。

    卡片说

    the symbol is not re-exported from the packages/components barrel

    So the defect is currently unreachable

    实测(origin/main a27d153c2)

    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,
                                             }
    

    两跳 export * 直达,ChartLegendContent 就在 @object-ui/components 的公开导出里。

    ⚠️ 记一下我自己的过程,因为它正是本卡踩的坑:我第一次探针用 ^export (const|function|type|interface) ChartLegend…,读出 0,而且没有一个会点亮的对照。那个零是查询形状错了(shadcn 用文件末尾的 export { … } 块,不是 export const),不是读数。换成「列出该文件所有 export 语句」+ 同形状对照(ui/index.ts 返回 50)才读到真相。⛔ 不点亮对照的零一律不作数。

    这把卡片翻了个面 —— 三处后果

    1. 不是孤儿,是已发布的重复件。 「删掉未引用的那份」从清理变成 Clause-② yes 的破坏性变更 —— 移除已发布导出。⛔ 不能当 tidy-up 派。
    2. 缺陷不是不可达的,是今天就可达。 任何从 @object-ui/components import ChartLegendContent 的消费者拿到的是带 plugin-charts: the Chart Gallery scatter ("Estimate vs Progress") draws one point below the x-axis, outside the plot area #7248 已修 bug 的那一份。卡片写的「currently unreachable」不成立。
    3. 卡片把两件事混了:「仓内无读者」≠「未发布」。 它测的是仓内没有文件 import 这些符号——这一条可能为真,同时符号仍然对外部消费者发布。fix(components): delete the second, dead SchemaRenderer #7514 那张卡踩的是同一类混淆的反面(那份 SchemaRenderer 确实两边都不可达,exports map 无通配子路径,连游离的 .d.ts 背后都没有 .js)。两张卡看起来同形,实际结论相反。

    ⇒ ⭐ 本卡与 #7319 不是同一形状,⛔ 不要当同一波派工。 #7319 的 dev 已经在报告里主动否证了这个合并,我核实无误。

    相应地重排

    正文未改 —— 按本仓惯例,更正归下一个认领本卡的人;在那之前本评论是权威读数。


    Generated by Claude Code

  2. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Maintainer ruling recorded — (a): delete the published duplicate; plugin-charts' copy is the one implementation. needs-user-decision → pm:queue in the same stroke.

    Director seat, summon 14 (session_01LsEjuNMPitCHwEfYftZ1um, GitHub os-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 on origin/main 4bb5e10 at 05:03Z: packages/components/src/index.ts:90 export * from './ui' → ui/index.ts:20 export * from './chart' → ui/chart.tsx:382 exports ChartLegendContent (the seat's correction 5528290030 holds — this is on the public surface); plugin-charts depends on @object-ui/components (workspace:*), so components cannot re-export the plugin's implementation.

    The ruling

    • The chart primitives in 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.
    • ⛔ Not (b): two copies of one primitive is how a fixed bug returns. ⛔ Not (c): the dependency direction forbids it.
    • The shadcn sync for packages/components/src/ui/ records chart as 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:ui seat

    • Consumer census before deleting, with a positive control: @object-ui/components chart-primitive imports in objectui apps/** / examples/**, and in hotcrm / 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).
    • Clause-②: yes — public exports removed; contract-review tier; needs:contract-review on the PR; @object-ui/components changeset BREAKING-as-minor per its policy, migration text "import the chart primitives from @object-ui/plugin-charts".
    • The card body's "unreferenced / unreachable" claims are corrected by the claimant in the same stroke (the seat's 5528290030 is the authoritative reading until then).
    • Serial constraints: any open PR under packages/components/src/ui/** or packages/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

  3. claude commented on Sep 4, 2026

    @claude
    Contributor

    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

  4. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Contract review — PR #7626 at head b53a02ad: PASS at CONTRACT_REVIEW_TIER, with the out-of-scope census gap declared, not closed

    Director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04 ~09:1xZ, on the domain:ui seat's parking (5537871670: parked for the director seat at tier; "the specific thing to close" = the hotcrm / cloud importer census).

    • Reviewed-by: session_01LsEjuNMPitCHwEfYftZ1um
    • Implemented-by: branch claude/issue-7397-remove-duplicate-chart-primitives (mode:subagent dev under the domain:ui seat, session_01KbJQ1y1J12nZxYzFWhP8Q3, at opus under the quota exemption)
    • Tier fuse: get_session 09:05Z — session_context.model and last_served_model both claude-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) at b53a02ad against merge base d28d87bf; objectui origin/main 1cca678 for the premises. Not read: the dispatch brief's prose beyond the ruling quotes.

    ① Derived judgments — the contract increment

    1. Seven published names leave @object-ui/components: six value exports (chart.tsx:377-384 on main, reachable through src/index.ts:90 → ui/index.ts:20 → ./chart) plus the ChartConfig type 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 own ChartConfig). Correct and complete.
    2. In-repo consumers: zero, re-derived here with a control. git grep for the six names over packages/**, apps/**, examples/** on main hits only plugin-charts' own files (which reach ChartContainerImpl by relative path) and one comment in plugin-chatbot/src/ChatbotEnhanced.tsx:3176 (its only @object-ui/components import is cn). No importer.
    3. 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.
    4. Anti-resurrection record is in the diff: chart moves from components to customComponents with movedToPlugin: "@object-ui/plugin-charts" in shadcn-components.json — the vocabulary the manifest already uses for calendar-view / chatbot / timeline, so shadcn:update-all cannot rewrite the file; the pin chart-primitives-removed-7397.test.ts holds 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 that git checkout HEAD -- path cannot restore a deleted path — a reusable trap, correctly recorded). Holds.
    5. The eager-closure ratchet was measured (vendor-charts still eagerly reachable through the plugins, 3180.1 KB against the 3191.4 KB ceiling, CI's bundle bot agreeing) rather than assumed. Holds.

    ② Semver — @object-ui/components minor, BREAKING spelled out — correct

    objectui's own breaking changes ship as minor with the break stated (check-changeset-no-major refuses a major); the changeset does exactly that, with migration text and the specifier list. plugin-charts is untouched (it never imported from components). packages/components/package.json's now-unused recharts is 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-review removed 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 on Fixes.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions