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.
Gantt · Dataset · tooltips ·
installPlugin — the only import a consumer makes; sealed
exports map
GanttShell · PluginRuntime ports · GesturePipeline ·
capabilities · commands · navigation
RenderBackend seam · dom backend · null backend · syncKeyed ·
decorations · ElementDescription
attachEntryGestures · attachColumnGestures ·
createPointerGesture
PluginRuntime · commands · keymap · createPopup · tooltips,
context menu, inline editing
computeFrame · FrameLayout · rows/bars ·
TimeScaleModel · ScrollAxis
DatasetState · EntryStore · PluginStores ·
transactions · fields · rollup
Instant arithmetic · IANA zones · TimeScale · view presets · snap
FreeGanttError, Field, ChangeSet — zero deps,
near-zero runtime
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.
| Layer | May import | Note |
|---|---|---|
model | — nothing — | Leaf. Types plus entryId/rowId/barId and FreeGanttError. |
time | model | Plus the one runtime dep temporal-polyfill, confined to zone.ts. |
layout | time, model | Where the shareable viewport models live — the only DOM-free layer both allowed time/ and reachable from view/. |
scheduling | time, model | Stub. Never imported by data/ statically — they meet at the extension hook. |
data | time, model | Fully built. Deliberately no scheduling edge: mutations resolve through the generic EditExtender hook (identity function when no plugin is installed). |
render | layout | Consumes 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. |
view | render, layout, data, model, extensions | data/ 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. |
interaction | view, data, model | Built. 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/. |
extensions | api, model | Dogfood 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. |
api | view, data, model, time, layout, interaction, extensions | model, 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.
:::