Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ Each entry records:
| F-312 | Tick stubs could not be styled independently | API | resolved |
| F-313 | Initial observer precision cancelled entrance motion | Tooling | resolved |
| F-314 | Framework ticks and immediate focus layers were unclear | Documentation | resolved |
| F-315 | Orthogonal dependency connectors need a hand-built curve | API | monitoring |
| F-316 | Keyboard focus cannot follow a dependency | API | monitoring |
| F-317 | Time-ranged bars cannot inset only their lane edges | API | monitoring |

## Findings

Expand Down Expand Up @@ -9317,3 +9320,70 @@ Each entry records:
direction changes. A focus dot appears during entrance while the data path
and its running animation remain intact. Concurrent inline states remain a
separate feature; this addresses the documented alternatives requested.

### F-315 - Orthogonal dependency connectors need a hand-built curve

- Status: monitoring
- Severity: medium
- Owner: API
- Observed in: catalog case 198, a Gantt plan with finish-to-start
dependencies
- Friction: `link` draws a straight segment and `arrow` draws a straight
arrow. A right-angle connector that leaves the predecessor's right edge,
steps, and enters the successor's left edge, plus its arrowhead, had to be
written as a d3 `CurveFactory` passed to the link's `curve` through
`d3Curve`. The curve sees only the two endpoint pixels. It cannot read the
resolved band step, so the definition estimates the lane gutter for backward
detours from the outer chart height. It cannot tell a bar from a milestone,
so dependencies into the milestone need a second link mark with a larger end
gap.
- Decision: keep the recipe. The public curve hook is enough and adds no bytes
to other charts. Consider a connector primitive with resolved band geometry
and target-aware end gaps only if a second timeline or network case repeats
this routing.
- Verification: case 198 passes the standard 320, 640, and 960 px light and
dark conformance matrix, before and after its revision update, with 13
right-angle connectors in both renderers. The same curve factory drives the
Observable Plot reference.

### F-316 - Keyboard focus cannot follow a dependency

- Status: monitoring
- Severity: low
- Owner: API
- Observed in: catalog case 198, a Gantt plan with finish-to-start
dependencies
- Friction: default keyboard navigation steps through focusable points by x
center, then y, so phase summaries interleave with tasks instead of
following the lane tree. Changing only the order requires a complete focus
strategy. Its `resolve` receives points but not the scene, so the default
geometry-aware pointer hit test (`findNearestPoint` needs the scene) cannot
be reused; the recipe spreads `focusNearestY` and replaces `navigation` with
a y-then-x sort. Decorative connectors correctly add no focus stops, but no
key moves from a task to its predecessor or successor. The recipe names each
task's predecessors in its tooltip text instead.
- Decision: accept the lane-order strategy and text description for this
recipe. Graph navigation would add interaction state to the default host,
which is outside the current stop boundary. Revisit with a second dependency
or network case.
- Verification: the case 198 unit tests confirm one focus stop per phase,
task, and milestone, none for connectors or progress fills, and a
navigation order that matches the lane order.

### F-317 - Time-ranged bars cannot inset only their lane edges

- Status: monitoring
- Severity: low
- Owner: API
- Observed in: catalog case 198, a Gantt plan with phase summary bars
- Friction: `rect` `inset` removes pixels from all four edges, so a thinner
phase summary bar inside its band lane would also shorten its time extent.
`barX` `inset` trims only the categorical edges, but `barX` rejects `Date`
fields for `x1` and `x2` on a UTC axis (TS2322). The recipe uses
full-height bars and band padding instead of thinner summary bars.
- Decision: keep full-height bars for now. Consider either `Date` endpoints
on `barX` or an axis-specific `rect` inset if another timeline case needs
bars thinner than their lane.
- Verification: a strict compile probe of `barX(rows, { x1: 'start', x2:
'end', y: 'lane' })` with `Date` fields fails with TS2322. Case 198 uses
`rect` with `inset: 0`.
5 changes: 4 additions & 1 deletion PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2490,7 +2490,10 @@ The third tranche pressures composition and performance:

1. Multi-pane financial chart with synchronized crosshair, viewport, and
logical-range navigation.
2. Gantt/resource timeline with nested groups and dependencies.
2. Gantt/resource timeline with nested groups and dependencies: recipe
implemented as conformance case 198 over existing marks; a reusable
connector primitive and dependency keyboard traversal remain pending
(F-315, F-316).
3. General 100k/1m-row render, update, streaming, decimation, and lifecycle
pressure is implemented; pan, zoom, and cursor-specific large-data pressure
remains pending.
Expand Down
11 changes: 6 additions & 5 deletions benchmarks/conformance/DEFINITION-COVERAGE-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Date: 2026-08-10

Scope: all 188 catalog directories. The 67 cases previously classified as
Scope: all 189 catalog directories. The 67 cases previously classified as
strict custom authoring, preparation review, or shell-only are reviewed beside
the former 42-case definition-native control group from
[the custom authoring audit](./CUSTOM-AUTHORING-AUDIT.md). That audit remains
Expand Down Expand Up @@ -31,12 +31,12 @@ work:

| Disposition | Cases | Meaning |
| --------------------- | ------: | ------------------------------------------------------------------------ |
| Definition now | 135 | Current marks and eager transforms are sufficient |
| Definition now | 136 | Current marks and eager transforms are sufficient |
| First-party primitive | 35 | Add a reusable mark, transform, layout, guide, or controlled signal |
| Optional primitive | 15 | Keep a heavy dependency granular, but hide its layout DTOs and lifecycle |
| Application boundary | 2 | The remaining work is product state, DOM layout, or data arrival |
| Inline custom mark | 1 | The geometry is intentionally case-specific |
| **Total** | **188** | |
| **Total** | **189** | |

A normal definition does not require every implementation algorithm to live
in Charts core. D3 may implement an optional `sankeyDiagram`, `densityContour`,
Expand Down Expand Up @@ -322,6 +322,7 @@ ownership boundary.
| [195 — shadcn tooltip-label-custom](./cases/195-shadcn-tooltip-label-custom/example.tsx) | Definition now | The self-contained native TanStack Charts tooltip example reproduces the pinned shadcn variant and card presentation. |
| [196 — shadcn tooltip-label-formatter](./cases/196-shadcn-tooltip-label-formatter/example.tsx) | Definition now | The self-contained native TanStack Charts tooltip example reproduces the pinned shadcn variant and card presentation. |
| [197 — shadcn tooltip-label-none](./cases/197-shadcn-tooltip-label-none/example.tsx) | Definition now | The self-contained native TanStack Charts tooltip example reproduces the pinned shadcn variant and card presentation. |
| [198 — Gantt dependencies](./cases/198-gantt-dependencies/example.tsx) | Definition now | Native rect, dot, rule, and decorative link marks compose phase lanes, progress, a milestone, and the status date; a case-local d3 curve routes each link as a right-angle dependency arrow. |

## Reference evidence

Expand Down Expand Up @@ -353,8 +354,8 @@ bundle.

## Delivery result

All 188 catalog directories now have one roadmap record and case-local
evidence. All 185 normal-definition cases are verified against their current
All 189 catalog directories now have one roadmap record and case-local
evidence. All 186 normal-definition cases are verified against their current
boundary; only cases 85, 86, and 116 retain accepted application or bespoke
geometry work. The roadmap validator compares its IDs with the live catalog
directories so a new case cannot silently remain outside this review.
9 changes: 5 additions & 4 deletions benchmarks/conformance/DEFINITION-COVERAGE-OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

Date: 2026-08-10

The catalog has 188 case directories. 185 visualizations now use
The catalog has 189 case directories. 186 visualizations now use
normal chart definitions. Cases 85 and 86 retain application shells, and Case
116 retains one deliberately bespoke inline mark. No other case-owned layout
or renderer is an accepted endpoint.

| Disposition | Cases | Result |
| --------------------- | ------: | ------------------------------------------------- |
| Definition now | 135 | Normal definition |
| Definition now | 136 | Normal definition |
| First-party primitive | 35 | Normal definition |
| Optional primitive | 15 | Normal definition |
| Application boundary | 2 | Accepted boundary |
| Inline custom mark | 1 | Accepted boundary |
| **Total** | **188** | **185 normal definitions; 3 accepted boundaries** |
| **Total** | **189** | **186 normal definitions; 3 accepted boundaries** |

## Shared decisions

Expand Down Expand Up @@ -63,7 +63,7 @@ presentation policy.

## Verification

- The roadmap validator compares all 188 roadmap and audit IDs with the live
- The roadmap validator compares all 189 roadmap and audit IDs with the live
catalog directories, requires unique IDs, validates the capability DAG, and
requires every verified or accepted case to cite evidence under its own
directory.
Expand Down Expand Up @@ -260,6 +260,7 @@ presentation policy.
| [195-shadcn-tooltip-label-custom — Tooltip - Custom label](./cases/195-shadcn-tooltip-label-custom/example.tsx) | Definition now | `current-definition-api` | `example.tsx`, `case.json` |
| [196-shadcn-tooltip-label-formatter — Tooltip - Label Formatter](./cases/196-shadcn-tooltip-label-formatter/example.tsx) | Definition now | `current-definition-api` | `example.tsx`, `case.json` |
| [197-shadcn-tooltip-label-none — Tooltip - No Label](./cases/197-shadcn-tooltip-label-none/example.tsx) | Definition now | `current-definition-api` | `example.tsx`, `case.json` |
| [198-gantt-dependencies — Project Gantt with nested phases and dependencies](./cases/198-gantt-dependencies/example.tsx) | Definition now | `current-definition-api` | `example.tsx`, `route.ts`, `tanstack.test.ts` |
| [bar-grouped — Grouped bars](./cases/bar-grouped/example.tsx) | Definition now | `current-definition-api` | `example.tsx` |
| [bar-horizontal-ranking — Horizontal ranking with long labels](./cases/bar-horizontal-ranking/example.tsx) | Definition now | `current-definition-api` | `example.tsx` |
| [bar-stacked — Stacked bars](./cases/bar-stacked/example.tsx) | Definition now | `current-definition-api` | `example.tsx` |
Expand Down
40 changes: 40 additions & 0 deletions benchmarks/conformance/cases/198-gantt-dependencies/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"schemaVersion": 1,
"height": 520,
"order": 1990,
"id": "198-gantt-dependencies",
"title": "Project Gantt with nested phases and dependencies",
"family": "interval",
"intent": "Show a small project plan as phase header lanes with indented task lanes, progress fills, a milestone, a status-date rule, and right-angle finish-to-start dependency arrows, with every task reachable by keyboard.",
"support": "composed",
"features": [
"gantt chart",
"band lanes over a UTC time scale",
"phase summary lanes with indented child lanes",
"progress fill over each task",
"milestone marker",
"status-date reference rule",
"orthogonal finish-to-start dependency arrows",
"decorative connectors outside keyboard focus",
"keyboard task descriptions"
],
"geometry": [
{
"role": "rect",
"count": 24,
"maxCount": 24,
"rendererRoles": { "observable-plot": "bar" }
},
{ "role": "link", "count": 13, "maxCount": 13 },
{ "role": "dot", "count": 1, "maxCount": 1 },
{ "role": "rule", "count": 1 }
],
"source": {
"title": "Highcharts Gantt dependencies between tasks",
"url": "https://www.highcharts.com/docs/gantt/gantt-task-dependencies"
},
"ai": {
"create": "Create a responsive Gantt chart from typed project tasks with phase, dates, progress, and finish-to-start predecessors: phase header lanes followed by indented task lanes on a band scale, UTC time, progress fills, a milestone, a status-date rule, and right-angle dependency arrows that stay out of keyboard focus.",
"maintain": "Add a task and a predecessor to one phase without changing lane order rules, breaking the phase roll-up, adding focus stops for connectors, or routing any dependency segment diagonally."
}
}
137 changes: 137 additions & 0 deletions benchmarks/conformance/cases/198-gantt-dependencies/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
export const planGroups = ['Plan', 'Build', 'Launch'] as const

export type PlanGroup = (typeof planGroups)[number]

export interface PlanTask {
id: string
label: string
group: PlanGroup
start: Date
/** Equal to `start` for a milestone. */
end: Date
/** Completed share of the scheduled duration, from 0 to 1. */
progress: number
/** Finish-to-start predecessors by task id. */
after: readonly string[]
}

const day = (value: string) => new Date(`${value}T00:00:00Z`)

const tasks = [
['research', 'User research', 'Plan', '2026-03-02', '2026-03-13', 1, []],
['audit', 'Content audit', 'Plan', '2026-03-04', '2026-03-18', 1, []],
[
'sitemap',
'Site map',
'Plan',
'2026-03-19',
'2026-03-27',
1,
['research', 'audit'],
],
[
'wireframes',
'Wireframes',
'Plan',
'2026-03-30',
'2026-04-10',
0.7,
['sitemap'],
],
[
'visual',
'Visual design',
'Build',
'2026-03-30',
'2026-04-17',
0.4,
['sitemap'],
],
['cms', 'CMS setup', 'Build', '2026-03-23', '2026-04-10', 0.5, ['audit']],
[
'frontend',
'Front end',
'Build',
'2026-04-20',
'2026-05-15',
0.1,
['wireframes', 'visual'],
],
[
'migration',
'Content migration',
'Build',
'2026-04-13',
'2026-05-08',
0.2,
['cms'],
],
[
'qa',
'QA and fixes',
'Launch',
'2026-05-18',
'2026-05-29',
0,
['frontend', 'migration'],
],
[
'accessibility',
'Accessibility review',
'Launch',
'2026-05-18',
'2026-05-26',
0,
['frontend'],
],
[
'launch',
'Go live',
'Launch',
'2026-06-01',
'2026-06-01',
0,
['qa', 'accessibility'],
],
] as const satisfies readonly (readonly [
string,
string,
PlanGroup,
string,
string,
number,
readonly string[],
])[]

/** Status dates for the initial plan and one revised weekly status update. */
export const statusDates = [day('2026-04-15'), day('2026-04-22')] as const

/**
* Returns the project plan as reported on one status date. The revision keeps
* every task identity and dependency, advances progress, and slips one task.
*/
export function planForRevision(revision: number): PlanTask[] {
const revised = revision % 2 === 1
return tasks.map(([id, label, group, start, end, progress, after]) => ({
id,
label,
group,
start: day(start),
end: revised && id === 'migration' ? day('2026-05-13') : day(end),
progress: revised ? revisedProgress(id, progress) : progress,
after,
}))
}

export function statusDateForRevision(revision: number): Date {
return statusDates[revision % 2 === 1 ? 1 : 0]
}

function revisedProgress(id: string, progress: number) {
if (id === 'wireframes') return 1
if (id === 'visual') return 0.75
if (id === 'cms') return 0.9
if (id === 'frontend') return 0.2
if (id === 'migration') return 0.3
return progress
}
Loading