Skip to content

plugin-charts: the Chart Gallery scatter ("Estimate vs Progress") draws one point below the x-axis, outside the plot area #7248

Description

@hotlong

What happens

Showcase → Analytics → Chart Gallery → Estimate vs Progress (scatter, x = estimate hours 0-100, y = progress 0-60): six points sit inside the axes and a seventh is drawn below the x-axis baseline, roughly at x≈40, y≈-10 in plot coordinates — outside the axis domain, overlapping the axis label area.

Screenshot from the pin-bump dogfood at ~290px widget width; objectui @ 67dadd6. The data comes from the seeded showcase tasks (estimate_hours × progress), where the task in question has a progress value that the y-axis domain does not include (likely 0 or null being mapped past the origin).

Expected

The y-domain includes every plotted value (or the point is dropped and the widget says so — plugin-charts already has the "cannot place a row" notice from #7197 for scatter); a point must never render outside the plot area.

Possibly related: objectui#7194 (multi-series scatter y values) — this one is single-series.

Found during the objectstack pin-bump dogfood (objectstack#14295).

Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 2, 2026
  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 2, 2026
  3. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim: PM loop round 5 (dogfood dispatch)
    Session: session_62849a16-0144-4728-8942-9f60bb3a73f1
    Branch: claude/issue-7248-scatter-y-domain
    Worktree: objectui-issue-7248
    Domain: domain:ui
    File surface: packages/plugin-charts/src/** (the scatter series/axis domain and the #7197 cannot-place notice) + tests, .changeset/ (stop on breach; explain in the report)
    Container & model: M, model: opus, mode:subagent
    Clause-②: no
    Serial constraints cleared: serialized AFTER #7385 (same package, AdvancedChartImpl.tsx) — dispatch waits for its merge and the branch starts from a main that carries it; #7250 (in flight) is barred from AdvancedChartImpl.tsx; no other open objectui PR touches plugin-charts (REST pulls list 2026-09-03)

    Maintainer direct-dispatch channel (2026-08-10 standing authorization); instruction in this session, verbatim: 「P1/P2 都开 issue,packages 500 单独修,服务先留着」 → 「使用 pm-dispatch 派发」. Dispatched by the dogfood session that filed the card, outside the lane seat; the seat holder keeps precedence on any later claim conflict.

    Generated by Claude Code

  4. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim: os-dev subagent picking this up.

    Session: session_62849a16-0144-4728-8942-9f60bb3a73f1
    Branch: claude/issue-7248-scatter-y-domain
    Worktree: objectui-issue-7248, branched from origin/main @ d6fe1e1 (carries #7385).

    Starting on the scatter y-domain now.

    Generated by Claude Code

  5. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 7248,
      "status": "done",
      "branch": "claude/issue-7248-scatter-y-domain",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7398",
      "premise_still_valid": false,
      "summary": "The reported symptom is real and fixed, but the card's diagnosis was wrong: the seventh mark is not a data point and the y domain was innocent. ChartLegendContent resolves a label as config[nameKey || item.dataKey || 'value'], and a Scatter carries no dataKey (scatter's keys live on the axes, not the mark), so the key collapsed to the literal 'value', missed a config keyed by measure name, and the legend rendered its colour swatch with NO text: an 8x8 square in --chart-1, the same colour as the marks, under the x-axis. Measured in real Chromium the swatch sat at cy 341 against a plot area ending at cy 295 on a 4.835 px/unit scale, i.e. y = -9.5 at x = 45 -- the card's 'x approx 40, y approx -10' to the pixel -- while all six real marks were inside the plot area at every viewport width from 1440 down to 480. The live showcase API independently rules out the 'bad row' hypothesis: 10 seeded tasks, every progress a finite non-negative number, aggregating to six buckets. Fix is two parts: the scatter passes nameKey so its legend resolves the measure's config, and ChartLegendContent falls back to item.value (the series name recharts itself puts on the legend item) so no family can paint a nameless swatch again. The y domain is deliberately untouched -- clamping it, the fix the card asked for, would have CREATED the described bug, which is pinned with mixed-sign and all-negative fixtures.",
      "tests": "Union re-run on the final commit d2537532b. (1) pnpm exec vitest run packages/plugin-charts/ --maxWorkers=2 from the repo root -- 'Test Files 42 passed (42) / Tests 390 passed (390)'; the package-scoped pnpm --filter PKG test form is the silent-green trap objectui AGENTS.md bans and was not used. (2) pnpm --filter @object-ui/plugin-charts type-check -- exit 0 (script echoed as 'tsc --noEmit'); tsc --listFiles confirms AdvancedChartImpl.tsx, ChartContainerImpl.tsx AND the new test file are inside the program, so this green is NOT the excluded-test-file NOT-MEASURED case. (3) pnpm exec eslint . in packages/plugin-charts -- exit 0, '318 problems (0 errors, 318 warnings)', 55 files linted (count from --format json), all three touched files present in the linted set. Every exit code captured by redirect-then-read, never through a pipe. ABLATION: each half ablated from the COMMITTED state, restore trap on absolute paths, mutation proved on disk by HEAD-blob-hash comparison plus an anchor grep count, git diff HEAD empty after every leg. No rebuild leg applies -- the test imports ./AdvancedChartImpl which imports ./ChartContainerImpl, both relative SOURCE paths, no dist in the resolution. Results: baseline 7 passed; remove nameKey only (640e2290 to 52a84b18, anchor count 0) 1 red; remove the item.value fallback only (117306ba to 525ac855, anchor count 0) 2 red; remove BOTH (origin/main behaviour) 4 red including the headline assertion. The ablation changed the PR: the first run showed removing nameKey alone left ALL assertions green because the item.value fallback masks it on the label, so rather than ship an unenforced change a pin was added for the one branch the fallback cannot substitute for -- itemConfig.icon, which only resolves when the config lookup hits. Browser geometry (Playwright 1.62.1, real Chromium, viewport 1440x1000, widget svg 510x350): BEFORE plot y 5..295, marks cy 215.3/101.7/5/179/150/237 all inside, legend swatch cx 255 cy 341 with empty innerText; AFTER plot y 5..287, marks cy 209.5/99/5/174.2/146/230.6 all inside, legend text 'Avg Estimate'.",
      "mcp_calls": "0 -- GitHub reads and writes all went through the REST channel (gh api / gh issue / gh pr), probed green at the start with a repo-scoped read",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #7396: a scatter's extreme points are half-clipped by the plot edge -- the numeric axes get no domain padding, so with plot x 53..505 and marks at cx 53 and cx 505 about half of each extreme symbol paints outside the plot area; real, different mechanism, and padding would change every scatter's appearance",
        "filed as #7397 (label finding): packages/components/src/ui/chart.tsx is an unreferenced duplicate of the plugin-charts chart primitives carrying this same legend hole -- imported by nothing and absent from the barrel, so currently unreachable; left untouched rather than widening this PR across a second published package"
      ],
      "facet_answers": {
        "7197_cannot_place_notice": "not the mechanism, unchanged -- that notice answers rows carrying no number and every gallery row has two; pinned here as no note and no refusal. Also 7197 was already an ANCESTOR of the build the card was filed against (67dadd602a3a), so it was never the missing piece.",
        "7194_multi_series_scatter": "real, DISTINCT root cause, left out of scope -- YAxis binds dataKey=series[0].dataKey for every series and each Scatter receives the same unprojected data, so every series does plot at the first series' y values; that is axis binding, not label resolution, and this card's chart is single-series",
        "7385_categorical_axis": "no interaction, as predicted -- scatter uses type=number axes and never reaches the categorical thinning path; not reverted, not touched"
      },
      "declared_deviations": {
        "verify_lock": "os-verify-lock ran DECLARED UNLOCKED on this host (verbatim: no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized); all heavy runs still went through the entry point and the verdict is declared in the PR body",
        "lint_narrowing": "lint run at package scope, not repo-wide. Population from eslint's own config resolution, count 55 from --format json, and the config is NOT type-aware (no parserOptions.project / projectService) so this diff cannot move the verdict on any untouched file. CI runs the full farm regardless.",
        "bounded_in_place_fix": "the ChartContainerImpl.tsx half (the item.value fallback) is a declared bounded in-place fix: same defect class, at the layer that decides whether a swatch gets a name, same package and gate family, no new verification surface, and named with evidence in the PR body"
      },
      "issue_body_completeness": "complete -- all four segments the dispatch named were present: the x approx 40 / y approx -10 observation, the data source (seeded tasks estimate_hours x progress), the Expected section, and the 7194 reference. No sanitizer truncation."
    }

    Generated by Claude Code

  6. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    ACCEPT — reviewer of record: PM session session_62849a16-0144-4728-8942-9f60bb3a73f1.

    Checked against GitHub, not the report: draft PR #7398 targets main, first line Fixes #7248, no other closing keyword, changed files stay inside the claimed surface (AdvancedChartImpl.tsx scatter nameKey, ChartContainerImpl.tsx legend fallback, one test, changeset; no governed path), every check-run green at head d253753. Spot-read: the "stray point" was the scatter legend's nameless swatch (no dataKey ⇒ label lookup collapsed to value); measured cy 341 vs plot end 295 matches the card's y≈-10 to the pixel; the y domain is untouched and pinned against clamping with mixed-sign fixtures; ablation of each half turned only its own pins red; findings #7396 (edge clipping) and #7397 (duplicate chart.tsx) filed. Flipping to ready and arming queue merge.

    PR: #7398

    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

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