@jorgerdz/timeview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/cli/timeview.js +6710 -0
- package/dist/timeview.cjs +1 -0
- package/dist/timeview.js +5667 -0
- package/dist/tokens.css +67 -0
- package/dist/types/timeview/BandedTimeline.d.ts +11 -0
- package/dist/types/timeview/BandedTimeline.d.ts.map +1 -0
- package/dist/types/timeview/DensityHeatmap.d.ts +11 -0
- package/dist/types/timeview/DensityHeatmap.d.ts.map +1 -0
- package/dist/types/timeview/LaneCalendar.d.ts +11 -0
- package/dist/types/timeview/LaneCalendar.d.ts.map +1 -0
- package/dist/types/timeview/MetricTimeline.d.ts +8 -0
- package/dist/types/timeview/MetricTimeline.d.ts.map +1 -0
- package/dist/types/timeview/SpanMatrix.d.ts +8 -0
- package/dist/types/timeview/SpanMatrix.d.ts.map +1 -0
- package/dist/types/timeview/config.d.ts +22 -0
- package/dist/types/timeview/config.d.ts.map +1 -0
- package/dist/types/timeview/core/aggregate.d.ts +113 -0
- package/dist/types/timeview/core/aggregate.d.ts.map +1 -0
- package/dist/types/timeview/core/calendar.d.ts +27 -0
- package/dist/types/timeview/core/calendar.d.ts.map +1 -0
- package/dist/types/timeview/core/intervals.d.ts +8 -0
- package/dist/types/timeview/core/intervals.d.ts.map +1 -0
- package/dist/types/timeview/core/labels.d.ts +5 -0
- package/dist/types/timeview/core/labels.d.ts.map +1 -0
- package/dist/types/timeview/core/metric.d.ts +58 -0
- package/dist/types/timeview/core/metric.d.ts.map +1 -0
- package/dist/types/timeview/core/time.d.ts +22 -0
- package/dist/types/timeview/core/time.d.ts.map +1 -0
- package/dist/types/timeview/dashboard.d.ts +17 -0
- package/dist/types/timeview/dashboard.d.ts.map +1 -0
- package/dist/types/timeview/data.d.ts +21 -0
- package/dist/types/timeview/data.d.ts.map +1 -0
- package/dist/types/timeview/export.d.ts +14 -0
- package/dist/types/timeview/export.d.ts.map +1 -0
- package/dist/types/timeview/index.d.ts +28 -0
- package/dist/types/timeview/index.d.ts.map +1 -0
- package/dist/types/timeview/registry.d.ts +285 -0
- package/dist/types/timeview/registry.d.ts.map +1 -0
- package/dist/types/timeview/shared/Caption.d.ts +9 -0
- package/dist/types/timeview/shared/Caption.d.ts.map +1 -0
- package/dist/types/timeview/shared/EmptyState.d.ts +16 -0
- package/dist/types/timeview/shared/EmptyState.d.ts.map +1 -0
- package/dist/types/timeview/shared/Legend.d.ts +10 -0
- package/dist/types/timeview/shared/Legend.d.ts.map +1 -0
- package/dist/types/timeview/shared/Tooltip.d.ts +15 -0
- package/dist/types/timeview/shared/Tooltip.d.ts.map +1 -0
- package/dist/types/timeview/shared/useMeasuredWidth.d.ts +2 -0
- package/dist/types/timeview/shared/useMeasuredWidth.d.ts.map +1 -0
- package/dist/types/timeview/types.d.ts +158 -0
- package/dist/types/timeview/types.d.ts.map +1 -0
- package/docs/AGENT-USAGE.md +93 -0
- package/docs/COMPATIBILITY.md +134 -0
- package/docs/STUDIO.md +41 -0
- package/examples/README.md +21 -0
- package/examples/configs/bandedTimeline.json +31 -0
- package/examples/configs/densityHeatmap.json +33 -0
- package/examples/configs/laneCalendar.json +31 -0
- package/examples/configs/metricTimeline.json +51 -0
- package/examples/configs/spanMatrix.json +31 -0
- package/package.json +94 -0
- package/render.html +12 -0
- package/src/render.tsx +67 -0
- package/src/styles/tokens.css +67 -0
- package/src/timeview/BandedTimeline.tsx +620 -0
- package/src/timeview/DensityHeatmap.tsx +513 -0
- package/src/timeview/LaneCalendar.tsx +496 -0
- package/src/timeview/MetricTimeline.tsx +993 -0
- package/src/timeview/SpanMatrix.tsx +721 -0
- package/src/timeview/config.ts +399 -0
- package/src/timeview/core/aggregate.ts +317 -0
- package/src/timeview/core/calendar.ts +81 -0
- package/src/timeview/core/intervals.ts +52 -0
- package/src/timeview/core/labels.ts +19 -0
- package/src/timeview/core/metric.ts +263 -0
- package/src/timeview/core/time.ts +103 -0
- package/src/timeview/dashboard.ts +80 -0
- package/src/timeview/data.ts +242 -0
- package/src/timeview/export.ts +48 -0
- package/src/timeview/index.ts +106 -0
- package/src/timeview/registry.ts +207 -0
- package/src/timeview/shared/Caption.tsx +40 -0
- package/src/timeview/shared/EmptyState.tsx +90 -0
- package/src/timeview/shared/Legend.tsx +67 -0
- package/src/timeview/shared/Tooltip.tsx +59 -0
- package/src/timeview/shared/useMeasuredWidth.ts +21 -0
- package/src/timeview/types.ts +159 -0
- package/vite.config.ts +11 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Timeview Compatibility And Versioning
|
|
2
|
+
|
|
3
|
+
Timeview has three versioned surfaces that can change at different speeds:
|
|
4
|
+
|
|
5
|
+
- The npm package version in `package.json`.
|
|
6
|
+
- The dataset schema version, currently `timeview.dataset.v1`.
|
|
7
|
+
- The hosted/local config version, currently `TimeviewConfigV1` with `v: 1`.
|
|
8
|
+
|
|
9
|
+
Until the package is published, the source tree is still pre-1.0. Treat local changes as
|
|
10
|
+
eligible for cleanup, but keep saved configs and agent output compatible whenever practical.
|
|
11
|
+
After the first published package, Timeview should follow semver for the package and keep
|
|
12
|
+
schema/config migrations explicit.
|
|
13
|
+
|
|
14
|
+
## Stable Surfaces
|
|
15
|
+
|
|
16
|
+
These are compatibility commitments for the current V1 runtime:
|
|
17
|
+
|
|
18
|
+
- `TimeDataset.schemaVersion` remains `"timeview.dataset.v1"` for event, interval, label,
|
|
19
|
+
and optional metric-series datasets accepted by `validateTimeDataset`.
|
|
20
|
+
- `TimeviewConfigV1.v` remains `1` for URL hash configs, local render configs, and
|
|
21
|
+
dashboard panel configs shaped as `{ visualizer, dataset, spec, palette }`.
|
|
22
|
+
- Future stored short links must resolve to the same versioned config contract as URL hash
|
|
23
|
+
links. The storage id is transport metadata, not a replacement config shape.
|
|
24
|
+
- Visualizer IDs are stable string values: `bandedTimeline`, `laneCalendar`,
|
|
25
|
+
`densityHeatmap`, `metricTimeline`, and `spanMatrix`.
|
|
26
|
+
- A `spec.kind` value must match the selected visualizer. Missing supported fields are
|
|
27
|
+
normalized from registry defaults.
|
|
28
|
+
- Unknown top-level config fields are not part of the public contract. Callers should not
|
|
29
|
+
depend on them being preserved by normalization.
|
|
30
|
+
|
|
31
|
+
The public TypeScript exports in `src/timeview/index.ts` are the intended library surface.
|
|
32
|
+
Internal component helpers and demo files may change unless they are exported there.
|
|
33
|
+
|
|
34
|
+
## Non-Breaking Changes
|
|
35
|
+
|
|
36
|
+
The following changes can land without a dataset or config version bump:
|
|
37
|
+
|
|
38
|
+
- Adding a new optional field to `TimeDataset`, a visualizer spec, or a dashboard panel.
|
|
39
|
+
- Adding a new visualizer ID, as long as existing IDs keep their behavior.
|
|
40
|
+
- Adding a new registry metadata field for docs, studio controls, or agent selection.
|
|
41
|
+
- Expanding validation to accept a previously undocumented but compatible value.
|
|
42
|
+
- Improving rendering, responsive behavior, accessibility, or export determinism without
|
|
43
|
+
changing accepted input meaning.
|
|
44
|
+
- Adding package exports while keeping existing exports available.
|
|
45
|
+
|
|
46
|
+
Optional fields must have safe defaults in `normalizeViewSpec`, the registry, or the
|
|
47
|
+
renderer path before docs or examples depend on them.
|
|
48
|
+
|
|
49
|
+
## Breaking Changes
|
|
50
|
+
|
|
51
|
+
These changes require a versioned migration plan:
|
|
52
|
+
|
|
53
|
+
- Renaming or removing a required `TimeDataset` field.
|
|
54
|
+
- Changing `TimeDataset.schemaVersion` semantics while keeping the same string.
|
|
55
|
+
- Renaming or removing a visualizer ID.
|
|
56
|
+
- Changing the meaning of an existing `ViewSpec` field.
|
|
57
|
+
- Changing `TimeviewConfigV1.v` shape or requiring fields that older saved configs do not
|
|
58
|
+
have.
|
|
59
|
+
- Removing an exported component, type, helper, or CLI command after publication.
|
|
60
|
+
- Making deterministic exports depend on browser-local state unless that state is explicit
|
|
61
|
+
in the config.
|
|
62
|
+
|
|
63
|
+
Prefer additive schemas over breaking replacements. If a replacement is unavoidable, add a
|
|
64
|
+
new schema or config version and keep a decoder/migration path for old saved links and agent
|
|
65
|
+
artifacts.
|
|
66
|
+
|
|
67
|
+
## Version Bump Rules
|
|
68
|
+
|
|
69
|
+
Before the first published package:
|
|
70
|
+
|
|
71
|
+
- Patch changes can update docs, fixtures, rendering polish, and validation messages.
|
|
72
|
+
- Minor changes can add visualizers, optional fields, CLI capabilities, and export formats.
|
|
73
|
+
- Breaking cleanup is allowed, but update docs and examples in the same change.
|
|
74
|
+
|
|
75
|
+
After publication:
|
|
76
|
+
|
|
77
|
+
- Patch: bug fixes, docs, visual polish, validation-message improvements, and compatible
|
|
78
|
+
export fixes.
|
|
79
|
+
- Minor: additive public API, optional schema/spec fields, new visualizers, and new CLI
|
|
80
|
+
commands.
|
|
81
|
+
- Major: breaking public API, schema, config, visualizer ID, or deterministic export
|
|
82
|
+
behavior changes.
|
|
83
|
+
|
|
84
|
+
## Migration Requirements
|
|
85
|
+
|
|
86
|
+
When introducing `timeview.dataset.v2` or `TimeviewConfigV2`:
|
|
87
|
+
|
|
88
|
+
1. Keep the V1 decoder available until a documented removal release.
|
|
89
|
+
2. Add a migration helper that returns the newest supported shape.
|
|
90
|
+
3. Update `npm run timeview -- describe --json` so agents can discover supported versions.
|
|
91
|
+
4. Document changed fields with before/after JSON examples.
|
|
92
|
+
5. Keep old URL hash links readable if the local code can infer an equivalent V2 config.
|
|
93
|
+
|
|
94
|
+
Migrations should be deterministic and side-effect free. They should not depend on current
|
|
95
|
+
date, browser timezone, network state, or hosted storage.
|
|
96
|
+
|
|
97
|
+
## Agent And Export Compatibility
|
|
98
|
+
|
|
99
|
+
Agent-generated charts and saved exports should be reproducible:
|
|
100
|
+
|
|
101
|
+
- Use explicit dates for `today` fields, or use `null`/omission to disable live markers.
|
|
102
|
+
- Use `today: "auto"` only in interactive browser configs; the CLI freezes it during
|
|
103
|
+
export.
|
|
104
|
+
- Keep palette arrays in config. Palette order maps to `dataset.labels` by index.
|
|
105
|
+
- Include complete datasets in URL/config artifacts rather than relying on fixture names.
|
|
106
|
+
- Freeze navigable viewport state before static export when a visualizer supports pan/zoom.
|
|
107
|
+
|
|
108
|
+
The CLI `describe --json` output is the machine-readable capability guide for agents. Update
|
|
109
|
+
it with any new schema, config, visualizer, or export capability before documenting that
|
|
110
|
+
capability for agent workflows.
|
|
111
|
+
|
|
112
|
+
## Stored Link Compatibility
|
|
113
|
+
|
|
114
|
+
Short-link/backend persistence is future hosted work, documented in `docs/PERSISTENCE.md`.
|
|
115
|
+
When it lands, stored links should remain additive to the URL hash flow:
|
|
116
|
+
|
|
117
|
+
- Keep `#tv=<token>` links decodable without network access.
|
|
118
|
+
- Store normalized `TimeviewConfigV1` payloads, not fixture names or partial studio state.
|
|
119
|
+
- Revalidate fetched configs before rendering.
|
|
120
|
+
- Treat the opaque id and timestamps as persistence metadata outside the public chart
|
|
121
|
+
config.
|
|
122
|
+
- Require a migration plan before writing or serving a newer config version.
|
|
123
|
+
- Fall back to encoded hash links when the persistence service is unavailable.
|
|
124
|
+
|
|
125
|
+
## Release Checklist
|
|
126
|
+
|
|
127
|
+
Before tagging or publishing a release:
|
|
128
|
+
|
|
129
|
+
1. Run `npm run typecheck`.
|
|
130
|
+
2. Run `npm run build`.
|
|
131
|
+
3. Validate at least one config per registered visualizer with `npm run timeview -- validate`.
|
|
132
|
+
4. Render a deterministic PNG or HTML export for a representative config.
|
|
133
|
+
5. Update `README.md`, `docs/ROADMAP.md`, and any agent docs that mention changed surfaces.
|
|
134
|
+
6. Note whether the release is patch, minor, or major by the rules above.
|
package/docs/STUDIO.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Timeview Studio
|
|
2
|
+
|
|
3
|
+
The studio is the hosted/demo surface for Timeview. It is separate from the React package
|
|
4
|
+
API and the CLI contract, but it uses the same `TimeviewConfigV1` config shape.
|
|
5
|
+
|
|
6
|
+
## Local Development
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The dev page lets users inspect visualizers, edit dataset/spec JSON, copy configs, copy
|
|
14
|
+
React snippets, export PNG/HTML, and create shareable hash links.
|
|
15
|
+
|
|
16
|
+
## Production Build
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm run build
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The studio build is written to `dist/studio`. The library bundle, CLI bundle, CSS tokens,
|
|
23
|
+
and declaration files live at the top of `dist` and are the package artifacts.
|
|
24
|
+
|
|
25
|
+
## Deployment
|
|
26
|
+
|
|
27
|
+
Any static host can serve `dist/studio`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx vite preview --outDir dist/studio
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Recommended production settings:
|
|
34
|
+
|
|
35
|
+
- Serve `index.html` for unknown routes if route-based studio pages are added later.
|
|
36
|
+
- Keep encoded `#tv=<token>` hash links intact; the hash is read client-side.
|
|
37
|
+
- Do not require a backend for encoded hash links.
|
|
38
|
+
- If short-link persistence is added later, store normalized `TimeviewConfigV1` payloads
|
|
39
|
+
and keep encoded hash links as the offline fallback.
|
|
40
|
+
|
|
41
|
+
See `docs/PERSISTENCE.md` for the future short-link storage contract.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Timeview Examples
|
|
2
|
+
|
|
3
|
+
`examples/configs` contains one complete `TimeviewConfigV1` file per registered visualizer.
|
|
4
|
+
Each file is intentionally self-contained: it includes the dataset, matching view spec, and
|
|
5
|
+
palette needed by the React components, CLI renderer, hosted links, or agents.
|
|
6
|
+
|
|
7
|
+
Validate an example:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm run timeview -- validate examples/configs/bandedTimeline.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Render an example:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run timeview -- render examples/configs/metricTimeline.json \
|
|
17
|
+
--format html \
|
|
18
|
+
--preset 1600x900 \
|
|
19
|
+
--out .context/metricTimeline.html \
|
|
20
|
+
--today 2026-10-29
|
|
21
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"visualizer": "bandedTimeline",
|
|
4
|
+
"dataset": {
|
|
5
|
+
"schemaVersion": "timeview.dataset.v1",
|
|
6
|
+
"timezone": "UTC",
|
|
7
|
+
"meta": { "title": "Launch Plan" },
|
|
8
|
+
"labels": [
|
|
9
|
+
{ "id": "design", "name": "Design" },
|
|
10
|
+
{ "id": "build", "name": "Build" },
|
|
11
|
+
{ "id": "launch", "name": "Launch" }
|
|
12
|
+
],
|
|
13
|
+
"events": [
|
|
14
|
+
{ "id": "kickoff", "title": "Kickoff", "at": "2026-06-01", "labelIds": ["design"] },
|
|
15
|
+
{ "id": "ship", "title": "Ship", "at": "2026-06-28", "labelIds": ["launch"] }
|
|
16
|
+
],
|
|
17
|
+
"intervals": [
|
|
18
|
+
{ "id": "research", "title": "Research", "range": { "start": "2026-06-01", "end": "2026-06-08" }, "labelIds": ["design"] },
|
|
19
|
+
{ "id": "implementation", "title": "Implementation", "range": { "start": "2026-06-06", "end": "2026-06-22" }, "labelIds": ["build"] },
|
|
20
|
+
{ "id": "release", "title": "Release prep", "range": { "start": "2026-06-20", "end": "2026-06-28" }, "labelIds": ["launch"] }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"spec": {
|
|
24
|
+
"kind": "bandedTimeline",
|
|
25
|
+
"title": "Launch Plan",
|
|
26
|
+
"overlapMode": "lanes",
|
|
27
|
+
"legend": { "position": "bottom" },
|
|
28
|
+
"events": { "showLabels": true }
|
|
29
|
+
},
|
|
30
|
+
"palette": ["#2563eb", "#16a34a", "#dc2626"]
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"visualizer": "densityHeatmap",
|
|
4
|
+
"dataset": {
|
|
5
|
+
"schemaVersion": "timeview.dataset.v1",
|
|
6
|
+
"timezone": "UTC",
|
|
7
|
+
"meta": { "title": "Support Load" },
|
|
8
|
+
"labels": [
|
|
9
|
+
{ "id": "billing", "name": "Billing" },
|
|
10
|
+
{ "id": "product", "name": "Product" },
|
|
11
|
+
{ "id": "incident", "name": "Incident" }
|
|
12
|
+
],
|
|
13
|
+
"events": [
|
|
14
|
+
{ "id": "incident-1", "title": "Incident opened", "at": "2026-08-04", "labelIds": ["incident"] },
|
|
15
|
+
{ "id": "billing-1", "title": "Billing spike", "at": "2026-08-06", "labelIds": ["billing"] }
|
|
16
|
+
],
|
|
17
|
+
"intervals": [
|
|
18
|
+
{ "id": "billing-window", "title": "Billing queue", "range": { "start": "2026-08-01", "end": "2026-08-05" }, "labelIds": ["billing"] },
|
|
19
|
+
{ "id": "product-window", "title": "Product queue", "range": { "start": "2026-08-03", "end": "2026-08-09" }, "labelIds": ["product"] },
|
|
20
|
+
{ "id": "incident-window", "title": "Incident response", "range": { "start": "2026-08-04", "end": "2026-08-07" }, "labelIds": ["incident"] }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"spec": {
|
|
24
|
+
"kind": "densityHeatmap",
|
|
25
|
+
"title": "Support Load",
|
|
26
|
+
"bucket": "day",
|
|
27
|
+
"measure": "duration",
|
|
28
|
+
"groupBy": "category",
|
|
29
|
+
"scaleMode": "category",
|
|
30
|
+
"showValues": true
|
|
31
|
+
},
|
|
32
|
+
"palette": ["#2563eb", "#16a34a", "#dc2626"]
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"visualizer": "laneCalendar",
|
|
4
|
+
"dataset": {
|
|
5
|
+
"schemaVersion": "timeview.dataset.v1",
|
|
6
|
+
"timezone": "UTC",
|
|
7
|
+
"meta": { "title": "Editorial Calendar" },
|
|
8
|
+
"labels": [
|
|
9
|
+
{ "id": "draft", "name": "Draft" },
|
|
10
|
+
{ "id": "review", "name": "Review" },
|
|
11
|
+
{ "id": "publish", "name": "Publish" }
|
|
12
|
+
],
|
|
13
|
+
"events": [
|
|
14
|
+
{ "id": "newsletter", "title": "Newsletter", "at": "2026-07-10", "labelIds": ["publish"] }
|
|
15
|
+
],
|
|
16
|
+
"intervals": [
|
|
17
|
+
{ "id": "article", "title": "Article draft", "range": { "start": "2026-07-01", "end": "2026-07-04" }, "labelIds": ["draft"] },
|
|
18
|
+
{ "id": "review-window", "title": "Review window", "range": { "start": "2026-07-06", "end": "2026-07-09" }, "labelIds": ["review"] },
|
|
19
|
+
{ "id": "launch-week", "title": "Launch week", "range": { "start": "2026-07-10", "end": "2026-07-13" }, "labelIds": ["publish"] }
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"spec": {
|
|
23
|
+
"kind": "laneCalendar",
|
|
24
|
+
"title": "Editorial Calendar",
|
|
25
|
+
"laneMode": "packed",
|
|
26
|
+
"today": "2026-07-08",
|
|
27
|
+
"legend": { "position": "bottom" },
|
|
28
|
+
"events": { "showLabels": true }
|
|
29
|
+
},
|
|
30
|
+
"palette": ["#2563eb", "#f59e0b", "#16a34a"]
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"visualizer": "metricTimeline",
|
|
4
|
+
"dataset": {
|
|
5
|
+
"schemaVersion": "timeview.dataset.v1",
|
|
6
|
+
"timezone": "UTC",
|
|
7
|
+
"meta": { "title": "Weekly Weight Trend" },
|
|
8
|
+
"labels": [
|
|
9
|
+
{ "id": "deficit", "name": "Deficit" },
|
|
10
|
+
{ "id": "maintenance", "name": "Maintenance" },
|
|
11
|
+
{ "id": "travel", "name": "Travel" }
|
|
12
|
+
],
|
|
13
|
+
"events": [
|
|
14
|
+
{ "id": "checkin", "title": "Coach check-in", "at": "2026-10-15", "labelIds": ["maintenance"] }
|
|
15
|
+
],
|
|
16
|
+
"intervals": [
|
|
17
|
+
{ "id": "deficit-1", "title": "Calorie deficit", "range": { "start": "2026-09-01", "end": "2026-10-01" }, "labelIds": ["deficit"] },
|
|
18
|
+
{ "id": "maintenance-1", "title": "Maintenance", "range": { "start": "2026-10-02", "end": "2026-10-20" }, "labelIds": ["maintenance"] },
|
|
19
|
+
{ "id": "travel-1", "title": "Travel", "range": { "start": "2026-10-21", "end": "2026-10-28" }, "labelIds": ["travel"] }
|
|
20
|
+
],
|
|
21
|
+
"series": {
|
|
22
|
+
"id": "weight",
|
|
23
|
+
"name": "Weight",
|
|
24
|
+
"unit": "lb",
|
|
25
|
+
"target": { "value": 182, "label": "Target" },
|
|
26
|
+
"samples": [
|
|
27
|
+
{ "at": "2026-09-01", "value": 190.2 },
|
|
28
|
+
{ "at": "2026-09-08", "value": 188.9 },
|
|
29
|
+
{ "at": "2026-09-15", "value": 187.1 },
|
|
30
|
+
{ "at": "2026-09-22", "value": 185.8 },
|
|
31
|
+
{ "at": "2026-10-01", "value": 184.7 },
|
|
32
|
+
{ "at": "2026-10-08", "value": 184.4 },
|
|
33
|
+
{ "at": "2026-10-15", "value": 183.9 },
|
|
34
|
+
{ "at": "2026-10-22", "value": 184.8 },
|
|
35
|
+
{ "at": "2026-10-29", "value": 184.1 }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"spec": {
|
|
40
|
+
"kind": "metricTimeline",
|
|
41
|
+
"title": "Weekly Weight Trend",
|
|
42
|
+
"stateMode": "band",
|
|
43
|
+
"yAxis": "auto",
|
|
44
|
+
"defaultDays": 90,
|
|
45
|
+
"today": "2026-10-29",
|
|
46
|
+
"showPoints": true,
|
|
47
|
+
"showTarget": true,
|
|
48
|
+
"minimap": true
|
|
49
|
+
},
|
|
50
|
+
"palette": ["#2563eb", "#16a34a", "#f59e0b"]
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"visualizer": "spanMatrix",
|
|
4
|
+
"dataset": {
|
|
5
|
+
"schemaVersion": "timeview.dataset.v1",
|
|
6
|
+
"timezone": "UTC",
|
|
7
|
+
"meta": { "title": "Coverage Matrix" },
|
|
8
|
+
"labels": [
|
|
9
|
+
{ "id": "coverage", "name": "Coverage" },
|
|
10
|
+
{ "id": "handoff", "name": "Handoff" },
|
|
11
|
+
{ "id": "risk", "name": "Risk" }
|
|
12
|
+
],
|
|
13
|
+
"events": [
|
|
14
|
+
{ "id": "review", "title": "Review", "at": "2026-09-12", "labelIds": ["risk"] }
|
|
15
|
+
],
|
|
16
|
+
"intervals": [
|
|
17
|
+
{ "id": "north", "title": "North team", "range": { "start": "2026-09-01", "end": "2026-09-10" }, "labelIds": ["coverage"] },
|
|
18
|
+
{ "id": "south", "title": "South team", "range": { "start": "2026-09-06", "end": "2026-09-16" }, "labelIds": ["coverage"] },
|
|
19
|
+
{ "id": "handoff-a", "title": "Handoff", "range": { "start": "2026-09-10", "end": "2026-09-13" }, "labelIds": ["handoff"] }
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"spec": {
|
|
23
|
+
"kind": "spanMatrix",
|
|
24
|
+
"title": "Coverage Matrix",
|
|
25
|
+
"bucket": "day",
|
|
26
|
+
"groupBy": "category",
|
|
27
|
+
"showCounts": true,
|
|
28
|
+
"today": "2026-09-12"
|
|
29
|
+
},
|
|
30
|
+
"palette": ["#2563eb", "#f59e0b", "#dc2626"]
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jorgerdz/timeview",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Timeview — a library of reusable, time-based visualization components.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Timeview contributors",
|
|
8
|
+
"homepage": "https://github.com/jorgerdz/timeview#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/jorgerdz/timeview.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/jorgerdz/timeview/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react",
|
|
18
|
+
"timeline",
|
|
19
|
+
"calendar",
|
|
20
|
+
"visualization",
|
|
21
|
+
"charts",
|
|
22
|
+
"time-series"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"*.css"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/cli",
|
|
29
|
+
"dist/timeview.cjs",
|
|
30
|
+
"dist/timeview.js",
|
|
31
|
+
"dist/tokens.css",
|
|
32
|
+
"dist/types",
|
|
33
|
+
"src/timeview",
|
|
34
|
+
"src/render.tsx",
|
|
35
|
+
"src/styles/tokens.css",
|
|
36
|
+
"docs/AGENT-USAGE.md",
|
|
37
|
+
"docs/COMPATIBILITY.md",
|
|
38
|
+
"docs/STUDIO.md",
|
|
39
|
+
"examples",
|
|
40
|
+
"render.html",
|
|
41
|
+
"vite.config.ts",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
],
|
|
46
|
+
"exports": {
|
|
47
|
+
".": {
|
|
48
|
+
"types": "./dist/types/timeview/index.d.ts",
|
|
49
|
+
"import": "./dist/timeview.js",
|
|
50
|
+
"require": "./dist/timeview.cjs"
|
|
51
|
+
},
|
|
52
|
+
"./tokens.css": "./dist/tokens.css",
|
|
53
|
+
"./package.json": "./package.json"
|
|
54
|
+
},
|
|
55
|
+
"main": "./dist/timeview.cjs",
|
|
56
|
+
"module": "./dist/timeview.js",
|
|
57
|
+
"types": "./dist/types/timeview/index.d.ts",
|
|
58
|
+
"bin": {
|
|
59
|
+
"timeview": "dist/cli/timeview.js"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"dev": "vite",
|
|
63
|
+
"timeview": "tsx scripts/timeview.ts",
|
|
64
|
+
"build": "npm run clean && npm run build:types && npm run build:lib && npm run build:cli && npm run build:assets && vite build",
|
|
65
|
+
"build:assets": "mkdir -p dist && cp src/styles/tokens.css dist/tokens.css",
|
|
66
|
+
"build:cli": "vite build --config vite.cli.config.ts && node scripts/postbuild-cli.js",
|
|
67
|
+
"build:lib": "vite build --config vite.lib.config.ts",
|
|
68
|
+
"build:types": "tsc -p tsconfig.lib.json",
|
|
69
|
+
"clean": "rm -rf dist node_modules/.tmp/tsconfig.lib.tsbuildinfo",
|
|
70
|
+
"test": "npm run typecheck && npm run test:config && npm run test:examples && npm run test:cli",
|
|
71
|
+
"test:cli": "tsx tests/cli.test.ts",
|
|
72
|
+
"test:config": "tsx tests/config.test.ts",
|
|
73
|
+
"test:examples": "tsx tests/examples.test.ts",
|
|
74
|
+
"smoke:render": "node dist/cli/timeview.js render examples/configs/bandedTimeline.json --format html --preset 1200x720 --out .context/render-smoke.html --json",
|
|
75
|
+
"preview": "vite preview",
|
|
76
|
+
"typecheck": "tsc -b --noEmit"
|
|
77
|
+
},
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"react": "^18.3.1",
|
|
80
|
+
"react-dom": "^18.3.1"
|
|
81
|
+
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"playwright": "^1.60.0",
|
|
84
|
+
"vite": "^8.0.16"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@types/node": "^25.9.2",
|
|
88
|
+
"@types/react": "^18.3.12",
|
|
89
|
+
"@types/react-dom": "^18.3.1",
|
|
90
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
91
|
+
"tsx": "^4.22.4",
|
|
92
|
+
"typescript": "^5.6.3"
|
|
93
|
+
}
|
|
94
|
+
}
|
package/render.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Timeview Render</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/render.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/src/render.tsx
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import {
|
|
4
|
+
normalizePalette,
|
|
5
|
+
normalizeTimeviewConfig,
|
|
6
|
+
TV_VISUALIZER_BY_ID,
|
|
7
|
+
type TimeviewConfigV1,
|
|
8
|
+
} from "./timeview";
|
|
9
|
+
import "./styles/tokens.css";
|
|
10
|
+
|
|
11
|
+
type RenderResult = { ok: true } | { ok: false; errors: string[] };
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
interface Window {
|
|
15
|
+
renderTimeview: (config: TimeviewConfigV1, width: number, height: number) => Promise<RenderResult>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rootElement = document.getElementById("root");
|
|
20
|
+
if (!rootElement) throw new Error("Missing #root.");
|
|
21
|
+
|
|
22
|
+
const root = ReactDOM.createRoot(rootElement);
|
|
23
|
+
|
|
24
|
+
function RenderFrame({ config, width, height }: { config: TimeviewConfigV1; width: number; height: number }) {
|
|
25
|
+
const visualizer = TV_VISUALIZER_BY_ID[config.visualizer];
|
|
26
|
+
const ActiveComponent = visualizer.Component;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<main
|
|
30
|
+
id="timeview-render-frame"
|
|
31
|
+
style={{
|
|
32
|
+
width,
|
|
33
|
+
minHeight: height,
|
|
34
|
+
margin: 0,
|
|
35
|
+
padding: 0,
|
|
36
|
+
background: "#ffffff",
|
|
37
|
+
color: "var(--tv-ink)",
|
|
38
|
+
fontFamily: "var(--tv-font)",
|
|
39
|
+
overflow: "hidden",
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<ActiveComponent dataset={config.dataset} spec={config.spec} palette={normalizePalette(config.palette)} />
|
|
43
|
+
</main>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function nextFrame(): Promise<void> {
|
|
48
|
+
return new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
document.body.style.margin = "0";
|
|
52
|
+
document.body.style.background = "#ffffff";
|
|
53
|
+
|
|
54
|
+
window.renderTimeview = async (rawConfig, width, height) => {
|
|
55
|
+
const normalized = normalizeTimeviewConfig(rawConfig);
|
|
56
|
+
if (!normalized.value) return { ok: false, errors: normalized.errors };
|
|
57
|
+
|
|
58
|
+
root.render(
|
|
59
|
+
<React.StrictMode>
|
|
60
|
+
<RenderFrame config={normalized.value} width={width} height={height} />
|
|
61
|
+
</React.StrictMode>,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
await document.fonts.ready;
|
|
65
|
+
await nextFrame();
|
|
66
|
+
return { ok: true };
|
|
67
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* ──────────────────────────────────────────────────────────────────
|
|
2
|
+
Timeview — design tokens
|
|
3
|
+
A fresh, restrained developer-tool / analytics visual language.
|
|
4
|
+
White plot canvas · cool-neutral slate ink · hairline gridlines ·
|
|
5
|
+
Geist + Geist Mono · indigo focus accent · categorical mark palette.
|
|
6
|
+
────────────────────────────────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap");
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
/* ── Ink (cool slate) ───────────────────────────────────────── */
|
|
12
|
+
--tv-ink: #15181e; /* primary text / titles */
|
|
13
|
+
--tv-ink-2: #3b414d; /* band titles, strong labels */
|
|
14
|
+
--tv-ink-3: #6b7280; /* axis labels, secondary */
|
|
15
|
+
--tv-ink-4: #9aa1ad; /* faint meta, muted ticks */
|
|
16
|
+
--tv-ink-5: #c2c7d0; /* very faint */
|
|
17
|
+
|
|
18
|
+
/* ── Surfaces ───────────────────────────────────────────────── */
|
|
19
|
+
--tv-canvas: #ffffff; /* the plot itself */
|
|
20
|
+
--tv-frame: #fbfcfd; /* component frame background */
|
|
21
|
+
--tv-rail: #f5f7f9; /* legend chips, quiet fills */
|
|
22
|
+
--tv-rail-2: #eef1f4; /* secondary-axis week strip */
|
|
23
|
+
|
|
24
|
+
/* ── Lines ──────────────────────────────────────────────────── */
|
|
25
|
+
--tv-line: #e7eaee; /* frame borders, lane separators */
|
|
26
|
+
--tv-grid: #eef1f5; /* day gridlines */
|
|
27
|
+
--tv-grid-week:#dde2e8; /* week boundary gridline (stronger) */
|
|
28
|
+
--tv-grid-today:#cfd5dd;
|
|
29
|
+
|
|
30
|
+
/* ── Chrome accent (selection / focus only — NOT a data color) ─ */
|
|
31
|
+
--tv-accent: #4f46e5; /* indigo */
|
|
32
|
+
--tv-accent-soft: rgba(79, 70, 229, 0.12);
|
|
33
|
+
--tv-focus-ring: 0 0 0 2px #fff, 0 0 0 4px rgba(79, 70, 229, 0.55);
|
|
34
|
+
|
|
35
|
+
/* ── Type ───────────────────────────────────────────────────── */
|
|
36
|
+
--tv-font: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
37
|
+
--tv-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
|
38
|
+
|
|
39
|
+
/* ── Radii ──────────────────────────────────────────────────── */
|
|
40
|
+
--tv-r-xs: 4px;
|
|
41
|
+
--tv-r-sm: 6px;
|
|
42
|
+
--tv-r-md: 8px;
|
|
43
|
+
--tv-r-lg: 12px;
|
|
44
|
+
--tv-r-pill: 9999px;
|
|
45
|
+
|
|
46
|
+
/* ── Shadows (tight, inspectable) ───────────────────────────── */
|
|
47
|
+
--tv-shadow-1: 0 1px 2px rgba(16, 24, 40, 0.04);
|
|
48
|
+
--tv-shadow-pop: 0 6px 20px -6px rgba(16, 24, 40, 0.18), 0 2px 6px rgba(16,24,40,0.06);
|
|
49
|
+
|
|
50
|
+
/* ── Motion ─────────────────────────────────────────────────── */
|
|
51
|
+
--tv-ease: cubic-bezier(0.2, 0.6, 0.2, 1);
|
|
52
|
+
--tv-fast: 0.14s;
|
|
53
|
+
--tv-mid: 0.22s;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── Stage (the gray studio backdrop, outside the component) ───── */
|
|
57
|
+
.tv-stage {
|
|
58
|
+
background:
|
|
59
|
+
radial-gradient(circle at 1px 1px, #d9dde3 1px, transparent 0) 0 0 / 22px 22px,
|
|
60
|
+
#eef0f3;
|
|
61
|
+
color: var(--tv-ink);
|
|
62
|
+
font-family: var(--tv-font);
|
|
63
|
+
-webkit-font-smoothing: antialiased;
|
|
64
|
+
-moz-osx-font-smoothing: grayscale;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
*, *::before, *::after { box-sizing: border-box; }
|