Skip to main content

Layers & import rules

Which directories may import which, and the lint rules that hold the line. The structure enforced here defines the architecture.

Derived from src/**, .dependency-cruiser.cjs.

The ten directories​

Ten directories under src/. Four of them (model, time, data, layout) are the mandatory DOM-free core; scheduling is DOM-free too but optional. Only render, view, interaction, extensions may name document or window.

harness/
main.ts · plugins.ts · editing.ts — the library's first consumer, outside the src lint scope
↓
api/
Gantt · Dataset · tooltips · installPlugin — the only import a consumer makes; sealed exports map
↓
view/
GanttShell · PluginRuntime ports · GesturePipeline · capabilities · commands · navigation
render/
RenderBackend seam · dom backend · null backend · syncKeyed · decorations · ElementDescription
interaction/
attachEntryGestures · attachColumnGestures · createPointerGesture
extensions/
PluginRuntime · commands · keymap · createPopup · tooltips, context menu, inline editing
touches the DOM
↓
layout/
computeFrame · FrameLayout · rows/bars · TimeScaleModel · ScrollAxis
data/
DatasetState · EntryStore · PluginStores · transactions · fields · rollup
scheduling/
stub — optional plugin
↓
time/
Instant arithmetic · IANA zones · TimeScale · view presets · snap
↓
model/
entity types, ids, brands, FreeGanttError, Field, ChangeSet — zero deps, near-zero runtime
pure — no DOM, unit-tested in plain Node

Who may import whom​

.dependency-cruiser.cjs transcribes the diagram literally: one forbid() rule per layer, listing its only legal targets. Anything not listed fails the build.

LayerMay importNote
model— nothing —Leaf. Types plus entryId/rowId/barId and FreeGanttError.
timemodelPlus the one runtime dep temporal-polyfill, confined to zone.ts.
layouttime, modelWhere the shareable viewport models live — the only DOM-free layer both allowed time/ and reachable from view/.
schedulingtime, modelStub. Never imported by data/ statically — they meet at the extension hook.
datatime, modelFully built. Deliberately no scheduling edge: mutations resolve through the generic EditExtender hook (identity function when no plugin is installed).
renderlayoutConsumes GeometryFrame and nothing else from layout. Named leaf data/dev-mode.ts is also allowed. Reaches BarId/RowId through layout/index.ts's re-export, not model/ directly.
viewrender, layout, data, model, extensionsdata/ and model enter as type and dataset surfaces the shell orchestrates. extensions/ is the plugin runtime the shell constructs; the Gantt's own event bus is view/event-bus.ts. Still no time edge — every date/pixel computation a gesture needs is a pure layout/ function the shell hands back through EntryGestureContext.
interactionview, data, modelBuilt. Drives the EntryGestureContext and ColumnGestureContext seams typed in view/; model enters as type-only params. Never reaches time/, layout/ or render/, and never imports scheduling/.
extensionsapi, modelDogfood gate: a built-in may import only what a third-party plugin can. Named leaves: data/dev-mode.ts and layout/registration-table.ts. Never imports view/ — the shell constructs the runtime; the arrow is view → extensions.
apiview, data, model, time, layout, interaction, extensionsmodel, time and layout are type/primitive re-export edges, not behavioural ones. interaction arrives by constructor injection — the shell takes the gesture attachments structurally-typed rather than importing interaction/ itself. extensions is the plugin runtime and the shipped built-ins (tooltips, contextMenu, inlineEditing, createPopup).

:::note The thirteen custom lint rules eslint/rules/ holds what dependency-cruiser cannot see, all scoped to src/**. Time-related: no-date-outside-time, no-magic-time-constants, no-instant-arithmetic. Geometry-related: no-time-to-pixel-math, no-scroll-outside-scroll-attachment (exempt: view/scroll-attachment.ts), no-flow-layout-rows, no-inline-style-outside-geometry. Structure-related: no-module-level-state, model-is-types-only, no-store-mutation-outside-transaction, require-invariant-header, no-kind-literal, no-derived-in-json. Each rule ships a violation fixture so the rule itself is proven to bite. :::