Skip to main content

Harness docs

Maps of the library as the harness demos run it today. These pages sit next to the demos so a maintainer can open the Gantt and the explanation in one session. Start below with how to run the harness and what the smallest working Gantt looks like, then follow a page from the list at the bottom.

Run the harness​

The harness is the library's first consumer: plain TypeScript against src/api/index.ts, with no framework and no build step of its own. One command serves every page below, this one included.

Derived from package.json, vite.config.ts, harness/*.html, harness/*.ts.

pnpm install
pnpm dev # serves harness/ — open the printed URL
pnpm build # production build of the same pages into dist-harness/
PageWhat it showsRead alongside
index.htmlThe general demo: timeline, viewport, selection, snap, and theme. Driven by harness/main.ts.Lifecycle
hierarchy.htmlTree and grouped row sources, filter and sort, collapse, declared-field rollup, and JSON round-trip. Driven by harness/e2e/hierarchy.ts.Class map
scroll-sync.htmlOne pair of Gantts sharing a TimeScaleModel and both ScrollAxis instances, beside a pair sharing only the x axis and a pair sharing only the y axis.Class map
grid-scroll.htmlThe grid pane as its own vertical scroll surface, kept in step with the timeline rows.Lifecycle
zoom.htmlPresets, zoom in/out, pan to a date, pan to today, and the header bands.Timeline render
large-dataset.htmlRow virtualization under a large entry count.Lifecycle
data.htmlMutation, live binding, undo/redo, and JSON export/import.Class map
editing.htmlDirect manipulation: drag to move, resize, keyboard editing, snapping, inline cell edit, and the lock Dataset plugin.Lifecycle
plugins.htmlThe plugin runtime: the shipped timeShading() built-in, a milestone diamond() variant, the bufferKind/riskKind harness variants, a cell renderer, commands, and a popup demo. Driven by harness/e2e/plugins.ts.Plugin lifecycle, plugin authoring guide

:::note The harness is reviewed like library code It sits outside the src/** lint scope on purpose, so a rule it breaks fires no lint. Code here that re-derives what the library already computes is an API gap — record it and close it in src/ rather than tidying the harness. See Class map for the current reading of harness/main.ts. :::

Basic usage​

What an app author writes. Two classes carry the whole surface: a Dataset owns the entries and every edit to them; a Gantt mounts one view of that dataset. This is a summary — README.md is the full and current consumer API.

Derived from README.md, docs/05-consumer-api.md, src/api/index.ts, harness/main.ts.

The smallest Gantt​

import { Gantt, Dataset } from 'freegantt';

const dataset = new Dataset({
timeZone: 'America/Chicago', // IANA zone — every date below is read through it
entries: [
{ id: 't1', name: 'Design', start: '2026-09-01', end: '2026-09-07' },
{ id: 't2', name: 'Build', start: '2026-09-08', end: '2026-09-21' },
],
});

const gantt = new Gantt({ container: '#gantt', dataset });
gantt.destroy(); // tears the view down; the dataset outlives it

Ids are plain strings and dates are plain strings, a Date, epoch milliseconds, or an Instant. Nothing has to be constructed first. container takes an HTMLElement or a CSS selector.

timeZone is optional. Omit it and the Dataset resolves the environment's own zone once, at construction, then stores that IANA string:

const dataset = new Dataset({ entries }); // no timeZone
dataset.timeZone; // → 'America/Chicago' — resolved, concrete, never a sentinel

Omission reads the dates in this viewer's calendar, so the same entries can render differently for a viewer in another zone. Pass timeZone whenever more than one person opens the dataset.

Editing​

Every mutator auto-wraps in its own transaction, and a bound Gantt renders the result on the next frame — there is no second render path and no remount.

dataset.entries.add({ id: 't9', name: 'Roofing', start: '2026-10-01', end: '2026-10-15' });
dataset.entries.update('t2', { name: 'Framing — north wing' });
dataset.entries.remove('t9'); // and every descendant, in the same changeset

dataset.transaction(() => {
dataset.entries.update('t1', { start: '2026-10-05' });
dataset.entries.update('t2', { start: '2026-10-12' });
}); // one changeset, one render

dataset.undo(); // what it did arrives on 'change', tagged origin: 'undo'
dataset.redo();

Panes, fields, columns​

A Gantt is two panes on one set of rows. The Grid pane is on the left. The Timeline pane is on the right. The time axis is not a column. The whole view is a Gantt — not a chart.

A field is what a value is, and it is declared on the dataset. A grid column is one vertical slice of the Grid pane: it names a field and carries presentation, and it is declared on the Gantt. Declaring a field does not put it on screen. The Timeline pane paints Bars; a Bar is paint, not identity, and Item is its retired name.

const dataset = new Dataset({
timeZone: 'UTC',
entries,
fieldTypes: { money: { rollUp: 'sum', formatValue: asCurrency, column: { align: 'end' } } },
fields: [{ key: 'cost', type: 'money' }],
});

gantt.gridColumns = ['name', 'start', 'duration', { field: 'cost', header: 'Budget' }];
gantt.rowSource = { source: 'entries', tree: true }; // parentId as a tree
gantt.rowSource = { source: 'group', groupBy: (e) => String(e.read('team') ?? '') }; // one header row per value
gantt.collapse('p1'); // per-Gantt view state, never in the dataset

Every config key is a live property: assign gantt.preset, gantt.gridColumns, gantt.rowSource, gantt.theme and the rest without remounting. Pass plugins: [tooltips(), contextMenu(), inlineEditing()] on the Gantt, or call gantt.installPlugin later. Two Gantts on one page stay independent; give them the same scale or scroll and they stay in sync.

Events​

dataset.on('change', ({ changeSet }) => save(changeSet)); // data events on the Dataset
gantt.on('selectionChange', () => render(gantt.selectedEntries)); // view events on the Gantt
gantt.on('beforeEntryMove', () => false); // every mutating interaction has a cancelable before* pair

:::note Where the full surface lives README.md documents the public API, docs/05-consumer-api.md is the index, and CONTEXT.md is the glossary. The pages below describe the inside of the library and are not a consumer reference. :::

The pages here​

Maintainer maps of src/, in reading order. Each page names the files it was derived from, so a change to a file says which page to update.

  • Layers & import rules — the ten directories under src/, which of them may import which, and the custom lint rules that hold the line.
  • File inventory — every non-test file in src/, with the one thing it is for.
  • Construction, render, notification — what new Gantt(…) builds, what one render() does, and how an edit reaches the screen, with measured pass counts.
  • How a refusal reaches the caller — a beforeChange veto from the mutator to the Error report and the thrown MutationCancelledError, plus the gesture and cell-editor doors.
  • Class map — every class in the built layers — layout/, view/, time/, model/, render/, api/, extensions/ — plus the consumer boundary and the harness reading.
  • Timeline render — how the header, density floor, sticky tick labels, and today line paint. Includes a pipeline graph and a density chart of the shipped presets.
  • Plugin lifecycle — what installPlugin and uninstallPlugin do, when the registration gate is open, how install() diffs a plugin list by id, and how a Dataset plugin's setup order resolves from requires.
  • Module map diagrams — the layer graph as a picture: the DOM-free core on the left, the DOM column on the right, the transaction pipeline, and the public API surface.
  • Maintaining these pages — what to update when a file changes, the rules for the content, and the recipe that re-measures the construction pass count. Read this before editing any page here.
  • API reference — the committed API Extractor report (pnpm api-report), taken from src/api/index.ts's built types. The gate fails on any drift, so it is the exact public surface — not a maintainer map like the pages above. The website's browsable /api section renders the same surface, generated by TypeDoc at site build.
  • Consumer API guide — outside this folder: links to the README, CONTEXT.md, and the generated export report.
  • Plugin authoring guide — outside this folder: definePlugin, the one plugin's two halves, every registration seam, and the errors an author meets.