Repository navigation
feat(quadrant): native quadrant charts across every surface - #564
Merged
Merged
Conversation
Collaborator
Author
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Contributor
Sketchi Web PreviewStatus:
|
Contributor
Sketchi Icons PreviewStatus:
|
Contributor
Sketchi Eval Harness PreviewStatus:
|
Contributor
Sketchi Excalidraw Workspace PreviewStatus:
|
Contributor
Sketchi Playground / Studio PreviewStatus:
|
anandpant
force-pushed
the
feat/quadrant-charts
branch
from
October 29, 2026 16:25
ba2ecec to
b9da89e
Compare
anandpant
force-pushed
the
feat/quadrant-charts
branch
from
October 29, 2026 16:49
b9da89e to
ab42725
Compare
anandpant
force-pushed
the
feat/quadrant-charts
branch
2 times, most recently
from
October 29, 2026 17:24
29b60cf to
a97af66
Compare
Merge activity
|
## What changes Native quadrant charts (#555). A prompt like "effort vs impact quadrant chart of our backlog" or "Eisenhower matrix of my tasks" used to go through `createCanvas` with model-placed coordinates. Now the model sends axes, quadrant names and scores, and Sketchi lays the chart out. - **Contract** (`quadrant`). `xAxis` and `yAxis` are `{ title, low, high }`: the end labels are free text, so an axis may run Urgent → Not urgent. `quadrants` is named by corner (`topLeft` = low x, high y; `topRight`, `bottomLeft`, `bottomRight`), which fixes the order. `points: [{ label, x, y, group? }]` (up to 30) has `x` and `y` in 0..1, so a score out of 100 is score / 100. No field takes coordinates. Validation returns repair hints for a score outside 0..1, a repeated label, more than 30 points or 6 groups, blank text, and text too long to place. - **Layout** (`renderer/src/quadrant.ts`): - A square plot of four tinted quadrants, each name in its outer corner. A name slides along the edge (then wraps, then moves inward) to stay clear of every dot and leave room for that dot's label. - The axes are arrows toward their high ends. x runs under the plot (low label left, high right, title between); y runs beside it (high label top, low bottom, title between). - Every dot sits at its exact position, drawn over the middle lines with a surface ring, so a point on a midline, edge or corner stays readable. - Each label takes the first clear spot beside its dot, most constrained point first, off the middle lines where it can, and only where it is nearer its own dot than any other dot. - A reader pairs a label with a dot level with it, or under or over it. So a corner spot that lines up with another dot within 48 px, and with neither its own dot's row nor its column, waits behind every leader: the label takes a spot that pairs clearly, else a short leader. - Crowded labels move out on leader lines. A leader crosses no other leader, label, name or other clump's dot, and leaders keep their far halves apart. A clump of near-identical dots stacks its labels in a column beside it. A label with no clear spot can push the one label in its way to a new spot. - A crowded chart grows its plot from 560 to 960 px until it reads. Past that, labels go a size smaller and the search runs longer. Only after that may a leader pass over another dot. A chart is rejected only after it has tried every plot size and the smaller labels. That fallback search runs within a fixed, deterministic budget of spot checks, so a chart that can never read fails fast. - Labels wrap at whole words and an authored newline is a line break. Both dividers and both axes are always drawn whole, and no label sits across a divider. - The plot starts large enough for every quadrant name's longest word and for the x axis's two end labels side by side. - **Never published unreadable**: a chart that still cannot place every label clear after growing, shrinking labels and searching longer is rejected (`invalid_quadrant_chart`, `quadrant_too_large`) with a repair hint: spread points that nearly coincide, shorten labels or names, or split the chart. - **Excalidraw**: a new `quadrant-mark` renderer role (square corners, roughness 0, no generated labels; patches keep it). Tints are grouped with their names, dots with their labels and leaders, axes with their labels, and legend keys with their names. Exports validate after a save and reload. - **Code Mode and MCP**: `buildQuadrantChart`, `docs({ topic: "buildQuadrantChart" })`, and three issue codes (`quadrant_too_large`, `invalid_quadrant_point`, `invalid_quadrant_text`). Docs cost, estimated at about 4 characters per token: - execute description head: +20 chars (the builder name), about 5 tokens. With wireframes the head would have reached 1,606 of its 1,600; shortening one helper line ("fix the spec per hint and rebuild") brings it back under; - `code` parameter: +508 chars (one summary line and one spec line), about 125 tokens (budget 12,400 → 12,920; tools/list budget 15,450 → 16,030); - topic, served on demand: 1,757 chars, about 440 tokens; - generation system prompt: about 920 chars, about 230 tokens. - **Every other family surface**: - generation IR rules, an example, a `points` count target and `point`/`quadrant` label targets; - `generate --type quadrant` (API and CLI) and `sketchi create`, with a changeset; - Studio `build_quadrant_chart` and `POST /api/v1/quadrant-charts/build`; - CLI PNGs skip the title banner (the chart draws its own title); - Storybook stories, both `llms.txt`, both READMEs, and `docs/quadrant-charts.md`. ## Scenarios From frontierclimb's draft §5: - `effort-impact-quadrant` (challenge): 15 items by score, 8 crowded into Quick wins, near-identical positions, Admin role templates on the effort midline, long labels. - `eisenhower-matrix-quadrant` (smoke): 8 tasks placed by quadrant only; the axis direction is left open. - `competitor-positioning-quadrant` (standard): 9 products by score, Budget and complex left empty, Our product in its own color. Checks read every placement through the axis ends as drawn, so an axis that runs high to low passes when every name and point follows it. They also accept swapped axes when a prompt leaves them open. - `axis:x|y` (variable by title), `axis-ends:x|y` (stated end labels, either direction), `quadrant:<key>` (each name in the corner its conditions describe). - `point:<key>`, `points-exact`, `position:<key>` (±0.02 of score / 100, mirrored for a reversed axis), and `quadrant-of:<key>` for membership-only items (strictly inside, at least 0.03 from both midlines). - `highlight:<key>` (a group no other point shares) and `empty:<key>`. - `quadrant-drawn` (output): every dot within 0.5 px of its position with its label, each name inside its own quadrant, both axes' titles and end labels. - `quadrant-layout` (hint): no label overlaps text or a dot, every label sits in the plot nearest its own dot or on a leader to it, none lines up with another dot instead of its own, and no line crosses text. ## Verification - **Tests that fail on main**: core contract (including zero scores, blank text, `constructor`/`__proto__` names), renderer (tints and corner names, axes, exact dots, boundary points, a dot in a name's corner, crowded clusters on leaders, ten points at one spot, plot growth, groups, whole words, authored newlines), Excalidraw conversion and reload, Code Mode (documented example, patch keeps the role — red with that clause removed — repair codes, malformed `points` rejected with a hint rather than dropped, round-trip), generation, scenarios (each expected diagram passes; every mutation fails its own check; reversed axes pass), CLI offline failure and PNG without banner, MCP docs and budgets, route lists. - **Fuzz** (`pnpm nx run diagram-excalidraw:quadrant-fuzz`; CI runs 14): 700 random charts covering uniform spreads, clusters, round scores on midlines and edges, near-coinciding points, one-quadrant crowds, all points at one spot, long and non-Latin labels, long names, and groups. Results: 0 defects (exact dots, labels inside the plot and clear of every text and dot, nearest their own dot or on a leader, whole words, valid export). 10 shortfalls (1.4%, capped at 2%), all in crowded charts: in 8 a leader passes over another dot, and in 2 a label lines up with another dot because no leader in reach was clear. Render time: median 10 ms, p95 338 ms, max 1.2 s (crowded 25–30 point charts). A chart that can never read (30 points at one corner) is rejected in 0.4–0.6 s locally, after the bounded fallback. No fuzz chart is rejected; the fuzz counts a rejection as a defect, and also fails any divider or axis drawn in pieces or crossed by a label. The three scenarios take 7–50 ms. - **Scenario CLI `--fixture`**: effort-impact 42/42, Eisenhower 28/28, competitor 32/32. - **Render identity**: a harness hashes the scene of each of the 3 scenario specs, the 3 preview-replay specs and the 700 fuzz charts. The tier fix (`5679928c`) renders all 706 byte-identically to `a97af669`, the commit the QD564 A/B ran on. The pairing fix (`421c98f6`) changes effort-impact's scenario and replay specs and 210 fuzz charts. Eisenhower and competitor stay byte-identical. Across the fuzz, labels that line up with another dot drop from 253 to 2. - **PNGs**: I inspected every scenario plus stress cases (clusters at corners, duplicate corner points, 14-point corner clumps, long labels). - **Local** (at `421c98f6`, on main `e74b914a`; the last rebase only took a version bump): `pnpm nx affected -t typecheck,test,build --base=origin/main` (18 projects), `pnpm run check`, `pnpm run test:tools`, `pnpm nx build-storybook diagram-ui`, `pnpm run test:wrangler-dry-runs`. - **Preview MCP replay** at `ba2ecec0`, and again at `b9da89eb` after restacking onto Venn and journey maps (`pr-564-sketchi-studio` MCP). For each scenario I wrote one `buildQuadrantChart` call from its prompt. Each was accepted on the first call in 0.8–1.4 s (8.5 s for the first call on a cold preview) and graded with `--input`: effort-impact 43/43, Eisenhower 29/29, competitor 33/33. The hosted PNGs match the local ones. - **Real page** (`agent-browser -p browserbase`, preview at `ba2ecec0`): the effort-impact artifact page renders with no page or console errors and survives a reload. In the editor, dragging the Dark mode dot moves its label with it as one group. ## Known limits - A 25–30 point chart crowded into tight clusters can take up to about a second to place. In about 1.4% of fuzz charts, all of them crowded, a leader passes over another dot or a label lines up with another dot. Shared files: additive edits to `DIAGRAM_TYPES`, `documents.ts`, `catalog.ts`, `messages.ts`, `intent.ts`, CLI generation types, `llms.txt` and `canvas.ts` (the new role), resolved alongside Venn, journey maps, Sankey and wireframes. `scene.ts` and other families' renderers are untouched; the quadrant palette reuses `CHART_SERIES_COLORS`. This is one PR rather than a stack: `type-structure.test.ts` rejects a contract module that is not registered, and registering a family requires every pipeline stage.
graphite-app
Bot
force-pushed
the
feat/quadrant-charts
branch
from
October 29, 2026 18:43
bb17240 to
854907c
Compare
Merged
This branch was successfully deployed
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.

What changes
Native quadrant charts (#555). A prompt like "effort vs impact quadrant chart of our backlog" or "Eisenhower matrix of my tasks" used to go through
createCanvaswith model-placed coordinates. Now the model sends axes, quadrant names and scores, and Sketchi lays the chart out.quadrant).xAxisandyAxisare{ title, low, high }: the end labels are free text, so an axis may run Urgent → Not urgent.quadrantsis named by corner (topLeft= low x, high y;topRight,bottomLeft,bottomRight), which fixes the order.points: [{ label, x, y, group? }](up to 30) hasxandyin 0..1, so a score out of 100 is score / 100. No field takes coordinates. Validation returns repair hints for a score outside 0..1, a repeated label, more than 30 points or 6 groups, blank text, and text too long to place.renderer/src/quadrant.ts):invalid_quadrant_chart,quadrant_too_large) with a repair hint: spread points that nearly coincide, shorten labels or names, or split the chart.quadrant-markrenderer role (square corners, roughness 0, no generated labels; patches keep it). Tints are grouped with their names, dots with their labels and leaders, axes with their labels, and legend keys with their names. Exports validate after a save and reload.buildQuadrantChart,docs({ topic: "buildQuadrantChart" }), and three issue codes (quadrant_too_large,invalid_quadrant_point,invalid_quadrant_text). Docs cost, estimated at about 4 characters per token:codeparameter: +508 chars (one summary line and one spec line), about 125 tokens (budget 12,400 → 12,920; tools/list budget 15,450 → 16,030);pointscount target andpoint/quadrantlabel targets;generate --type quadrant(API and CLI) andsketchi create, with a changeset;build_quadrant_chartandPOST /api/v1/quadrant-charts/build;llms.txt, both READMEs, anddocs/quadrant-charts.md.Scenarios
From frontierclimb's draft §5:
effort-impact-quadrant(challenge): 15 items by score, 8 crowded into Quick wins, near-identical positions, Admin role templates on the effort midline, long labels.eisenhower-matrix-quadrant(smoke): 8 tasks placed by quadrant only; the axis direction is left open.competitor-positioning-quadrant(standard): 9 products by score, Budget and complex left empty, Our product in its own color.Checks read every placement through the axis ends as drawn, so an axis that runs high to low passes when every name and point follows it. They also accept swapped axes when a prompt leaves them open.
axis:x|y(variable by title),axis-ends:x|y(stated end labels, either direction),quadrant:<key>(each name in the corner its conditions describe).point:<key>,points-exact,position:<key>(±0.02 of score / 100, mirrored for a reversed axis), andquadrant-of:<key>for membership-only items (strictly inside, at least 0.03 from both midlines).highlight:<key>(a group no other point shares) andempty:<key>.quadrant-drawn(output): every dot within 0.5 px of its position with its label, each name inside its own quadrant, both axes' titles and end labels.quadrant-layout(hint): no label overlaps text or a dot, every label sits in the plot nearest its own dot or on a leader to it, none lines up with another dot instead of its own, and no line crosses text.Verification
constructor/__proto__names), renderer (tints and corner names, axes, exact dots, boundary points, a dot in a name's corner, crowded clusters on leaders, ten points at one spot, plot growth, groups, whole words, authored newlines), Excalidraw conversion and reload, Code Mode (documented example, patch keeps the role — red with that clause removed — repair codes, malformedpointsrejected with a hint rather than dropped, round-trip), generation, scenarios (each expected diagram passes; every mutation fails its own check; reversed axes pass), CLI offline failure and PNG without banner, MCP docs and budgets, route lists.pnpm nx run diagram-excalidraw:quadrant-fuzz; CI runs 14): 700 random charts covering uniform spreads, clusters, round scores on midlines and edges, near-coinciding points, one-quadrant crowds, all points at one spot, long and non-Latin labels, long names, and groups. Results: 0 defects (exact dots, labels inside the plot and clear of every text and dot, nearest their own dot or on a leader, whole words, valid export). 10 shortfalls (1.4%, capped at 2%), all in crowded charts: in 8 a leader passes over another dot, and in 2 a label lines up with another dot because no leader in reach was clear. Render time: median 10 ms, p95 338 ms, max 1.2 s (crowded 25–30 point charts). A chart that can never read (30 points at one corner) is rejected in 0.4–0.6 s locally, after the bounded fallback. No fuzz chart is rejected; the fuzz counts a rejection as a defect, and also fails any divider or axis drawn in pieces or crossed by a label. The three scenarios take 7–50 ms.--fixture: effort-impact 42/42, Eisenhower 28/28, competitor 32/32.5679928c) renders all 706 byte-identically toa97af669, the commit the QD564 A/B ran on. The pairing fix (421c98f6) changes effort-impact's scenario and replay specs and 210 fuzz charts. Eisenhower and competitor stay byte-identical. Across the fuzz, labels that line up with another dot drop from 253 to 2.421c98f6, on maine74b914a; the last rebase only took a version bump):pnpm nx affected -t typecheck,test,build --base=origin/main(18 projects),pnpm run check,pnpm run test:tools,pnpm nx build-storybook diagram-ui,pnpm run test:wrangler-dry-runs.ba2ecec0, and again atb9da89ebafter restacking onto Venn and journey maps (pr-564-sketchi-studioMCP). For each scenario I wrote onebuildQuadrantChartcall from its prompt. Each was accepted on the first call in 0.8–1.4 s (8.5 s for the first call on a cold preview) and graded with--input: effort-impact 43/43, Eisenhower 29/29, competitor 33/33. The hosted PNGs match the local ones.agent-browser -p browserbase, preview atba2ecec0): the effort-impact artifact page renders with no page or console errors and survives a reload. In the editor, dragging the Dark mode dot moves its label with it as one group.Known limits
Shared files: additive edits to
DIAGRAM_TYPES,documents.ts,catalog.ts,messages.ts,intent.ts, CLI generation types,llms.txtandcanvas.ts(the new role), resolved alongside Venn, journey maps, Sankey and wireframes.scene.tsand other families' renderers are untouched; the quadrant palette reusesCHART_SERIES_COLORS.This is one PR rather than a stack:
type-structure.test.tsrejects a contract module that is not registered, and registering a family requires every pipeline stage.