Skip to content

Add timeline component primitives#374

Merged
IzumiSy merged 16 commits into
mainfrom
feat/timeline-component
Jul 13, 2026
Merged

Add timeline component primitives#374
IzumiSy merged 16 commits into
mainfrom
feat/timeline-component

Conversation

@IzumiSy

@IzumiSy IzumiSy commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
スクリーンショット 2026-07-09 15 04 18

Motivation

AppShell needs a reusable timeline primitive for time-based layouts. This PR adds the first public Timeline component and a demo page that shows two concrete uses: a task dependency view and a job execution timeline.

This API is also meant to serve as a foundation for more specialized timeline-based experiences we expect to build next, including internal stack trace / execution-trace viewers and customer-facing planning UIs such as Gantt charts. Those use cases share the same core need — placing items against time, layering guides/labels/decorations, and expressing dependencies — but differ significantly in presentation and interaction. The goal of this PR is to establish that shared base now, so those specialized views can be built by composition instead of each introducing a separate timeline model.

Design Decision

Why introduce a primitive instead of adopting an existing library?

I also evaluated whether AppShell should adopt an existing free OSS timeline or Gantt library instead of introducing a primitive here, but the available options did not look like a good foundation for the core package. kibo-ui's Gantt depends on extra libraries such as jotai and lodash that AppShell does not otherwise need, gantt-task-react appears effectively unmaintained, and SVAR's Gantt Chart is high quality but both paywalls important features and is much more tightly specialized around a finished Gantt presentation than the lower-level building block I want here. Given those tradeoffs, I decided it was better to design a small Timeline primitive first and let more specialized experiences sit on top of it. That keeps the API more flexible and keeps implementation control inside AppShell.

Why make Timeline composable instead of shipping a finished chart?

The motivation here is not just “show a Gantt.” The near-term use cases already span at least two different presentations — task dependency planning and execution/job traces — and the expected follow-up use cases diverge even more in layout, density, interaction, and labeling. What they actually share is a smaller set of primitives: a time range, a scrollable viewport, layered guides/labels/decorations, positioned intervals, and dependency links.

Designing Timeline at that shared layer lets AppShell own the common geometry and composition model once, while leaving higher-level presentation decisions to specialized wrappers built on top. If the first API were a finished chart, later use cases would either have to contort themselves around chart-specific assumptions or introduce parallel timeline implementations anyway. The primitive-first design avoids that fork early.

How are responsibilities split inside the primitive?

Timeline.Root owns the shared time range, Timeline.Viewport owns scrolling, axis rendering, decoration layers, and link layering, and apps compose rows/items with Timeline.Row, Timeline.Interval, and Timeline.Link.

Axis configuration is split into guides and levels so boundary lines and labels can be controlled independently. Row sizing and row background rendering live on Timeline.Row, which keeps viewport layout smaller and leaves presentation details with the app.

Summary

  • add Timeline primitives and exported timeline types in packages/core
  • add tests and public JSDoc for the timeline surface
  • add a demo page showing task dependency and job execution examples
  • add Tooltip.Content noArrow support

@IzumiSy IzumiSy requested a review from a team as a code owner July 9, 2026 02:45
@IzumiSy IzumiSy marked this pull request as draft July 9, 2026 02:46
@IzumiSy IzumiSy changed the title Redesign timeline component API Add timeline component primitives Jul 9, 2026
@IzumiSy IzumiSy force-pushed the feat/timeline-component branch from 802bd5d to 24cf367 Compare July 9, 2026 02:50
@IzumiSy

IzumiSy commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Design Review completed successfully!

API Design Review complete for PR #374 (Add timeline component primitives). Posted 3 inline review comments. Verdict: Request Changes — 2 Medium issues found: (1) TimelineAxis.levels is required but should be optional, symmetric with optional guides; (2) sub-component props types (TimelineRowProps, TimelineIntervalProps, etc.) are not exported, violating AppShell convention. 1 Low issue: Timeline.Link silently renders nothing for unresolved IDs with no dev-mode warning. The Tooltip.Content noArrow addition and all other aspects of the Timeline API are well-designed. Note: add_comment tool was not available in this workflow, so the overall verdict summary was not posted as a PR comment — only inline review comments were posted.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by API Design Review for issue #374 · 122.9 AIC · ⌖ 9.67 AIC · ⊞ 5.8K
Comment /review to run again

Comment thread packages/core/src/components/timeline.tsx Outdated
Comment thread packages/core/src/index.ts
Comment thread packages/core/src/components/timeline.tsx
@IzumiSy IzumiSy marked this pull request as ready for review July 10, 2026 01:45

@victornguyen victornguyen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Very keen to make use of this in Workflow Viewer 🚀

I'm keen to get this merged so we can start playing with it and iterate through usage, but there were a couple of things my agent picked up that I thought were worth highlighting now:

1. Row labels need to be owned by the timeline

The demo hand-rolls the label column with paddingTop: 28 (a copy of the default axis level height) and height: 40 label cells (a copy of each row's height). That works until anything changes: add an axis level, and the labels drift 28px; a row resizes (which rows explicitly support — they're measured via ResizeObserver), and the external column has no way to know. Every trace/Gantt consumer needs labeled rows, so each one will rebuild this sync by hand.

The machinery already exists: rows register their measured top/height with the viewport for the row-background layer. A gutter is the same registration rendering labels. Minimal ask: label?: ReactNode on Timeline.Row + a gutter width on the viewport, rendered sticky; left: 0.

This was hard for me to conceptualise, so here's a Claude artifact that visually demos the failure modes:
https://claude.ai/code/artifact/4d8e34f9-9455-4561-ae6e-a55e60cb1316

2. Timeline.Interval needs a minimum pixel width

In an execution trace most bars are sub-second jobs inside runs spanning minutes: a 300ms job in a 10-min run is 0.05% ≈ 0.6px on the demo's own 1200px canvas — invisible, and an unusable click target. This is the common case for traces, not an edge case (Jaeger/Perfetto/DevTools all clamp).

Apps can't work around it: style is spread before the computed width, so overrides are discarded, and a min-width via className fights the built-in min-w-0. Only Interval can clamp correctly — and cheaply: width: max(${width}%, ${minWidth}px) via CSS max(), ideally also feeding the clamped rect into registerItem so link anchors match the drawn bar (bodySize.width is already measured).

Ask: minWidth?: number on Timeline.Interval, keeping the start edge time-true.

@IzumiSy

IzumiSy commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — this was a very helpful review.

On (1), I agree the demo currently duplicates layout values in a brittle way, so I should clean that up. That said, I’m hesitant to add Timeline.Row.label / viewport gutter API to the core primitive. In the current design, axis height is already owner-managed via levels[].height (28 is just the default), and row height is owner-managed via Timeline.Row height, so a companion column can keep those as shared values on the consumer side.

I’d like to keep Timeline neutral here, since some consumers may want simple labels while others may want a table or richer side pane. If we later find a real need to synchronize intrinsic/measured row geometry across panes, I’d rather add a more generic layout-sync primitive than bake label semantics into the core API.

On (2), I agree with the underlying concern: very short intervals still need to be visible and usable as click targets. I’m less convinced that Timeline.Interval.minWidth is the right primitive for that, though, because it makes the rendered bar less faithful to the x-axis scale. My preference would be to first treat that as a consumer-side presentation problem: choosing an x-axis scale / zoom level that gives short spans enough room when they need to be inspected, and otherwise letting the rendered interval content overflow the time-true box to provide a larger visible/clickable affordance. The current implementation already allows that latter pattern — Timeline.Interval itself does not clip overflow — and in the demo the main thing preventing it today is the bar implementation’s own overflow: hidden, not the primitive. That also seems closer to tracing UIs like Google Cloud Trace / Stackdriver Trace, where the timeline stays time-true while labels remain readable even for very short spans.

I’d prefer to try that route first, and only revisit a min-width API if real usage in Workflow Viewer shows scale choice + overflowed content still aren’t enough.

@victornguyen victornguyen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on both. Happy to road test it as-is in Workflow Viewer. LGTM 🚀

@IzumiSy IzumiSy merged commit 57a3870 into main Jul 13, 2026
4 checks passed
@IzumiSy IzumiSy deleted the feat/timeline-component branch July 13, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants