@toclocoinc/lattice-grid 1.61.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 +16 -14
  3. package/docs/api-detail.html +863 -17
  4. package/lattice-grid.d.ts +22 -11
  5. package/lattice-grid.esm.min.js +456 -43
  6. package/lattice-grid.min.cjs +456 -43
  7. package/lattice-grid.min.css +1 -1
  8. package/lattice-grid.min.js +456 -43
  9. package/modules/ai.d.ts +11 -2
  10. package/modules/ai.esm.min.js +6 -4
  11. package/modules/ai.min.cjs +6 -4
  12. package/modules/ai.min.js +6 -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 +17 -11
  55. package/modules/charts.min.cjs +17 -11
  56. package/modules/charts.min.js +17 -11
  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 +1 -1
  70. package/modules/gantt.esm.min.js +4 -4
  71. package/modules/gantt.min.cjs +4 -4
  72. package/modules/gantt.min.js +4 -4
  73. package/modules/htmx.d.ts +1 -1
  74. package/modules/htmx.esm.min.js +456 -43
  75. package/modules/htmx.min.cjs +456 -43
  76. package/modules/htmx.min.js +456 -43
  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 +4 -4
  103. package/modules/tabs.min.cjs +4 -4
  104. package/modules/tabs.min.js +4 -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 +456 -43
  111. package/modules/webcomponent.min.cjs +456 -43
  112. package/modules/webcomponent.min.js +456 -43
  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.61.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.61.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.61.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.61.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>
@@ -6227,14 +6227,15 @@ ai.insights(document.querySelector('#insights')); <span class="c
6227
6227
  const { text, flagged } = await ai.explain({ kind: 'column', colId: 'amount' });</code></pre>
6228
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>
6229
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>
6230
6231
  <div class="table-wrap">
6231
6232
  <table>
6232
6233
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
6233
6234
  <tbody>
6234
- <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>
6235
- <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>
6236
- <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>
6237
- <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>
6238
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>
6239
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>
6240
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>
@@ -6465,7 +6466,8 @@ createGrid(layout.payload('pipeline'), { rowKey: 'id', rows, columns });</code><
6465
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>
6466
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>
6467
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>
6468
- <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>
6469
6471
  <div class="table-wrap">
6470
6472
  <table>
6471
6473
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
@@ -7892,7 +7894,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
7892
7894
  <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
7893
7895
  <tbody>
7894
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>
7895
- <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>
7896
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>
7897
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>
7898
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>
@@ -10206,7 +10208,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
10206
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>
10207
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>
10208
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>
10209
- <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>
10210
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>
10211
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>
10212
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>
@@ -12932,7 +12934,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
12932
12934
  <!-- END GENERATED TYPE REFERENCE -->
12933
12935
 
12934
12936
  <footer>
12935
- Lattice Grid 1.61.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
12937
+ Lattice Grid 1.62.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
12936
12938
  This document describes the behaviour of the shipped library. Where this guide and the code
12937
12939
  disagree, the code wins: please <a href="https://www.latticegrid.dev">tell us</a>.
12938
12940
  </footer>