@toclocoinc/lattice-grid 1.60.0 → 1.62.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.
Files changed (113) hide show
  1. package/README.md +1 -1
  2. package/docs/API.html +21 -17
  3. package/docs/api-detail.html +863 -17
  4. package/lattice-grid.d.ts +22 -11
  5. package/lattice-grid.esm.min.js +614 -49
  6. package/lattice-grid.min.cjs +614 -49
  7. package/lattice-grid.min.css +1 -1
  8. package/lattice-grid.min.js +614 -49
  9. package/modules/ai.d.ts +11 -2
  10. package/modules/ai.esm.min.js +24 -4
  11. package/modules/ai.min.cjs +24 -4
  12. package/modules/ai.min.js +24 -4
  13. package/modules/angular.d.ts +1 -1
  14. package/modules/angular.esm.min.js +2 -2
  15. package/modules/angular.min.cjs +2 -2
  16. package/modules/angular.min.js +2 -2
  17. package/modules/chart-alluvial.d.ts +1 -1
  18. package/modules/chart-alluvial.esm.min.js +1 -1
  19. package/modules/chart-arc.d.ts +1 -1
  20. package/modules/chart-arc.esm.min.js +1 -1
  21. package/modules/chart-bubblemap.d.ts +1 -1
  22. package/modules/chart-bubblemap.esm.min.js +1 -1
  23. package/modules/chart-bump.d.ts +1 -1
  24. package/modules/chart-bump.esm.min.js +1 -1
  25. package/modules/chart-calendar.d.ts +1 -1
  26. package/modules/chart-calendar.esm.min.js +1 -1
  27. package/modules/chart-decomposition.d.ts +1 -1
  28. package/modules/chart-decomposition.esm.min.js +1 -1
  29. package/modules/chart-diverging.d.ts +1 -1
  30. package/modules/chart-diverging.esm.min.js +1 -1
  31. package/modules/chart-dumbbell.d.ts +1 -1
  32. package/modules/chart-dumbbell.esm.min.js +1 -1
  33. package/modules/chart-fan.d.ts +1 -1
  34. package/modules/chart-fan.esm.min.js +1 -1
  35. package/modules/chart-hexbin.d.ts +1 -1
  36. package/modules/chart-hexbin.esm.min.js +1 -1
  37. package/modules/chart-hexmap.d.ts +1 -1
  38. package/modules/chart-hexmap.esm.min.js +1 -1
  39. package/modules/chart-icicle.d.ts +1 -1
  40. package/modules/chart-icicle.esm.min.js +1 -1
  41. package/modules/chart-parallel.d.ts +1 -1
  42. package/modules/chart-parallel.esm.min.js +1 -1
  43. package/modules/chart-ridgeline.d.ts +1 -1
  44. package/modules/chart-ridgeline.esm.min.js +1 -1
  45. package/modules/chart-roc.d.ts +1 -1
  46. package/modules/chart-roc.esm.min.js +1 -1
  47. package/modules/chart-slope.d.ts +1 -1
  48. package/modules/chart-slope.esm.min.js +1 -1
  49. package/modules/chart-splom.d.ts +1 -1
  50. package/modules/chart-splom.esm.min.js +1 -1
  51. package/modules/chart-waffle.d.ts +1 -1
  52. package/modules/chart-waffle.esm.min.js +1 -1
  53. package/modules/charts.d.ts +1 -1
  54. package/modules/charts.esm.min.js +48 -15
  55. package/modules/charts.min.cjs +48 -15
  56. package/modules/charts.min.js +48 -15
  57. package/modules/data-router.d.ts +1 -1
  58. package/modules/data-router.esm.min.js +4 -4
  59. package/modules/data-router.min.cjs +4 -4
  60. package/modules/data-router.min.js +4 -4
  61. package/modules/devtools.d.ts +1 -1
  62. package/modules/devtools.esm.min.js +2 -2
  63. package/modules/devtools.min.cjs +2 -2
  64. package/modules/devtools.min.js +2 -2
  65. package/modules/dhtmlx-compat.d.ts +1 -1
  66. package/modules/dhtmlx-compat.esm.min.js +4 -4
  67. package/modules/dhtmlx-compat.min.cjs +4 -4
  68. package/modules/dhtmlx-compat.min.js +4 -4
  69. package/modules/gantt.d.ts +144 -12
  70. package/modules/gantt.esm.min.js +1070 -201
  71. package/modules/gantt.min.cjs +1070 -201
  72. package/modules/gantt.min.js +1070 -201
  73. package/modules/htmx.d.ts +1 -1
  74. package/modules/htmx.esm.min.js +614 -49
  75. package/modules/htmx.min.cjs +614 -49
  76. package/modules/htmx.min.js +614 -49
  77. package/modules/kanban.d.ts +1 -1
  78. package/modules/kanban.esm.min.js +4 -4
  79. package/modules/kanban.min.cjs +4 -4
  80. package/modules/kanban.min.js +4 -4
  81. package/modules/kpi.d.ts +1 -1
  82. package/modules/kpi.esm.min.js +19 -6
  83. package/modules/kpi.min.cjs +19 -6
  84. package/modules/kpi.min.js +19 -6
  85. package/modules/layout.d.ts +1 -1
  86. package/modules/layout.esm.min.js +4 -4
  87. package/modules/layout.min.cjs +4 -4
  88. package/modules/layout.min.js +4 -4
  89. package/modules/mock-socket.d.ts +1 -1
  90. package/modules/mock-socket.esm.min.js +2 -2
  91. package/modules/mock-socket.min.cjs +2 -2
  92. package/modules/mock-socket.min.js +2 -2
  93. package/modules/react.d.ts +1 -1
  94. package/modules/react.esm.min.js +2 -2
  95. package/modules/react.min.cjs +2 -2
  96. package/modules/react.min.js +2 -2
  97. package/modules/svelte.d.ts +1 -1
  98. package/modules/svelte.esm.min.js +2 -2
  99. package/modules/svelte.min.cjs +2 -2
  100. package/modules/svelte.min.js +2 -2
  101. package/modules/tabs.d.ts +1 -1
  102. package/modules/tabs.esm.min.js +11 -4
  103. package/modules/tabs.min.cjs +11 -4
  104. package/modules/tabs.min.js +11 -4
  105. package/modules/vue.d.ts +1 -1
  106. package/modules/vue.esm.min.js +2 -2
  107. package/modules/vue.min.cjs +2 -2
  108. package/modules/vue.min.js +2 -2
  109. package/modules/webcomponent.d.ts +1 -1
  110. package/modules/webcomponent.esm.min.js +614 -49
  111. package/modules/webcomponent.min.cjs +614 -49
  112. package/modules/webcomponent.min.js +614 -49
  113. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  dependencies, no build step required. Optional adapters for React, Vue, Svelte
5
5
  and Web Components ship alongside it.
6
6
 
7
- Version 1.60.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
7
+ Version 1.62.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
8
8
 
9
9
  ---
10
10
 
package/docs/API.html CHANGED
@@ -360,7 +360,7 @@
360
360
  <div class="shell">
361
361
  <aside class="rail">
362
362
  <p class="rail__brand">Lattice Grid</p>
363
- <p class="rail__sub">API reference · v1.60.0</p>
363
+ <p class="rail__sub">API reference · v1.62.0</p>
364
364
  <nav>
365
365
  <div class="rail__group">
366
366
  <span class="rail__label">Start</span>
@@ -443,7 +443,7 @@
443
443
  </header>
444
444
 
445
445
  <p class="chips">
446
- <span class="chip">Version 1.60.0</span>
446
+ <span class="chip">Version 1.62.0</span>
447
447
  <span class="chip">Zero dependencies</span>
448
448
  <span class="chip"><a href="api-detail.html">Developer guide &rarr;</a></span>
449
449
  </p>
@@ -969,7 +969,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
969
969
  <tr><td class="name">stripedRows</td><td class="type">boolean</td><td class="dflt">false</td><td class="desc">Shade alternate data rows (zebra striping). Strictly opt-in, so an existing grid is unchanged on upgrade. Parity follows each row's logical index, so a stripe survives a scroll; group headings, footers and the grand total are never striped; selection and hover still win. Uses the theme's <code>--lattice-surface-alt</code>, so dark, high-contrast and terminal come for free.</td></tr>
970
970
  <tr><td class="name">verticalAlign</td><td class="type">'top' | 'middle' | 'bottom'</td><td class="dflt">, </td><td class="desc">Vertical alignment of cell content within a row, as a default for every column &mdash; the vertical counterpart to the per-column <code>align</code>. A column's own <code>verticalAlign</code> (or <code>cell.verticalAlign</code>) overrides it. Omitted, the grid keeps its historical placement (centred in a fixed-height row, top in an <code>autoHeight</code> row), so an existing grid is unchanged on upgrade. Setting a value aligns every column uniformly, including auto-height rows, unless a column opts out. See <a href="api-detail.html#vertical-align">Vertical alignment</a>.</td></tr>
971
971
  <tr><td class="name">tooltip</td><td class="type">TooltipConfig</td><td class="dflt">, </td><td class="desc"><code>{ delay, maxWidth }</code> &mdash; grid-level defaults for the rich cell tooltip. <code>delay</code> is how long the pointer or the keyboard cursor must rest on a cell before anything is built, 400ms by default; <code>maxWidth</code> is how wide the tooltip may grow (a number is pixels, a string is used as written). Defaults only: it switches nothing on, and a grid whose columns declare no <code>cell.tooltip</code> has no tooltips whatever is set here. See <a href="#cell-tooltips">Rich cell tooltips</a>.</td></tr>
972
- <tr><td class="name">scrollbars</td><td class="type">'auto' | 'always' | { x, y }</td><td class="dflt">'auto'</td><td class="desc">Keep the scroll viewport's scrollbars visible. <code>'auto'</code> is the platform's native behaviour, where overlay scrollbars fade when idle; <code>'always'</code> keeps both axes shown whether or not the pointer is over the grid. The object form <code>{ x, y }</code> pins each axis on its own, so <code>{ y: 'always' }</code> keeps the vertical bar while the horizontal one stays native. Omitted, the grid is unchanged on upgrade. See <a href="api-detail.html#scrollbars">Always-visible scrollbars</a>.</td></tr>
972
+ <tr><td class="name">scrollbars</td><td class="type">'auto' | 'always' | 'custom' | { x, y }</td><td class="dflt">'auto'</td><td class="desc">How the scroll viewport's scrollbars are drawn. <code>'auto'</code> is the platform's native behaviour, where overlay scrollbars fade when idle; <code>'always'</code> keeps that native bar shown whether or not the pointer is over the grid; <code>'custom'</code> makes the grid draw its own bar instead &mdash; always visible, the same in every browser, and sized by the <code>--lattice-scrollbar-*</code> tokens rather than by the platform, for a target bigger than a 7px overlay ribbon. Scrolling itself is unchanged in every mode. The object form <code>{ x, y }</code> sets each axis on its own, so <code>{ y: 'always' }</code> keeps the vertical bar while the horizontal one stays native; note that <code>'custom'</code> on one axis hides the native bar on both, and the grid warns once when the two disagree. Omitted, the grid is unchanged on upgrade. See <a href="api-detail.html#scrollbars">Always-visible and grid-drawn scrollbars</a>.</td></tr>
973
973
  <tr><td class="name">columnTagFilter</td><td class="type">boolean | { multiple, label }</td><td class="dflt">, </td><td class="desc">A bar above the headings for showing only the columns carrying a chosen tag. Draws nothing unless some column has <code>tags</code>. See <a href="api-detail.html#column-tags">Column tags</a>.</td></tr>
974
974
  <tr><td class="name">rowTemplate</td><td class="type">string | { template, cardsPerRow, maxCardWidth, gap, className, role, itemRole }</td><td class="dflt">, </td><td class="desc">Draw each row with a template instead of dividing it into columns, a card list, a feed, a search-result list. Compiles once; binds with <code>{{data.field}}</code>. <code>cardsPerRow</code> or <code>maxCardWidth</code> puts several on a line. The pipeline underneath is unchanged. See <a href="api-detail.html#cards">Cards, lists and feeds</a>.</td></tr>
975
975
  <tr><td class="name">gallery</td><td class="type">boolean | { template, tileWidth, tileHeight, cardsPerRow, gap, className, role, itemRole }</td><td class="dflt">, </td><td class="desc">Present rows as a gallery of tiles, laid out by the same 2-D virtualisation the grid runs. <code>true</code> generates a tile per row from the columns; <code>tileWidth</code> sizes them and the count across follows the container, or <code>cardsPerRow</code> fixes it. Presentation only &mdash; sort, filter, group and export are unchanged. See <a href="api-detail.html#cards">Cards, lists and feeds</a>.</td></tr>
@@ -1218,7 +1218,7 @@ grid.destroy();
1218
1218
  <table>
1219
1219
  <thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
1220
1220
  <tbody>
1221
- <tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.60.0'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
1221
+ <tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.62.0'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
1222
1222
  <tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
1223
1223
  <tr><td class="sig">set(key, value)</td><td class="type">void</td><td class="desc">Write one key. Every key is live; nothing needs a rebuild.</td></tr>
1224
1224
  <tr><td class="sig">setAll(values)</td><td class="type">void</td><td class="desc">Write several in one pass. Emits one <code>config:changed</code> for the batch, not one per key.</td></tr>
@@ -2730,7 +2730,7 @@ grid.permissions.setContext({ role: 'clerk' }); // re-resolves everything</
2730
2730
  <tbody>
2731
2731
  <tr><td class="sig">query(question, opts?)</td><td class="type">Promise&lt;result&gt;</td><td class="desc">Ask the model, validate the reply into a read-only spec. <code>result.describe()</code> shows the resolved query; nothing applies until <code>result.apply()</code> (or <code>autoApply</code>).</td></tr>
2732
2732
  <tr><td class="sig">applyQuery(result, opts?)</td><td class="type">object</td><td class="desc">Apply a reviewed result. Re-gated at the seam: an unsafe plan is refused. <code>opts.router</code> fans the answer to other viewers.</td></tr>
2733
- <tr><td class="sig">askBar(el, opts?)</td><td class="type">controller</td><td class="desc">Mount the ask/review/apply bar with an "auto-apply safe reads" toggle (off by default).</td></tr>
2733
+ <tr><td class="sig">askBar(el, opts?)</td><td class="type">controller</td><td class="desc">Mount the ask/review/apply bar with an "auto-apply safe reads" toggle (off by default). Mounts only when <code>createAI</code>'s <code>enable</code> allows <code>'query'</code>/<code>'ask'</code> (all allowed when omitted); <code>query()</code> itself is never gated.</td></tr>
2734
2734
  </tbody>
2735
2735
  </table>
2736
2736
  </div>
@@ -2776,7 +2776,7 @@ return `${rows} rows; write ${write.ok ? 'allowed' : 'refused'}`;</code></pre>
2776
2776
  <tbody>
2777
2777
  <tr><td class="sig">propose(instruction, opts?)</td><td class="type">Promise&lt;proposal&gt;</td><td class="desc">Ask the model for structured edits, validate + resolve them against the current view, and return a reviewable proposal with a before/after <code>diff</code>. Nothing is written. <code>opts.widen</code> opts into the full dataset; <code>opts.board</code> routes a Kanban move.</td></tr>
2778
2778
  <tr><td class="sig">applyProposal(proposal, opts?)</td><td class="type">Promise&lt;report&gt;</td><td class="desc">Apply an approved proposal through the gate (<code>origin: 'ai'</code>). A vetoing before-handler stops it; the report gives <code>applied</code>/<code>vetoed</code>.</td></tr>
2779
- <tr><td class="sig">actorBar(el, opts?)</td><td class="type">controller</td><td class="desc">Mount the propose &rarr; review-diff &rarr; approve bar, stating the scope.</td></tr>
2779
+ <tr><td class="sig">actorBar(el, opts?)</td><td class="type">controller</td><td class="desc">Mount the propose &rarr; review-diff &rarr; approve bar, stating the scope. Mounts only when <code>createAI</code>'s <code>enable</code> allows <code>'actor'</code> (allowed when omitted); <code>propose()</code> itself is never gated.</td></tr>
2780
2780
  </tbody>
2781
2781
  </table>
2782
2782
  </div>
@@ -5810,7 +5810,8 @@ gantt.mount(document.querySelector('#plan'), { editable: true });
5810
5810
  <p>Hovering a bar shows a tooltip with its dates, duration, % complete and slack. Tasks are flagged when they slip: <strong>overdue</strong> (incomplete and finishing before <code>today</code>) and <strong>at-risk</strong> (negative total float). Negative float needs a target: pass a <code>deadline</code> (a day-number) to <code>createGantt</code> and any task that cannot meet it gets negative slack and is drawn at-risk.</p>
5811
5811
  <p>Export: <code>gantt.toCSV()</code> writes the scheduled tasks as CSV (<code>{ dates: true }</code> for ISO dates); when a grid is bound, the grid's own Excel/CSV export works too. <code>gantt.view.toSVG()</code> serialises the drawn chart to a standalone SVG string — the handoff for turning it into an image or PDF.</p>
5812
5812
  <p>Accessibility: bars are focusable and carry an <code>aria-label</code> describing the task (name, dates, progress, slack, critical). With the keyboard, arrows move a focused task, Shift+arrows resize it, and <kbd>L</kbd> links two tasks (press it on the source, then on the successor) with a finish-to-start dependency; every edit is announced in a polite live region and focus follows the edited task. Set <code>keyboard: false</code> to opt out.</p>
5813
- <p><strong>Split view.</strong> The Gantt does not build a grid or the two-pane layout — you create and place a normal Lattice grid over the same task rows, and the Gantt <em>consumes</em> it. Binding is two-way: a drag on the timeline writes back through the grid (cycle 3), and an edit in the grid pane (any editor) reflects on the timeline. v1 assumes both panes share the same row height; <code>view.linkVerticalScroll(el)</code> mirrors vertical scroll so the rows stay aligned.</p>
5813
+ <p><strong>Two panes, two ways.</strong> There are two arrangements, and which one you want depends on whether the left pane is <em>your</em> grid or the Gantt's own. <code>gantt.mountSplit(container, options)</code> — the joined split view, whose options are listed with <code>GanttController</code> in the type reference — draws both panes itself as one row-aligned surface, and is what to reach for unless you specifically need your own grid beside the timeline. The arrangement described here is the other one: you create and place a normal Lattice grid over the same task rows, mount the timeline beside it with <code>gantt.mount()</code>, and the Gantt <em>consumes</em> the grid rather than building it.</p>
5814
+ <p><strong>Binding a grid you built yourself.</strong> It is two-way. A drag on the timeline writes the new dates back through the grid's public edit surface (<code>grid.edit.setCells</code>) whenever <code>createGantt</code> was given a <code>grid</code> and a <code>columns</code> map — without both, the drag moves the bar and writes nothing — and an edit made in the grid pane, from any editor, reflects on the timeline. Both panes must be given the same row height for the rows to line up, and <code>view.linkVerticalScroll(el)</code> mirrors vertical scroll between them. Neither is needed with <code>mountSplit</code>, which shares one scroll and one row height by construction.</p>
5814
5815
  <pre><code>&lt;div class="split" style="display:grid;grid-template-columns:360px 1fr"&gt;
5815
5816
  &lt;div id="tasks"&gt;&lt;/div&gt; &lt;!-- the grid pane --&gt;
5816
5817
  &lt;div id="plan"&gt;&lt;/div&gt; &lt;!-- the timeline pane --&gt;
@@ -6226,14 +6227,15 @@ ai.insights(document.querySelector('#insights')); <span class="c
6226
6227
  const { text, flagged } = await ai.explain({ kind: 'column', colId: 'amount' });</code></pre>
6227
6228
  <p><strong>Grounded, and reconciled.</strong> Where your provider offers tool-use, the model is given a curated <strong>read-only</strong> tool set (<code>getSchema</code>, <code>getProfile</code>, <code>getStatistics</code>, <code>getForecast</code>, <code>runQuery</code>) and <em>our</em> engine computes what it asks for; where it does not, the module builds a <strong>facts packet</strong> from the grid's computed results (<code>grid.statistics</code>, the profile, forecasts, view counts) and passes it in the prompt. Either way, <strong>every figure in the narrative is reconciled against the values the engine produced this render</strong> &mdash; an ungrounded number is stripped before the user sees it. The prompt is constrained to narrate-only; the layer is read-only and never mutates data. <code>redact</code> and <code>maxRows</code> bound what the module hands <code>ask()</code>, and the module sends nothing itself. An <code>ask()</code> error surfaces a friendly message and the grid stays fully usable &mdash; AI is additive, never load-bearing.</p>
6228
6229
  <p><code>createAI</code> is complementary to <code>grid.ai</code>: <code>grid.ai</code> is the intent/plan skill layer (a question becomes a validated filter/sort plan you preview and apply); <code>createAI</code> is the narrative/insights consumer that explains figures. They can share one host <code>ask()</code> &mdash; pass none to <code>createAI</code> and it adopts the grid's configured <code>ai.ask</code>, running the facts-packet path over it.</p>
6230
+ <p><strong><code>enable</code> gates only the three DOM-mounting convenience methods</strong>: <code>'narrative'</code>/<code>'insights'</code> is what lets <code>insights()</code> mount, <code>'query'</code>/<code>'ask'</code> is what lets <code>askBar()</code> mount (see &sect;<a href="#ai-askdata">Ask-your-data</a>), and <code>'actor'</code> is what lets <code>actorBar()</code> mount (see &sect;<a href="#ai-actor">AI as a governed actor</a>). All three are allowed when <code>enable</code> is omitted. The programmatic API &mdash; <code>explain()</code>, <code>query()</code>, <code>propose()</code>, <code>facts()</code>, <code>riskSummary()</code> and the rest of the controller &mdash; is <strong>never gated by <code>enable</code></strong> and runs regardless of the allowlist: a host that wants no AI surface at all simply never calls these methods.</p>
6229
6231
  <div class="table-wrap">
6230
6232
  <table>
6231
6233
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
6232
6234
  <tbody>
6233
- <tr><td class="sig">createAI(grid, config)</td><td class="desc">Create an AI narrative / insights controller over a live grid (headless or rendered). Config: <code>ask</code> (the host callback; falls back to the grid's <code>ai.ask</code>), <code>enable</code>, <code>maxRows</code>, <code>redact</code>, <code>tools</code>, <code>locale</code>, <code>reconcile</code> (<code>'strip'</code>/<code>'flag'</code>), <code>element</code>, <code>onNarrative</code>, <code>onError</code>.</td></tr>
6234
- <tr><td class="sig">explain(target?, opts?) / narrate(...)</td><td class="desc">Produce a grounded, reconciled narrative. <code>target</code> is <code>{ kind: 'view' }</code>, <code>{ kind: 'column', colId }</code>, <code>{ kind: 'forecast', colId, options }</code>, <code>{ kind: 'kpi'|'chart', facts }</code>, or <code>{ kind: 'risk', gantt, board }</code> for a board / Gantt risk summary. Resolves to <code>{ text, facts, grounded, flagged, rounds, mode }</code>.</td></tr>
6235
- <tr><td class="sig">riskSummary(sources?, opts?)</td><td class="desc">A board / Gantt <strong>RISK SUMMARY</strong> &mdash; &ldquo;3 tasks at risk on the critical path, SPI 0.67, 2 SLA breaches&rdquo; &mdash; grounded on the separate Gantt / Kanban modules' outputs (<code>gantt</code>, <code>board</code>/<code>sla</code>, or their precomputed <code>earnedValue</code>/<code>schedule</code>/<code>breaches</code>). A convenience over <code>explain({ kind: 'risk' })</code>, through the same reconciliation guard.</td></tr>
6236
- <tr><td class="sig">insights(el?, opts?)</td><td class="desc">Mount (or re-target) the insights panel into an element, its generate control wired to a view narrative. Keeps the grid usable on an <code>ask()</code> error.</td></tr>
6235
+ <tr><td class="sig">createAI(grid, config)</td><td class="desc">Create an AI narrative / insights controller over a live grid (headless or rendered). Config: <code>ask</code> (the host callback; falls back to the grid's <code>ai.ask</code>), <code>enable</code> (gates only <code>insights()</code>/<code>askBar()</code>/<code>actorBar()</code> &mdash; see above), <code>maxRows</code>, <code>redact</code>, <code>tools</code>, <code>locale</code>, <code>reconcile</code> (<code>'strip'</code>/<code>'flag'</code>), <code>element</code>, <code>onNarrative</code>, <code>onError</code>.</td></tr>
6236
+ <tr><td class="sig">explain(target?, opts?) / narrate(...)</td><td class="desc">Produce a grounded, reconciled narrative. Always callable &mdash; not gated by <code>enable</code>. <code>target</code> is <code>{ kind: 'view' }</code>, <code>{ kind: 'column', colId }</code>, <code>{ kind: 'forecast', colId, options }</code>, <code>{ kind: 'kpi'|'chart', facts }</code>, or <code>{ kind: 'risk', gantt, board }</code> for a board / Gantt risk summary. Resolves to <code>{ text, facts, grounded, flagged, rounds, mode }</code>.</td></tr>
6237
+ <tr><td class="sig">riskSummary(sources?, opts?)</td><td class="desc">A board / Gantt <strong>RISK SUMMARY</strong> &mdash; &ldquo;3 tasks at risk on the critical path, SPI 0.67, 2 SLA breaches&rdquo; &mdash; grounded on the separate Gantt / Kanban modules' outputs (<code>gantt</code>, <code>board</code>/<code>sla</code>, or their precomputed <code>earnedValue</code>/<code>schedule</code>/<code>breaches</code>). A convenience over <code>explain({ kind: 'risk' })</code>, through the same reconciliation guard. Always callable &mdash; not gated by <code>enable</code>.</td></tr>
6238
+ <tr><td class="sig">insights(el?, opts?)</td><td class="desc">Mount (or re-target) the insights panel into an element, its generate control wired to a view narrative. Keeps the grid usable on an <code>ask()</code> error. Mounts only when <code>enable</code> allows <code>'narrative'</code>/<code>'insights'</code>.</td></tr>
6237
6239
  <tr><td class="sig">attachExplain(target, opts?)</td><td class="desc">Build an &ldquo;Explain&rdquo; button bound to a target (a KPI tile, a chart datum, a column). Clicking it narrates the target.</td></tr>
6238
6240
  <tr><td class="sig">facts(target?, opts?)</td><td class="desc">Build the facts packet for a target <em>without</em> calling <code>ask()</code> &mdash; the exact grounded set a narrative would use, and what would leave the browser.</td></tr>
6239
6241
  <tr><td class="sig">on(name, fn) / off(name, fn) / destroy()</td><td class="desc">Events: <code>narrative</code> and <code>error</code>. <code>destroy()</code> tears the controller down and leaves the grid untouched.</td></tr>
@@ -6464,7 +6466,8 @@ createGrid(layout.payload('pipeline'), { rowKey: 'id', rows, columns });</code><
6464
6466
  <p><strong>Which payloads re-lay-out on <code>window:resized</code>, honestly.</strong> The grid and the chart each own a <code>ResizeObserver</code> and respond correctly; the Gantt does too since 1.52.0; kanban and KPI do no JS work at all on a resize and need none, because they reflow by CSS construction (a kanban column keeps its 280px and the board starts scrolling). Every one of those is measured in <code>test/layout-browser.test.js</code> rather than asserted, including a grid column declared as a <em>percentage</em> (<code>layout: { width: '50%' }</code>), which follows the window (BACKLOG-0001117): half of the viewport at 800px, half of it again at 400px.</p>
6465
6467
  <p><strong>Idle cost, measured.</strong> A twelve-window dashboard is <strong>indistinguishable from a page with no layout module on it at all</strong>. Over 8 seconds of real Chrome (<code>bench/layout-idle.mjs</code>), twelve windows with empty payloads, twelve <em>independent</em> live grids in them, and a single lone grid with no layout module all sit in the same few-millisecond band &mdash; under a tenth of one percent of a core. They are not separated here because they cannot be: seven runs across two machines land between 3.1ms and 9.0ms and the ordering between them inverts run to run, so a stated delta would be reporting the noise floor. The module adds no timer, no frame loop and no polling, and owns exactly one <code>ResizeObserver</code> for the whole layout rather than one per window. <strong>The one figure that is a result rather than noise is not this module's:</strong> twelve grids <em>derived</em> from one shared parent filtered at 20Hz cost <strong>3,155&ndash;3,409ms</strong> over the same 8 seconds &mdash; several hundred times the quiet band, and stable across every run &mdash; because the engine's repaint listener for a derived source's <code>rows:changed</code> calls the renderer directly and bypasses <code>grid.updates.pause()</code>. The bench reports the size of that gap rather than leaving it inferred.</p>
6466
6468
  <p><strong>Closing a window does not destroy its payload.</strong> <code>window:closed</code> hands the payload container back; whatever you mounted inside it is yours to destroy. Stated plainly because a leaked grid per closed window is the obvious failure, and this module has no way to know that a <code>div</code> contains something with a <code>destroy()</code>.</p>
6467
- <p><strong>Not in v1:</strong> per-frame drag events; nested layouts; tabbed windows (that is <code>modules/tabs</code>); and <strong>responsive breakpoints &mdash; a twelve-window dashboard on a phone is unsolved, and this does not pretend otherwise</strong>. Server-side persistence is the host's, with <code>getLayout()</code>.</p>
6469
+ <p><strong>Not in v1:</strong> per-frame drag events; and <strong>responsive breakpoints &mdash; a twelve-window dashboard on a phone is unsolved, and this does not pretend otherwise</strong>. Server-side persistence is the host's, with <code>getLayout()</code>.</p>
6470
+ <p><strong>Nesting is not a limitation, because a payload is an ordinary container.</strong> A window's payload is a plain <code>div</code> the module creates, sizes and never reads, so anything composes into it exactly as it would into any other element &mdash; including another <code>createLayout</code> dashboard, or a <code>modules/tabs</code> strip for tabbed windows. Neither is a special case this module wires up; both are the ordinary consequence of being payload-agnostic, and both are proved by test rather than asserted from the design (<code>test/layout.test.js</code>'s two composition tests).</p>
6468
6471
  <div class="table-wrap">
6469
6472
  <table>
6470
6473
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
@@ -7891,7 +7894,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
7891
7894
  <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
7892
7895
  <tbody>
7893
7896
  <tr><td class="name">ask</td><td class="type">AIAsk</td><td class="desc">The host's model callback. Falls back to the grid's `ai.ask` when omitted. <small>(optional)</small></td></tr>
7894
- <tr><td class="name">enable</td><td class="type">string[]</td><td class="desc">Opt into specific features: `'narrative'`, `'insights'`, `'query'`/`'ask'`. All on when omitted. <small>(optional)</small></td></tr>
7897
+ <tr><td class="name">enable</td><td class="type">string[]</td><td class="desc">Restricts which of the three DOM-mounting convenience methods are allowed to mount: `'narrative'`/`'insights'` for `insights()`, `'query'`/`'ask'` for `askBar()`, `'actor'` for `actorBar()`. All three are allowed when `enable` is omitted. This does NOT gate the programmatic API — `explain()`, `query()`, `propose()`, `facts()`, `riskSummary()` and the rest of the controller always run regardless of `enable` — because a host that wants no AI surface at all simply never calls these methods. <small>(optional)</small></td></tr>
7895
7898
  <tr><td class="name">autoApply</td><td class="type">boolean</td><td class="desc">Ask-your-data: apply a safe (read-only) query result without a confirm step. Off by default — the resolved query is shown and waits for Apply. <small>(optional)</small></td></tr>
7896
7899
  <tr><td class="name">router</td><td class="type">unknown</td><td class="desc">A Data Router instance; on applying a query the answer rows are fanned to its attached viewers (grid + chart + KPI together) via `load()`. <small>(optional)</small></td></tr>
7897
7900
  <tr><td class="name">schemaOptions</td><td class="type">object</td><td class="desc">Budgets passed to the schema builder for ask-your-data. <small>(optional)</small></td></tr>
@@ -9859,7 +9862,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
9859
9862
  <tr><td class="name">resourceLoad</td><td class="type">GanttResourceLoad | null</td><td class="desc">The latest resource-load report, or null before a successful schedule (BACKLOG-0000948). <small>(read-only)</small></td></tr>
9860
9863
  <tr><td class="name">setTasks</td><td class="type">(tasks: GanttTask[]): GanttSchedule</td><td class="desc"></td></tr>
9861
9864
  <tr><td class="name">setDependencies</td><td class="type">(deps: GanttDependency[]): GanttSchedule</td><td class="desc"></td></tr>
9862
- <tr><td class="name">applyEdit</td><td class="type">(patch: { id: string | number; start?: number; end?: number; duration?: number }, editOpts?: { writeBack?: boolean }): GanttSchedule</td><td class="desc"></td></tr>
9865
+ <tr><td class="name">applyEdit</td><td class="type">(patch: { id: string | number; start?: number; end?: number; duration?: number; percentComplete?: number; work?: number | Array&lt;{ date: number | string | Date; hours: number }&gt; }, editOpts?: { writeBack?: boolean }): GanttSchedule</td><td class="desc">Apply one task edit and recompute — the single gated choke point every drag, keypress, table cell and workload cell commits through. A `work` ARRAY is the task's per-day contour (BACKLOG-0001282). Given without an explicit `start`/`end`/`duration` it SETS the span: the task starts on the contour's first day and runs through its last, so booking hours beyond the bar extends it and clearing an edge bucket pulls it back. Conversely, a `start` or `duration` in the patch re-times an existing contour rather than discarding it — a move keeps its shape, a resize stretches it across the new span at the same daily levels.</td></tr>
9863
9866
  <tr><td class="name">compute</td><td class="type">(): GanttSchedule</td><td class="desc"></td></tr>
9864
9867
  <tr><td class="name">findViolations</td><td class="type">(): GanttViolation[]</td><td class="desc"></td></tr>
9865
9868
  <tr><td class="name">resources</td><td class="type">(loadOpts?: { resources?: GanttResourceSpec; defaultCapacity?: number }): GanttResourceLoad</td><td class="desc">Compute the resource load and over-allocations on demand (BACKLOG-0000948), optionally overriding the capacities for this call.</td></tr>
@@ -9870,7 +9873,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
9870
9873
  <tr><td class="name">on</td><td class="type">(event: 'schedule' | 'error', fn: (payload: unknown) =&gt; void): () =&gt; void</td><td class="desc"></td></tr>
9871
9874
  <tr><td class="name">off</td><td class="type">(event: 'schedule' | 'error', fn: (payload: unknown) =&gt; void): void</td><td class="desc"></td></tr>
9872
9875
  <tr><td class="name">mount</td><td class="type">(container: unknown, options?: {</td><td class="desc">Render the plan into a container as an SVG timeline (bars, dependency arrows, critical-path highlight, today line, non-working shading, milestones, progress). The view redraws when the schedule recomputes.</td></tr>
9873
- <tr><td class="name">mountSplit</td><td class="type">(container: unknown, options?: {</td><td class="desc">Mount the JOINED split view (BACKLOG-0000938): one continuous, row-aligned surface with a left task-grid panel (Task Name tree with expand/collapse, assignee avatars, a circular % ring, plus any host columns) and the right timeline, sharing a single vertical scroll so every grid row lines up exactly with its bar row. The timeline scrolls horizontally on its own. Composes the controller's schedule; makes no change to grid core.</td></tr>
9876
+ <tr><td class="name">mountSplit</td><td class="type">(container: unknown, options?: {</td><td class="desc">Mount the JOINED split view (BACKLOG-0000938): one continuous, row-aligned surface with a left task-grid panel — by default the Task Name tree with expand/collapse, start, finish, duration, assignee avatars and a circular % ring (BACKLOG-0001285), plus any host columns — and the right timeline, sharing a single vertical scroll so every grid row lines up exactly with its bar row. The timeline scrolls horizontally on its own. Composes the controller's schedule; makes no change to grid core. The plan is editable from BOTH panes (BACKLOG-0001280): every gesture `mount` has — pointer drag to move, drag on the right edge to resize, arrow-key move, Shift+arrow resize, `l` to link, Delete — works on the timeline here, and a `start`/`end`/`duration`/`progress`/`name` column in the left panel is inline-editable on a double-click. Both routes commit through the same `applyEdit` choke point, so `beforeTaskMove`, `beforeTaskResize`, `beforeProgressChange` and `beforeTaskEdit` stay the single veto whichever pane the edit came from. The three switches that govern it carry the same meaning and the same defaults as `mount`'s: `editable` (default true) turns every edit on or off, both panes at once; `keyboard` (default true) turns off the focusable bars, the arrow-key gestures and the ARIA announcements while leaving pointer editing alone; and `resizeZone` (default 6) is how many pixels in from a bar's right edge begin a resize rather than a move. `workload` adds the resource band beneath the plan (BACKLOG-0001281), which is display-only — it reports hours, it does not accept them.</td></tr>
9874
9877
  <tr><td class="name">captureBaseline</td><td class="type">(): Array&lt;{ id: string; baselineStart: number; baselineEnd: number; baselineDuration: number }&gt;</td><td class="desc">Capture a baseline (planned) snapshot of the current schedule as HOST data (this does not mutate the tasks). Store it and feed it back as `baselineStart`/`baselineEnd` task fields to get variance and ghost bars.</td></tr>
9875
9878
  <tr><td class="name">earnedValue</td><td class="type">(evmOpts?: { statusDate?: number | string | Date; costField?: string; actualCostField?: string }): GanttEarnedValue</td><td class="desc">Compute earned-value (EVM) metrics for the current plan at a status date (BACKLOG-0000958): PV/EV/AC and the derived SV/CV/SPI/CPI per task, rolled up to summaries and the project. Budget (BAC) is the task's `cost`, or its duration when no cost is given; AC comes from `actualCost`.</td></tr>
9876
9879
  <tr><td class="name">unmount</td><td class="type">(): void</td><td class="desc">Detach the mounted view, if any. The host still owns the container.</td></tr>
@@ -10089,6 +10092,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
10089
10092
  <tr><td class="name">assignees</td><td class="type">string[]</td><td class="desc"><small>(optional)</small></td></tr>
10090
10093
  <tr><td class="name">owner</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
10091
10094
  <tr><td class="name">assignments</td><td class="type">Array&lt;{ resource?: string; name?: string; id?: string; units?: number }&gt;</td><td class="desc">Explicit resource assignments with fractional units (BACKLOG-0000948): `units` is a multiplier where 1 is a full-time booking. Use this when a task books a resource at less (or more) than 100%; a bare `assignee` is `units: 1`. <small>(optional)</small></td></tr>
10095
+ <tr><td class="name">work</td><td class="type">number | Array&lt;{ date: number | string | Date; hours: number }&gt;</td><td class="desc">The task's effort, in one of two forms (BACKLOG-0001281/1282). A **number** is the task's TOTAL hours; the workload band divides it between the assignments in proportion to their units and spreads each share evenly over the working days the task spans. (`hours` is accepted as the same field under its other common name.) An **array** is an explicit per-day contour — what a planner types into a workload cell — and states each day's hours itself: the task's total is the sum of the entries, nothing is spread, and the contour is authoritative for the span, so `applyEdit` derives the task's `start` and `duration` from its first and last day. An EMPTY array means "no hours booked", which is how clearing every bucket is expressed without reviving the even spread. A bar move re-times the contour onto the new days unchanged; a resize stretches it across the new span at the same daily levels. `date` is an ISO date, a `Date` or a plan day-number; the module writes ISO dates back. <small>(optional)</small></td></tr>
10092
10096
  <tr><td class="name">priority</td><td class="type">number</td><td class="desc">Leveling priority: a higher value is delayed last (default 0). <small>(optional)</small></td></tr>
10093
10097
  <tr><td class="name">height</td><td class="type">number</td><td class="desc">An explicit row height (px) for the split view; applied to both panels. <small>(optional)</small></td></tr>
10094
10098
  <tr><td class="name">cost</td><td class="type">number</td><td class="desc">The budgeted cost (BAC) for earned-value analysis (BACKLOG-0000958). When omitted the task's duration is used as the budget, giving schedule-only EVM. <small>(optional)</small></td></tr>
@@ -10204,7 +10208,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
10204
10208
  <tr><td class="name">stripedRows</td><td class="type">boolean</td><td class="desc">Shade alternate data rows (zebra striping). Off by default, and strictly opt-in: an existing grid must look exactly the same on upgrade. When `true`, every other data row takes the theme's `--lattice-surface-alt` background, which every palette already defines, so dark, high-contrast and terminal stripe correctly without extra work. Parity follows the row's *logical* index, not its position in the DOM, so a row keeps its stripe across a scroll even though the rows are recycled. Structural rows — group headings, group footers and the grand total — are never striped, and both selection and hover still win over the stripe. <small>(optional)</small></td></tr>
10205
10209
  <tr><td class="name">verticalAlign</td><td class="type">VAlign</td><td class="desc">Vertical alignment of cell content within a row, as a default for every column (BACKLOG-0000989). `top`, `middle` or `bottom`; a column's own `verticalAlign` overrides it for that column. The horizontal counterpart is the per-column `align`. Omitted, the grid keeps its historical placement — content centred in a fixed-height row and top-aligned in an `autoHeight` row — so an existing grid is unchanged on upgrade. Setting a value aligns every column uniformly, including `autoHeight` rows, unless a column opts out. <small>(optional)</small></td></tr>
10206
10210
  <tr><td class="name">tooltip</td><td class="type">TooltipConfig</td><td class="desc">Defaults for the rich cell tooltip (BACKLOG-0001204). The tooltip itself is declared per column, on `cell.tooltip`; this only carries the settings that are a house style rather than a per-column decision. It switches nothing on: a column with no `cell.tooltip` has no tooltip whatever is set here. <small>(optional)</small></td></tr>
10207
- <tr><td class="name">scrollbars</td><td class="type">ScrollbarMode | { x?: ScrollbarMode; y?: ScrollbarMode }</td><td class="desc">Keep the scroll viewport's scrollbars visible (BACKLOG-0000990). `'auto'` (the default) is the platform's native behaviour, where overlay scrollbars fade when idle. `'always'` keeps both axes shown whether or not the pointer is over the grid. The object form controls each axis on its own — `{ y: 'always' }` pins the vertical bar while the horizontal one stays native. Omitted, the grid is unchanged on upgrade. <small>(optional)</small></td></tr>
10211
+ <tr><td class="name">scrollbars</td><td class="type">ScrollbarMode | { x?: ScrollbarMode; y?: ScrollbarMode }</td><td class="desc">How the scroll viewport's scrollbars are drawn (BACKLOG-0000990, BACKLOG-0001288). `'auto'` (the default) is the platform's native behaviour, where overlay scrollbars fade when idle. `'always'` keeps that native bar shown whether or not the pointer is over the grid. `'custom'` makes the grid draw its own bar on each axis instead — always visible, the same in every browser, and sized by `--lattice-scrollbar-size` / `--lattice-scrollbar-thumb-min` rather than by the platform. Scrolling itself is unchanged in every mode. The object form controls each axis on its own — `{ y: 'always' }` pins the vertical bar while the horizontal one stays native. Note that `'custom'` on one axis hides the native bar on both, because no browser offers per-axis control of that; the grid warns once if the two axes disagree. Omitted, the grid is unchanged on upgrade. <small>(optional)</small></td></tr>
10208
10212
  <tr><td class="name">columnTagFilter</td><td class="type">boolean | { multiple?: boolean; label?: string }</td><td class="desc">Show a bar above the column headings for filtering columns by tag. Off by default, and it draws nothing unless some column carries a `tags` entry. `multiple: true` lets more than one tag be chosen at once. Only tagged columns are ever hidden, so an untagged account or total column stays visible whatever is selected. <small>(optional)</small></td></tr>
10209
10213
  <tr><td class="name">anomalySummary</td><td class="type">boolean | { column?: string; label?: string }</td><td class="desc">Show a small chip in the grid chrome that reads how many rows an anomaly shadow column has flagged, and filters the grid to exactly those when it is clicked (BACKLOG-0000799). Off by default, and it draws nothing unless a column declares a `shadow: { kind: 'anomalyFlag' }`. The count and the filter both read that one shadow column, so the number on the chip is the number of rows the click reveals. `column` names the base column to summarise when more than one anomaly-flag shadow is present; `label` overrides the chip's wording. <small>(optional)</small></td></tr>
10210
10214
  <tr><td class="name">typeOptions</td><td class="type">Record&lt;string, {</td><td class="desc">Per-column options a data type reads. `ratio` and `percentRate` use `{ weight }` to name the column their average is weighted by. A unit type reads `{ significantFigures }` to render to a fixed precision rather than a fixed number of decimals. <small>(optional)</small></td></tr>
@@ -12930,7 +12934,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
12930
12934
  <!-- END GENERATED TYPE REFERENCE -->
12931
12935
 
12932
12936
  <footer>
12933
- Lattice Grid 1.60.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
12937
+ Lattice Grid 1.62.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
12934
12938
  This document describes the behaviour of the shipped library. Where this guide and the code
12935
12939
  disagree, the code wins: please <a href="https://www.latticegrid.dev">tell us</a>.
12936
12940
  </footer>