@ganttloom/gantt-core
v0.6.2
Published
Framework-agnostic, fully customizable Gantt chart engine: grouping, drag-drop, dependency linking, expand/collapse, theming.
Maintainers
Readme
@ganttloom/gantt-core
Framework-agnostic Gantt chart engine: layout, SVG rendering, drag/resize/link
interactions, grouping, critical path, undo/redo, and more — zero runtime
dependencies. If you're using React or Vue, prefer
@ganttloom/gantt-react or
@ganttloom/gantt-vue; use this package directly if you're
integrating with something else (Svelte, vanilla JS) or want full control
over the DOM lifecycle.

Install
npm install @ganttloom/gantt-coreQuick start
import { GanttChart } from "@ganttloom/gantt-core";
import "@ganttloom/gantt-core/styles.css"; // or call injectStyles() at runtime
const container = document.getElementById("gantt")!;
const chart = new GanttChart(
container,
[
{ id: "1", name: "Design", start: new Date("2026-01-05"), end: new Date("2026-01-09"), progress: 60 },
{ id: "2", name: "Build", start: new Date("2026-01-09"), end: new Date("2026-01-16"), progress: 10 },
],
[{ fromId: "1", toId: "2", type: "FS" }],
{ viewMode: "day", showCriticalPath: true, enableHistory: true }
);
chart.on("date-change", ({ task, start, end }) => {
console.log(`${task.name} moved to ${start} - ${end}`);
});Tasks and dependencies are plain objects — the chart never owns your state.
Call chart.setTasks(...) / chart.setDependencies(...) whenever your data
changes; call chart.destroy() when you're done with it.
Core concepts
GanttTask—id,name,start/end(Date), optionalprogress,parentId(for grouping),isGroup,isMilestone,collapsed,color/progressColor,readonly,baselineStart/baselineEnd(plusbaselinesfor multiple named snapshots),deadline,constraintType/constraintDate,assignees,segments(split/multi-bar rendering),notes, and a free-formdatabag.GanttDependency—fromId,toId, atypeof"FS" | "SS" | "FF" | "SF", and an optionallagMs.GanttColumn— describes one column of the left-hand grid panel, with an optionalaccessorto derive its cell value from a task,getHrefto render it as a link,renderfor full custom cell content,align, andsortable.GanttOptions— everything else: view mode, theme, readonly, which optional features are on, and the event callbacks. Seesrc/types.tsfor the full, always-current list — it's the source of truth.
Feature options (all opt-in)
| Option | What it does |
|---|---|
| showCriticalPath | Highlights the critical path (longest dependency chain) |
| showBaseline | Renders baselineStart/baselineEnd as a faint bar behind the live one |
| showDeadlines (default true) | Renders task.deadline as a flag marker; red when the task's end is past it |
| showAssigneeAvatars | Renders task.assignees as small avatars/initials at the bar's left edge |
| enableHistory | Turns on chart.undo() / chart.redo() for move/resize/link/progress/create actions |
| keyboardAccessible | ARIA roles/labels on bars + arrow-key movement |
| virtualScroll (default true) | Only renders DOM/SVG nodes for rows in the visible viewport |
| pagination | { pageSize, page } — render only a page of top-level rows |
| autoSchedule | Moving/resizing a task cascades date shifts to dependent successors |
| calendar | A WorkingCalendar (working days + holidays) — shades non-working time and, with autoSchedule, skips it when cascading |
| snapToUnit | Round drag/resize deltas to "hour" \| "day" \| "week", or false for raw pixels |
| colorScheme | "light" \| "dark" \| "auto" (default "auto", follows the OS preference live) |
| headerPosition | "top" \| "bottom" \| "none" (default "top") — positions the grid and timeline headers |
Working-time calendars
import type { WorkingCalendar } from "@ganttloom/gantt-core";
const calendar: WorkingCalendar = {
workingDays: [false, true, true, true, true, true, false], // Sun..Sat; this is also the default
holidays: ["2026-01-19", "2026-02-16"], // ISO "YYYY-MM-DD"
};
new GanttChart(container, tasks, deps, { calendar, autoSchedule: true });Non-working days are shaded in the timeline. With autoSchedule on, a
successor task pushed by a dependency cascade is nudged forward to the next
working day. Standalone helpers (isWorkingDay, isHoliday, nextWorkingDay,
previousWorkingDay, shiftToWorkingDay) are also exported if you need the
same logic outside the chart.
Task constraints
MSP-style constraints (constraintType + constraintDate on a GanttTask)
are enforced during autoSchedule cascades — when a dependency pushes a
constrained successor, its constraint is applied on top of the cascade
before the new dates are committed:
{ id: "3", name: "Vendor delivery", start, end, constraintType: "mso", constraintDate: new Date("2026-02-01") }ConstraintType is one of "asap" | "alap" | "snet" | "snlt" | "fnet" | "fnlt" | "mso" | "mfo"
(as-soon-as-possible / as-late-as-possible / start-no-earlier/later-than /
finish-no-earlier/later-than / must-start/finish-on). "asap"/"alap" are
no-ops — a true as-late-as-possible schedule needs a backward pass from the
project end date, which this incremental, per-drag engine doesn't attempt.
The standalone applyConstraint(range, type, date) function is exported if
you want the same logic elsewhere. Constraints only take effect during a
cascade; they don't restrict a direct manual drag of the constrained task
itself (matching how MSP flags rather than blocks a violated constraint).
Drag-to-create tasks
Opt in by supplying onTaskCreate: dragging on an empty part of any row's
timeline then creates a new sibling task (same parentId as that row) with
the dragged date range, inserted right after that row.
new GanttChart(container, tasks, deps, {
onTaskCreate: (task) => console.log("created", task),
});Without onTaskCreate, drag-to-create is off — dragging empty timeline
space does nothing, so existing consumers see no behavior change.
Resource histogram
import { computeResourceHistogram, renderResourceHistogramSVG } from "@ganttloom/gantt-core";
const buckets = computeResourceHistogram(tasks, { capacityHoursPerDay: 8, calendar });
// [{ assigneeId, assigneeName, date, allocatedHours, capacityHours, overallocated }, ...]
document.getElementById("histogram")!.innerHTML = renderResourceHistogramSVG(buckets);Each assignee is treated as fully allocated (one day's capacity) on every
working day a task they're on spans — there's no per-task effort/FTE field
in GanttTask, so that's the only allocation model the plain task data can
support. overallocated is true once a bucket's summed hours exceed
capacityHours (default 8/day).
WBS numbering
import { computeWBSCodes } from "@ganttloom/gantt-core";
const wbs = computeWBSCodes(tasks); // Map<taskId, "1" | "1.1" | "1.2" | "2" | ...>
const columns = [{ id: "wbs", title: "WBS", accessor: (t) => wbs.get(t.id) }];Codes are computed fresh from the current tasks array (recompute after any
reorder/renest) rather than stored on the task, keeping with "everything is
a plain object."
Timeline markers
new GanttChart(container, tasks, deps, {
markers: [{ date: new Date("2026-02-01"), label: "Sprint boundary" }],
});Arbitrary labeled vertical lines on the timeline, independent of any task — for release dates, sprint boundaries, or any other project-wide event.
Grid column resize/reorder
Both off by default; supply the corresponding handler to opt in (same
construction-time-only wiring caveat as onTaskCreate):
new GanttChart(container, tasks, deps, {
columns,
onColumnResize: (columnId, width) => { /* persist the new width */ },
onColumnReorder: (order) => { /* persist the new column id order */ },
});Drag a column header's right edge to resize it; drag a header itself to
reorder columns. The chart applies the change to its own internal copy of
columns immediately (so the drag feels live) and calls back so you can
persist it — same pattern as a task drag and onDateChange.
Grid column sorting
Mark a column sortable: true and clicking its header cycles ascending →
descending → none, with a ▲/▼ indicator:
new GanttChart(container, tasks, deps, {
columns: [
{ id: "name", title: "Task", sortable: true },
{ id: "assignee", title: "Assignee", accessor: (t) => t.assignees?.[0]?.name, sortable: true },
],
onSortChange: (sort) => console.log(sort), // { columnId, direction } | null
});
chart.setSort("assignee", "desc"); // or null to clear
chart.getSort(); // { columnId: "assignee", direction: "desc" } | nullSorting reorders siblings at every tree level by the column's value (via
accessor, or task.name for the conventional unaccessored "name" column)
— the group/parent hierarchy itself is never reshuffled, only the order
within it. A column with neither accessor nor the id "name" has nothing
sortable to read and is a no-op if marked sortable.
Header position
new GanttChart(container, tasks, deps, { headerPosition: "bottom" });headerPosition positions the grid and timeline headers: "top" (the
default), "bottom", or "none" to hide them entirely. Can also be changed
after construction via chart.setOptions({ headerPosition: "none" }).
Grid column width
A GanttColumn with no explicit width grows to fill whatever space is
left in the grid panel after its sibling columns' widths are subtracted —
like a CSS minmax(0, 1fr) track (flex: 1 1 0 under the hood) — instead
of shrinking to its own content size. With multiple width-less columns, the
leftover space is shared evenly between them. A column with an explicit
width is unaffected and never grows or shrinks beyond it:
const columns = [
{ id: "name", title: "Name" }, // grows to fill leftover space
{ id: "status", title: "Status", width: 80 }, // fixed at 80px
];Task search/filter
import { filterTasks } from "@ganttloom/gantt-core";
const { tasks: visible, matchedIds } = filterTasks(tasks, (t) =>
t.name.toLowerCase().includes(query.toLowerCase())
);
chart.setTasks(visible);Pure/compute-only — it returns the filtered subset (keeping a matched
task's ancestors and descendants by default, so the tree stays intact) and
the set of directly-matched ids, which you can use to dim ancestor/descendant
rows shown only for tree context. There's no special renderer wiring: feeding
chart.setTasks(visible) a smaller array hides the rest, the same as any
other plain-data update.
Scroll-to-today, group progress rollup, and custom cells
chart.scrollToToday(); // centers the viewport on today's date
new GanttChart(container, tasks, deps, {
autoRollupProgress: true, // a group with no explicit `progress` averages its children's, duration-weighted
});
const columns = [
{ id: "status", title: "Status", render: (task) => {
const el = document.createElement("span");
el.className = task.progress === 100 ? "badge-done" : "badge-pending";
el.textContent = task.progress === 100 ? "Done" : "Pending";
return el; // or return a plain string - never parsed as HTML, so it's XSS-safe by construction
} },
];Row reorder and dependency editing
Both off by default; supply the corresponding handler to opt in:
new GanttChart(container, tasks, deps, {
onTaskReorder: (draggedId, targetId, position) => {
// position: "before" | "after" (reorder as a sibling) | "inside" (reparent under target)
},
onDependencyDblClick: (dep) => {
// open your own "edit this link" UI, then call:
chart.updateDependency(dep.fromId, dep.toId, { type: "SS", lagMs: 3600_000 });
},
});Dragging a grid row onto another fires onTaskReorder; the chart applies
the reorder/reparent to its own internal task list immediately (live drag
feedback) the same way column resize does. Double-clicking a dependency
link fires onDependencyDblClick — the engine has no built-in "edit link"
popover (keeping with zero-dependency, minimal-UI scope), so build your own
and call chart.updateDependency(...) (undoable) to apply the result.
Double-clicking a task bar itself (not a link) fires the separate
onTaskDblClick(task) — also off by default until you supply a handler.
Filling the viewport at any zoom level
A short project at month/year zoom, or a chart with just a handful of
tasks, naturally renders narrow — the timeline's pixel width is purely a
function of the task date range and the current zoom, with no awareness of
how much horizontal space is actually available. Set
autoFitToViewport: true to stretch (never shrink) the timeline so it
fills the container instead of leaving empty space to the right:
new GanttChart(container, tasks, deps, { autoFitToViewport: true });Re-evaluated on view-mode/task changes and on container resize (via
ResizeObserver). It never applies if the content is already wider than
the container — that's what normal horizontal scrolling is for. Off by
default, so upgrading doesn't silently change an existing chart's pixel
layout for anyone who hasn't opted in.
Separately, and always on: a short project at a coarse zoom no longer renders as one or two lonely ticks. The visible date range is padded out to a sensible minimum column count per zoom level (year: 5, quarter: 4, month/week: 6, day: 7, hour: 8), so a 3-month project at year zoom still shows a real multi-year timeline instead of a cramped sliver. Wider ranges are unaffected — this only ever pads, never trims.
The timeline's date header also stays pinned to the top of the chart while you scroll a tall task list vertically (it still scrolls horizontally in sync with the bars), instead of scrolling out of view along with the rows.
Multi-select and bulk actions
new GanttChart(container, tasks, deps, {
selectable: true,
onSelectionChange: (taskIds) => console.log("selected:", taskIds),
});
// Click selects one; ctrl/cmd-click toggles; shift-click selects a range (by visible row order).
chart.selectTask("t1");
chart.selectTask("t2", { additive: true });
chart.bulkShiftDates(24 * 60 * 60 * 1000); // shift every selected task by 1 day, one undo step
chart.bulkDelete(); // remove every selected task (and touching dependencies), one undo stepSplit tasks
{ id: "qa", name: "QA pass", start, end, segments: [
{ start: day1, end: day2 },
{ start: day4, end: day5 }, // a gap, e.g. paused for an upstream fix
] }start/end stay the authoritative overall envelope (used for
dependencies, critical path, baseline, etc.); segments only changes how
the bar is drawn — as separate rects joined by a dashed connector.
Dragging the whole task shifts every segment together; resizing an edge
only adjusts the first (left) or last (right) segment's outer edge, leaving
any gap untouched.
Hover tooltips
Every bar shows a floating, CSS-customizable tooltip on hover (name, dates,
progress, assignees, and task.notes if set) — not the browser's native
title tooltip, so it's fully styleable:
.gantt-tooltip { background: #111827; border-radius: 8px; }
.gantt-tooltip-title { font-weight: 700; }For full control over markup, renderTooltip works exactly like
GanttColumn.render — return an HTMLElement to mount directly, a string
(rendered as text, never parsed as HTML), or null/undefined to suppress
the tooltip for that specific task:
new GanttChart(el, tasks, deps, {
renderTooltip: (task) => {
if (task.isMilestone) return null; // no tooltip on milestones
const div = document.createElement("div");
div.textContent = `${task.name} — ${task.progress ?? 0}%`;
return div;
},
});Set showTooltip: false to disable it entirely.
i18n / overridable labels
renderTooltip and GanttColumn.render cover markup, but a handful of built-in
strings (aria-labels, the default name for a newly-created task, the tooltip's
date/progress formatting) are plain English by default. Override any subset of
them via the labels option — every field is optional and additive:
new GanttChart(el, tasks, deps, {
labels: {
expand: "Développer",
collapse: "Réduire",
addChildTask: "Ajouter une sous-tâche",
newTaskName: "Nouvelle tâche",
formatDate: (d) => d.toLocaleDateString("fr-FR"),
formatProgress: (p) => `${Math.round(p)} % terminé`,
formatBarAriaLabel: (task, progress) =>
`${task.name}, du ${task.start.toLocaleDateString("fr-FR")} au ${task.end.toLocaleDateString("fr-FR")}, ${progress} % terminé`,
},
});labels is reactive via setOptions({ labels: {...} }), same as any other option.
Custom timeline header tick labels
labels.formatTickLabel overrides the day/week/month/... column headings
across the top of the timeline (distinct from labels.formatDate, which only
covers the tooltip and bar aria-label). It receives each tick's real Date
and the chart's current viewMode, so formatting can vary per zoom level:
new GanttChart(el, tasks, deps, {
viewMode: "week",
labels: {
formatTickLabel: (date, viewMode) =>
viewMode === "week"
? `W${Math.ceil(date.getDate() / 7)}`
: date.toLocaleDateString("fr-FR"),
},
});Leave it unset to keep the built-in per-view-mode label (weekday+day at
"day", month+year at "month", etc).
Custom sort comparator
GanttColumn.sortable sorts siblings by the column's accessor value by
default. Set sortFn on a column to fully control the comparison instead —
it receives the two raw tasks (not their accessor'd values); the chart still
applies asc/desc direction on top of whatever it returns:
const columns: GanttColumn[] = [
{
id: "priority",
title: "Priority",
accessor: (t) => t.data?.priority,
sortable: true,
// "high" < "medium" < "low" instead of alphabetical
sortFn: (a, b) => {
const rank = { high: 0, medium: 1, low: 2 } as const;
const ra = rank[a.data?.priority as keyof typeof rank] ?? 99;
const rb = rank[b.data?.priority as keyof typeof rank] ?? 99;
return ra - rb;
},
},
];A column that leaves sortFn unset keeps the default accessor-based sort.
Custom task-bar rendering
renderBar lets you draw custom SVG content (icons, patterns, extra shapes)
into a task bar without forking the renderer. It's called once per visible
bar, after the default fill/progress/milestone/segment shapes are drawn but
before the resize handles, link connector, and label are appended — append
to (or restyle) ctx.group in place rather than replacing it, so drag/resize
hit-testing (which depends on those handle/connector elements staying on top)
keeps working:
new GanttChart(el, tasks, deps, {
renderBar: ({ task, bar, group }) => {
if (!task.data?.flagged) return;
const icon = document.createElementNS("http://www.w3.org/2000/svg", "text");
icon.setAttribute("x", String(bar.width + 6));
icon.setAttribute("y", "-4");
icon.textContent = "⚑";
group.appendChild(icon);
},
});Leave renderBar unset for unchanged default rendering.
Task notes, auto-color, baseline history, and density presets
{ id: "t1", name: "Ship v2", start, end, notes: "Blocked on legal review" } // shown as an extra line in the default tooltipimport { applyColorByField } from "@ganttloom/gantt-core";
const colored = applyColorByField(tasks, (t) => t.assignees?.[0]?.id); // one color per first assignee
new GanttChart(container, colored, deps);{ id: "t1", name: "Ship v2", start, end, baselines: [
{ label: "Original plan", start: d1, end: d2 },
{ label: "As of last Friday", start: d3, end: d4 },
] } // rendered as multiple faint stacked bars, oldest furthest back; requires showBaseline: trueimport { DENSITY_PRESETS } from "@ganttloom/gantt-core";
new GanttChart(container, tasks, deps, { theme: { ...DENSITY_PRESETS.compact } });None of these need a core option - notes/baselines are plain GanttTask fields, applyColorByField/DENSITY_PRESETS are pure helpers/data you apply before constructing the chart.
Partial-day working hours
const calendar: WorkingCalendar = {
holidays: ["2026-01-19"],
workingHours: { start: 9, end: 17 }, // 9am-5pm
};
new GanttChart(container, tasks, deps, { calendar, autoSchedule: true, viewMode: "hour" });Narrows anchor points, not duration math: a 3-day task cascaded by
autoSchedule lands its new start within the working window (not at
11pm), but stays a 3-calendar-day task, not "3 working-window days." Hour
view shades individual non-working hours (not just whole non-working days).
isWithinWorkingHours/isWorkingTime/shiftToWorkingTime are exported if
you need the same logic elsewhere.
Resource leveling
const { tasks: leveled, shifted } = chart.getLeveledTasks();
// or standalone: levelResources(tasks, dependencies, { calendar })
chart.setTasks(leveled); // apply it
console.log(shifted); // [{ taskId, delayMs }, ...] - what moved and by how muchDelays (never pulls earlier) tasks so no assignee is double-booked on a
working day, using the same "fully allocated while active" model as
computeResourceHistogram - so a leveled schedule always shows zero
overallocated buckets afterward. Respects FS ("finish-to-start")
dependencies (never places a task before its FS predecessors finish);
SS/FF/SF aren't specifically enforced - a documented scope limit rather
than a full constraint solver.
Multi-page PNG export
const pages = await chart.toPNGDataURLs({ pageWidthPx: 1600, pageHeightPx: 1200 });
// [dataUrl1, dataUrl2, ...] in row-major tile order, each at full fidelityFor charts too large to comfortably rasterize as one image; omit the
options to get a single image at the chart's actual size (same as
toPNGDataURL()).
Keyboard navigation
With keyboardAccessible: true: Tab/Shift+Tab move between bars (native
browser focus order - every bar has tabindex="0"), arrow keys nudge a
focused bar by a day, Enter clicks it, Home/End scroll the timeline
to the start/end of the project's date range, and Ctrl/Cmd+A selects
every visible task when selectable is also on.
Zoom
chart.zoomIn(); // 1.25x by default, clamps at a 400px/unit ceiling
chart.zoomOut(1.5); // custom factor
chart.getZoomLevel(); // current columnWidth, in px/unit
chart.fitToViewport(containerEl.clientWidth); // zoom-to-fit: scales so the whole project fitsExport / import
chart.toSVGString()/chart.toPNGDataURL()— raster/vector snapshot of the current render.tasksToCSV(tasks)/tasksFromCSV(csv)— round-trip through CSV.- For PDF/PPTX, see
@ganttloom/gantt-export, which consumeschart.getRenderModel()directly.
Theming
Every visual token is a --gantt-* CSS custom property (see styles.css),
in addition to the JS theme option (Partial<GanttTheme>) — style via
either, or both. colorScheme: "auto" (the default) follows
prefers-color-scheme live.
To override a token via CSS, target .gantt-root itself (the element the
chart renders into) rather than an ancestor — the defaults are declared on
.gantt-root, and a same-element declaration always wins over one inherited
from a parent, regardless of specificity:
.gantt-root {
--gantt-bar-color: #16a34a;
--gantt-critical-color: #ea580c;
}Styling structural elements (buttons, cells, rows)
Beyond color tokens, every structural/interactive element the chart renders
has a stable, undocumented-nowhere-else class name and no inline styles
and no !important — so plain CSS targeting these classes has full
control, same as any element you'd render yourself:
| Class | What it is |
|---|---|
| .gantt-toggle | The expand/collapse button on a group row |
| .gantt-grid-header-cell | A column header cell |
| .gantt-grid-cell | A grid body cell |
| .gantt-grid-row | A grid body row (has a default :hover background) |
| .gantt-rename-input | The inline text input shown while renaming a task |
| .gantt-tooltip | The floating hover tooltip's container (see "Hover tooltips" above) |
| .gantt-tooltip-title / .gantt-tooltip-row / .gantt-tooltip-notes | Elements inside the default tooltip content — irrelevant if you use renderTooltip |
For example, to make the expand/collapse toggle look like a small round button instead of bare text:
.gantt-toggle {
border-radius: 999px;
padding: 2px 6px;
background: var(--gantt-grid-color, #e5e7eb);
}
.gantt-toggle:hover {
background: var(--gantt-bar-color, #3b82f6);
color: #fff;
}None of this is specific to the library's own chrome, either — anything
you render, like the playground demo's toolbar (apps/playground,
apps/vue-playground), is plain HTML/CSS in your own app code with no
library involvement at all, so it's exactly as customizable as any other
element on your page.
Styling by state (data-attributes)
Render state that would otherwise need a JS hook to detect is also mirrored as
data-* attributes on the relevant element, so plain CSS attribute selectors
can theme it directly — no renderTooltip/GanttColumn.render required:
| Element | Attribute | Values |
|---|---|---|
| .gantt-bar (the <g> for a task bar) | data-gantt-critical | "true" / "false" |
| | data-gantt-milestone | "true" / "false" |
| | data-gantt-selected | "true" / "false" |
| | data-gantt-overdue | "true" / "false" |
| | data-gantt-group-bar | present ("true") only on a group/summary row's bar |
| | data-gantt-readonly-bar | present ("true") only when the task is readonly |
| .gantt-grid-row (a grid body row) | data-gantt-group-row | "true" / "false" |
| | data-gantt-collapsed | "true" / "false" |
| | data-gantt-depth | tree depth as a string, e.g. "0", "1" |
/* Give critical-path bars a dashed outline instead of relying on --gantt-critical-color alone */
.gantt-bar[data-gantt-critical="true"] rect {
stroke-dasharray: 4 2;
}
/* Dim every selected bar's label */
.gantt-bar[data-gantt-selected="true"] text {
font-style: italic;
}Additional CSS variables
Beyond the GanttTheme color tokens, a few more structural choices are also
--gantt-* custom properties on .gantt-root (with the same override
pattern as above):
| Variable | Default | Controls |
|---|---|---|
| --gantt-radius | 8px | .gantt-root's corner radius |
| --gantt-shadow | a soft drop shadow | .gantt-root's box-shadow |
| --gantt-header-z-index | 2 | stacking order of the sticky grid/timeline header rows |
| --gantt-tooltip-bg / --gantt-tooltip-color | dark background / light text | the default hover tooltip's colors |
| --gantt-tooltip-radius / --gantt-tooltip-shadow / --gantt-tooltip-max-width / --gantt-tooltip-padding | 6px / soft shadow / 280px / 8px 10px | the default hover tooltip's box |
| --gantt-tooltip-z-index | 1000 | the default hover tooltip's stacking order |
API surface
The full, current type/function list is re-exported from
src/index.ts — treat it as the reference; this README
covers the concepts, not every signature.
