发布

  • Analytics: show every provider label on the category charts (#890) (#959)

    frostbyte_neo 发布于 2026-08-22 00:41:05 +00:00

    • Analytics: show every provider label on the category charts (#890)

    recharts' default interval is 'preserveStartEnd': when more providers than
    fit in the width, it silently drops the middle ticks, so only a few provider
    names render, the rest are hover-only, and a bar no longer lines up with the
    label the user can see (a bar with no label reads as a different provider).

    Force every tick (interval=0) and rotate + truncate the labels so they fit,
    via a small pure helper (lib/chart-axis.ts) applied to the five category
    charts: requests by provider/agent, latency by provider, TTFT by provider and
    errors by provider. The timeline charts are time series and are untouched.

    • client/src/lib/chart-axis.ts: categoryAxisProps() + truncateAxisLabel
    • client/src/pages/AnalyticsPage.tsx: spread the props on the 5 category X-axes
    • client/src/lib/chart-axis.test.ts: regression tests for the helper
    • Analytics: cover the errors-by-category axis and stop the tilted labels colliding (#890)

    Follow-ups to the first pass at #890:

    • "Errors by category" is a layout="vertical" chart, so its category axis is
      the YAxis. It was left on recharts' default interval and dropped labels the
      same way the vertical charts did. Added verticalCategoryAxisProps() — every
      tick, plus the 128px gutter and a truncation cap sized to it.

    • categoryAxisProps() was a fixed -30 degrees at height 56, which forced all
      the ticks on and then let them overlap on a crowded or narrow (single-column
      mobile) chart, and under-reserved the strip an 18-char label needs. It now
      takes the category count and picks the shallowest tilt that still seats them
      on a 280px plot (-30, -45, -90), with the height derived from the tilt and
      the truncation cap instead of hard-coded.

    • Callers pass their data.length.

    • Added a render test that mounts a real recharts BarChart under jsdom and
      counts the tick elements, so the props are proven to reach recharts
      rather than merely to have the right shape. Stubs getBoundingClientRect,
      which recharts uses to measure labels and jsdom answers with zeroes.

    • The comment claimed recharts defaults to 'preserveStartEnd'; both
      implicitXAxis and implicitYAxis default to 'preserveEnd'.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • Fix the client test job on Node 20: pin jsdom to 27 and declare it at the root

    The render test added in the previous commit brought in jsdom@30, which broke
    "Test & build (Node 20)" — 177 tests passed but vitest exited nonzero on an
    unhandled module-load error:

    TypeError: webidl.util.markAsUncloneable is not a function
      at new CacheStorage node_modules/undici/lib/web/cache/cachestorage.js:20
    

    Two separate problems, both fixed here.

    1. jsdom 28+ depends on undici, and jsdom 30 wants undici ^8.9.0. undici 8
      declares engines: >=22.19.0 and does

      const { markAsUncloneable } = require('node:worker_threads')
      webidl.util.markAsUncloneable = markAsUncloneable
      

      with no guard. worker_threads.markAsUncloneable landed in Node 22.10, so on
      Node 20 that assigns undefined and the first call throws. (undici 6, which
      the server pins, writes markAsUncloneable || (() => {}); undici 7
      feature-detects. Only 8 assumes it.) jsdom 30's own engines field says
      ^22.22.2 || ^24.15.0 || >=26.0.0 — it never supported the Node 20 this
      repo declares in engines and tests in CI; nothing enforced that.

      jsdom is now pinned to ^27, the last major with no undici dependency at
      all, so the failure mode cannot come back through a transitive bump.
      undici is now absent from the root tree entirely — the only copy left is
      the server's own direct 6.26.0.

    2. jsdom was declared in client/package.json, and npm nested it at
      client/node_modules/jsdom rather than hoisting it. vitest hoists to the
      root node_modules and resolves an environment package relative to itself,
      so it never looks in client/node_modules — the run died with "Cannot find
      package 'jsdom'", i.e. the same 177-pass/nonzero-exit symptom for a
      different reason. Whether npm hoists or nests depends on how the rest of
      the tree deduplicates, which is why this differed between Node versions.

      Declaring it in the root package.json puts it beside vitest deterministically.

    Verified every package in the installed tree now accepts Node 20.19/20.20;
    npm ci reproduces the tree from the lockfile; full npm test is green
    (server 2499, cli 95, client 182).

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com


    Co-authored-by: tashdroid 319142293+tashdroid@users.noreply.github.com
    Co-authored-by: Tash tash@MacBook-Pro.local
    Co-authored-by: Claude Fable 5 noreply@anthropic.com

    下载附件