laif-ds 0.2.91 → 1.0.0-app-data-grid.63
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.
- package/dist/CHANGELOG.md +284 -0
- package/dist/agent-docs/adoption-report.json +153 -93
- package/dist/agent-docs/components/AppBottomNav.md +426 -0
- package/dist/agent-docs/components/AppCard.md +25 -21
- package/dist/agent-docs/components/AppDataGrid.md +2467 -0
- package/dist/agent-docs/components/AppEditor.md +18 -13
- package/dist/agent-docs/components/AppSelect.md +50 -0
- package/dist/agent-docs/components/AppSidebar.md +302 -19
- package/dist/agent-docs/components/DataTable.md +34 -3
- package/dist/agent-docs/components/DatePicker.md +7 -7
- package/dist/agent-docs/components/FileUploader.md +12 -12
- package/dist/agent-docs/components/GridFilterPanel.md +112 -0
- package/dist/agent-docs/components/Sidebar.md +4 -0
- package/dist/agent-docs/components-list.md +45 -1
- package/dist/agent-docs/manifest.json +311 -32
- package/dist/components/design-tokens.js +222 -2
- package/dist/components/ui/app-bottom-nav.js +165 -0
- package/dist/components/ui/app-data-grid/app-data-grid.js +851 -0
- package/dist/components/ui/app-data-grid/engine/use-grid-column-resize.js +110 -0
- package/dist/components/ui/app-data-grid/engine/use-grid-engine.js +586 -0
- package/dist/components/ui/app-data-grid/engine/use-grid-infinite-loader.js +50 -0
- package/dist/components/ui/app-data-grid/engine/use-grid-virtualizer.js +90 -0
- package/dist/components/ui/app-data-grid/grid-aggregate.js +5 -0
- package/dist/components/ui/app-data-grid/grid-column-registry.js +28 -0
- package/dist/components/ui/app-data-grid/grid-columns.js +204 -0
- package/dist/components/ui/app-data-grid/grid-filter-compile.js +33 -0
- package/dist/components/ui/app-data-grid/grid-filter-operators.js +113 -0
- package/dist/components/ui/app-data-grid/grid-grouping.js +104 -0
- package/dist/components/ui/app-data-grid/grid-header-matrix.js +112 -0
- package/dist/components/ui/app-data-grid/grid-i18n.js +207 -0
- package/dist/components/ui/app-data-grid/grid-mount-count.js +8 -0
- package/dist/components/ui/app-data-grid/grid-reducer.js +200 -0
- package/dist/components/ui/app-data-grid/grid-result-scope.js +35 -0
- package/dist/components/ui/app-data-grid/grid-tokens.js +169 -0
- package/dist/components/ui/app-data-grid/grid-view-preset.js +33 -0
- package/dist/components/ui/app-data-grid/parts/grid-actions-row.js +86 -0
- package/dist/components/ui/app-data-grid/parts/grid-cell-text.js +67 -0
- package/dist/components/ui/app-data-grid/parts/grid-column-registry-panel.js +320 -0
- package/dist/components/ui/app-data-grid/parts/grid-density-control.js +88 -0
- package/dist/components/ui/app-data-grid/parts/grid-filter-chips.js +224 -0
- package/dist/components/ui/app-data-grid/parts/grid-filter-editor.js +197 -0
- package/dist/components/ui/app-data-grid/parts/grid-filter-panel.js +386 -0
- package/dist/components/ui/app-data-grid/parts/grid-filter-row.js +273 -0
- package/dist/components/ui/app-data-grid/parts/grid-fullscreen-toggle.js +29 -0
- package/dist/components/ui/app-data-grid/parts/grid-group-row.js +135 -0
- package/dist/components/ui/app-data-grid/parts/grid-header.js +338 -0
- package/dist/components/ui/app-data-grid/parts/grid-pagination-control.js +218 -0
- package/dist/components/ui/app-data-grid/parts/grid-row.js +181 -0
- package/dist/components/ui/app-data-grid/parts/grid-scroll-area.js +186 -0
- package/dist/components/ui/app-data-grid/parts/grid-selection-bar.js +195 -0
- package/dist/components/ui/app-data-grid/parts/grid-status-surface.js +66 -0
- package/dist/components/ui/app-data-grid/parts/grid-table.js +482 -0
- package/dist/components/ui/app-data-grid/parts/grid-toolbar-slots.js +192 -0
- package/dist/components/ui/app-data-grid/use-grid-filters.js +173 -0
- package/dist/components/ui/app-select.js +332 -222
- package/dist/components/ui/app-sidebar-mobile.js +220 -0
- package/dist/components/ui/app-sidebar.js +366 -109
- package/dist/components/ui/button.js +2 -1
- package/dist/components/ui/sheet.js +26 -24
- package/dist/components/ui/sidebar.js +190 -166
- package/dist/components/ui/tables/data-cross-table/data-cross-table.js +6 -6
- package/dist/components/ui/tables/data-table/data-table-constants.js +36 -16
- package/dist/components/ui/tables/data-table/data-table-i18n.js +135 -1
- package/dist/css-for-template.css +29 -2
- package/dist/index.d.ts +5430 -65
- package/dist/index.js +475 -406
- package/dist/styles.css +2 -1
- package/dist/styles.v3.css +1 -1
- package/package.json +27 -8
- /package/dist/agent-docs/components/{truncated-cell.md → TruncatedCell.md} +0 -0
package/dist/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,290 @@ All notable technical changes to the laif-ds core will be documented in this fil
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### 🚀 Added
|
|
11
|
+
|
|
12
|
+
- **AppDataGrid**: Nuova story `AppDataGrid/Completa → ScenaDipinta` — le **cinque personalizzazioni del chiamante accese nello stesso posto**, che il repo esponeva già una per una e non mostrava mai insieme: **header in HTML custom** (le due intestazioni di gruppo e quella foglia di «Stato», via `header: ReactNode` + `headerLabel`), **celle in HTML custom** (`cell` — avatar con le iniziali derivate dalla ragione sociale, e la barra di avanzamento), **celle colorate** (`cellClassName` a funzione, una tinta per ciascuno dei quattro stati), **righe colorate** (`rowClassName` a funzione, sul settore) e **colonne colorate** (`headerClassName` + `cellClassName` statica sulle due superfici di «Totale»). **Nessuna prop nuova**: sono cinque canali che il contratto ha già, e la story è una dimostrazione, non un allargamento di superficie. ⚠️ **UNA COLONNA PER DIMOSTRAZIONE, e non è pignoleria**: tinta di colonna e tinta per valore sulla stessa colonna sarebbero due `bg-*` nello stesso `cn()` e `tailwind-merge` ne terrebbe **una**, quindi la scena mostrerebbe quattro cose dichiarandone cinque e nessuna play potrebbe dire quale ha vinto — da qui la mappa, che è anche l'ordine delle **sei** colonne: `Commessa` (110) in chiaro con `rowHeader`, `Cliente` (170) cella custom, `Referente` (130) in chiaro, `Stato` (150) header custom **e** celle colorate, `Avanzamento` (150) cella custom, `Totale` (120) colonna colorata. ⚠️ **Header custom e tinta per valore CONVIVONO sulla stessa colonna** e la regola non lo vieta: quella riguarda due `bg-*` nello stesso `cn()`, non un `<th>` e dei `<td>` — sono due elementi, due prop, e due asserzioni separate. ⚠️ **`Referente` non è riempitivo**: è l'unica colonna senza tinta propria, cioè l'**unico** posto da cui si vede il fondo della **riga** — ed è per questo che la play misura lì il colore risolto. ⚠️ **SEI COLONNE PER 830px, E IL NUMERO È MISURATO**: con barra laterale e pannello addon aperti — come la story si apre — il viewport della griglia misura **850px** di `clientWidth`. La prima stesura aveva sette colonne per **1120px** dichiarati, onorati esatti dal `<colgroup>`, e proprio per questo `Totale` e `Consegna` cadevano **fuori**: la dimostrazione della colonna colorata si vedeva solo scorrendo, e una vetrina che chiede di scorrere per vedere una delle cinque cose che dichiara non le sta mostrando. Da qui la somma sotto la misura e `Consegna` — la seconda linea di base, la meno portante — via, verificato in Chrome: nessun contenitore scrollabile in orizzontale e il bordo destro di `Totale` dentro quello della griglia. ⚠️ **«In pausa» è `slate-200` e non `zinc-100`**, per una ragione che sta a schermo e non nei numeri: `zinc-100` è misurabilmente diverso da `amber-50` — l'asserzione sulle quattro stringhe distinte passava — ma accanto alla tinta di riga si legge come la stessa carta crema, cioè due tinte distinte che sembravano una. ⚠️ **NIENTE COLONNE PINNATE E NIENTE `checkable` in questa scena**, per due limiti già misurati e già scritti nel repo: lo sfondo opaco di una cella pinnata copre un `bg-*` che arrivi da `rowClassName` (lacuna dichiarata in `AppDataGrid.md` e nel commento di `parts/grid-row.tsx`), e la tinta della selezione è uno stile **inline** su ogni cella, che vince su qualunque utilità (`adg-r19`) — accendere l'una o l'altra renderebbe ambigua la dimostrazione proprio nel punto che deve dimostrare. ⚠️ **LA PRECEDENZA FRA LE TRE TINTE È MISURATA SUL COLORE RISOLTO**, non dedotta dalla cascata: su una riga tinta la play legge `getComputedStyle().backgroundColor` della cella di `Totale` e di quella di `Referente` e asserisce che sono **diversi**, e che la seconda è `rgba(0, 0, 0, 0)` — cioè che la cella in chiaro lascia passare il fondo della `<tr>` invece di ridipingerlo. La sonda è scritta **nel verso giusto**: se Tailwind non emettesse le classi della story i due colori sarebbero uguali e l'asserzione andrebbe **rossa**, non verde. ⚠️ **SEI FALSIFICAZIONI ESEGUITE**, ognuna riportata al verde subito dopo e ognuna rossa sul criterio suo: togliere `cellClassName` dalla colonna colorata (`expected +0 to be 12`); sostituire la tinta di colonna con una classe che Tailwind **non può emettere** — che lascia **verde** il criterio sulle classi e rossa la sonda del colore (`expected 'rgba(0, 0, 0, 0)' not to be 'rgba(0, 0, 0, 0)'`), cioè prova che quella sonda misura il CSS e non il markup; azzerare `rowClassName`; togliere il marcatore dell'header foglia; togliere quello dell'header di gruppo; e azzerare la tinta per valore (`expected 'truncate px-3 text-left' to contain 'bg-emerald-100 text-emerald-900'`). ⚠️ **La play non scrive numeri a mano**: il conteggio atteso delle celle custom è `dataRows(grid).length` letto dal DOM, e stato e settore di ogni riga si riagganciano ai dati per `data-grid-row` (da qui `getRowId={(riga) => riga.id}`, il codice e non l'indice), con `rigaDipintaDi` che **solleva** se una riga del DOM non si ritrova nei dati invece di misurare la riga sbagliata in silenzio
|
|
13
|
+
|
|
14
|
+
- **AppDataGrid**: New story `AppDataGrid/Completa → EtichettaDiGruppoNellaColonnaBloccata` — the signature scene with `groupLabelPlacement="first-column"`, the **only grouping prop of the contract the signature scene never passed at all**. It is not a duplicate of the three `AppDataGrid/Raggruppamento (P9)` stories that already cover the prop: those run on synthetic datasets **without `checkable`** (an exclusion declared there for an arithmetic constraint — with the checkbox column `aria-colcount` would be 15, not 14), without a `<tfoot>` and without the three-level header tree, so the composition the mockup actually shows — the label in the first **pinned** column, beside the checkbox cell also pinned at `left: 0` — was measured nowhere. It is the exact complement of `Raggruppata`: there `settore` carries the label and `commessa` is empty, here the inverse, and either play alone would stay green on a component that always puts the label in the same column. It also asserts the label MOVED rather than duplicated (label, toggle and count count 1 each in the group row and 1 each inside the `commessa` cell), that no column was hidden (`provaLAlberoDelleColonne` — fourteen leaves, three levels), and that the toggle still WORKS from there while the `<tfoot>` does not move. ⚠️ **Falsification executed**: dropping `etichettaDiGruppo="first-column"` from the render turns `npm run spec -- app-data-grid-completa` red on **this story only**, with «expected '' to contain 'Edilizia'». ⚠️ **And it retires a FALSE sentence** that stood in `Raggruppata`'s play — «the only way to move it would be to hide the grouping column: the rendered leaves would drop from fourteen to thirteen». That has not been true since `groupLabelPlacement` existed: the component rewrites only `group.columnId` and leaves `engine.groupByColumnIds` intact, so no column is hidden
|
|
15
|
+
- **AppDataGrid**: `GridDensityControl` and `GridDensityControlProps` are now **public exports** of `src/components/ui/app-data-grid/index.ts`. It is the same control the toolbar used to render, unchanged: a `role="group"` with the three i18n labels and the `sr-only` full words. It exists as public surface because `adg-c12` removed the grid's own switcher — without this export the file would have had no importer left (dead code) and every consumer wanting the switch would have hand-rolled three buttons, losing the group semantics and the labels. Mount it in `toolbar.actions` to put it back where it was, or anywhere outside the table. ⚠️ **If you also pass a fixed `viewportHeight`, derive it from the LIVE density**: `gridViewportHeight` multiplies `gridRowHeight[density]` by the row count, so a constant `"default"` beside rows rendered taller promises ten rows and fits eight, and the `<tfoot>` covers rows the paginator announces — a mistake that was not possible while the control lived inside the grid
|
|
16
|
+
- **AppDataGrid**: `AppDataGrid/Completa → ModalitaFiltri`, the scene the delivery is signed on, now turns on the mockup capabilities that were off while a prop already governed them, and **marks in a dashed orange frame every piece the library does not render** — in three declared provenances, asserted BOTH ways so a frame in the wrong category goes red rather than merely colouring wrong: `controllo` (outside `[data-app-data-grid]`: the deviations note, the filter-mode switch, the body-state switch, the chip row, the density control), `slot` (inside it, but consumer content: `toolbar.actions`, `selection.summary`) and `sonda` (the two `data-scena-*` probes, which were empty zero-height `div`s — their attributes existed and their box did not). Turned on: the **visible status row** with its megaphone (`statusRowVisible`, deviation 20 of the plan, which was `aperto` — closed by one prop, with no component change, since the wrapper was already implemented and the scene simply did not pass it); the **applied-filters chip row** (`GridFilterChips`, deviation 10 — mounted as a framed sibling because there is no slot between the toolbar and the table band, which SHOWS the deviation without closing it: whether the grid or the consumer should render it stays a product decision); and the **four body states** of the mockup's single shell — ready / empty / error with a wired «Riprova» / loading. ⚠️ **The frames carry no background and no `dark:` variant, and that is a measurement**: the first draft used `bg-orange-50/50 dark:bg-orange-950/20`, and in Tailwind v4 `dark:` is a `prefers-color-scheme` media query, not the class Storybook's theme selector toggles — with the Storybook theme on LIGHT and the OS on dark the frame received `oklab(0.266 … / 0.2)`, a dark brown wash over a `rgb(250, 250, 250)` body. Verified after the fix with the real theme switcher (page background `rgb(250,250,250)` → `rgb(10,10,10)`): one single colour triple across all frames in both themes
|
|
17
|
+
- **AppDataGrid**: New data grid, exported from `src/components/organisms/index.ts` together with `AppDataGridProps` — native `<table>` semantics with single and multi-column sorting (capped by `maxSortedColumns`), column widths with drag resize and left/right pinning over a measured sticky band, multi-level headers with a derived `id`/`headers` chain, opt-in virtualization, an infinite source with skeleton rows, client-side row grouping with per-column `aggregate` totals rendered in `<tfoot>`, density and full screen, page-scope selection with bulk actions that carry the count in their name, saved views applied in one tick through `preset`/`onPresetApplied`, and filters with two homes — `filterMode: "panel" | "row"`, default `"panel"` — fed by the one badge constructor, so the overlay inventory and the zero row produce the same state; the fifteen requirements behind it are tracked in one place, each next to the command that measures it, in the «Registro dei requisiti» section of `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`, and six of them carry a signed limit, in three different shapes. **Three are partial**: R6 ships the always-reachable horizontal scrollbar but no arrow controls (`grep -c 'scrollLeft' src/components/ui/app-data-grid/parts/grid-scroll-area.tsx` returns 0), R12 ships multi-level headers but not moving or grouping columns from the header, and R14 has no prop at all (open-at-first-paint is a story default, not a contract). **Two ship in a different shape than the wording asked for**: R8 as a port — `GridViewStore` plus `createInMemoryGridViewStore`, no persistence and no server adapter — and R9 as a `Collapsible` rather than a `DropdownMenu`. **And one is half-closable here by nature**: the editorial half of R3 — deciding which filters deserve to live outside the table, scene by scene — is a choice of whoever mounts the grid, so the design system makes it possible without being able to take it. The same six are listed in the «I SEI LIMITI FIRMATI» block at the top of `src/components/ui/app-data-grid/app-data-grid.tsx`
|
|
18
|
+
- **AppDataGrid**: New Storybook story `AppDataGrid/Completa` (`src/components/stories/app-data-grid-completa.stories.tsx`) replays the mockup scene with the real engine. **Fourteen leaf columns** over three header levels — twelve of data (Commessa, Ragione sociale, Referente, Settore, Stato, Avanzamento, Q1, Q2, Q3, Q4, Totale, Consegna) plus the two service columns, the checkbox column and the row menu — measured on the rendered table as `aria-colcount="14"` in both scenes, the same count the mockup makes in `COMPLETE_LEAF_COLUMNS`, where `sel` and `azioni` are leaves in their own right. Five cell renderers declared through `cell`: status badges, an initials avatar **derived** from the referent (in the mockup the initials were a field of the record), a progress bar with its percentage, `it-IT` amounts and `gg/mm/aaaa` dates — none of which the grid renders on its own, all of which are the call-site's, and all of which the previous shape of this story was missing. Group rows whose aggregates come from `aggregatedCell` and a `<tfoot>` whose row count and average come from `aggregate`; single-level grouping on the sector; the `columns` and `actions` toolbar slots («Colonne», «Esporta»); the search copy overridden per story by passing a whole `i18n` object; a selection bar with three actions and its own `summary` render; and a mute row-menu column (`header: ""` plus `headerLabel: "Azioni"`) — every piece coming from props and none from a story shell. It is split into two exports, `ModalitaFiltri` (paginated, no groups) and `Raggruppata` (grouped, no pager), because `paginated` does not exist on the type branch that accepts `groupBy` (`grep -n 'paginated?: never' src/components/ui/app-data-grid/app-data-grid.types.ts` returns 982); an on-screen note (`data-testid="nota-scostamenti"`) names what the target will not have — the mockup's «Seleziona tutte le 128 righe» link, since selection is page-scope only and promotion to the whole result set is not even writable (`GridSelectionSlots.mode` is a one-member union), the two filter forms left out by decision 9 («frase in prosa» and «domande pronte»), that split scene, and the group label that falls outside the pinned column — plus deviation 12, which is not an absence: the scene starts with zero applied filters where the mockup starts with six and a «Filtri (6)» badge, a bench choice made so that every count in the scene stays derivable from the corpus. The deviations from the mockup are tabulated one by one, each with its proposed outcome, in the «Scostamenti dal mockup» section of `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`: **thirty-seven rows, twenty-two `accettato`, seven `aperto`, seven closed by a task and one half-closed** — counts DERIVED from the table by a command, not typed by hand: `awk -F'|' '/^\| # \| Scostamento \|/{t=1;next} t&&!/^\|/{t=0} t&&/^\| *[0-9]+ *\|/{e=$(NF-1);gsub(/^ +| +$|`/,"",e);c[e]++;n++} END{print n; for(k in c) print k, c[k]}' docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`
|
|
19
|
+
- **AppDataGrid**: ⚠️ **What the remounted scene does NOT close, and nothing here claims it does.** **Seven** of the **thirty-seven** tabulated deviations stay `aperto` — rows **10, 13, 14, 20, 22, 27, 37** of the table in `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`. ⚠️ **The numbers and the row list are DERIVED from that table by a command, never typed**, because a previous wording of this entry enumerated ten items, called them nine, put an `accettato` row among them and dropped an `aperto` one: `awk -F'|' '/^\| # \| Scostamento \|/{t=1;next} t&&!/^\|/{t=0} t&&/^\| *[0-9]+ *\|/{n=$2;gsub(/ /,"",n);e=$(NF-1);gsub(/^ +| +$|`/,"",e);tot++;if(e=="aperto")printf "%s ",n} END{printf "\\nof %d\\n",tot}' docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`. The split is **five of the component** (13, 14, 20, 22, 27), **one of governance** (10, the chip row) and **one of toolbar composition** (37, opened by `adg-r14`: with the «N filters out of view» indicator gone the row still wraps, by **7px**). ⚠️ **The prose below describes an earlier, larger set** — rows 15, 17, 19, 24, 25 and 34 have since been closed by `adg-m1`/`adg-m2`/`adg-m3`/`adg-m6`, and the table is the authority, not this paragraph. **The component ones still open**: the group label falls in the grouping column instead of the pinned one and reads «Edilizia 78 righe» rather than «Edilizia (78)» (two rows, placement and form); «Schermo intero» has no visible label, only an `aria-label`; the search keeps the primitive's stacked geometry — label above, full-width field, no inline lens — and only its **copy** was closed; the scope figure «Righe 1-10 di 128» is not rendered, and — this is the correction that matters — it is **not** a prop the story forgot: `resultTotal`exists only on`GridClientProps` (`app-data-grid.types.ts:1088`), the branch that declares `paginated?: never`(line 982), so passing it next to`paginated`is`error TS2322`, measured with `tsc --noEmit`on the real file; the status line is`sr-only`; the selection bar's «N righe selezionate su M in pagina» is a fixed template literal that `summary`cannot replace; the two trailing skeleton rows do not exist, and chasing them with`loading`would delete every data row; the viewport is **64px shorter than its content** with three header levels (measured on the rendered scene:`scrollHeight - clientHeight`= 64 = 2 ×`designTokens.table.headerHeight`), because `gridViewportHeight`counts one header level and`GridViewportHeightInput` has no input for how many there are — the consumer-composed viewport height of Fase 9 is still a pact the grid does not keep for you; the order of the toolbar slots is fixed in JSX; and the paginated grid announces **`aria-rowcount="10"`** over 128 rows where the mockup announces 133, which the component's own source already records as a defect of its own and which the story cannot fix for the same TS2322 reason. **The one governance row** is the chip row §11.3-A, which no signed decision covers and which the mockup itself asks to renegotiate — it had disappeared from this entry entirely. **The one story row** is that the scene opens with every `aria-sort`at`none`where the mockup opens sorted by Settore then Totale; it stays open because nobody has written *why*, and the decision comes before the code. ⚠️ Two of the thirteen — the toolbar order and the missing initial sort — carry **no written reason anywhere**, and the table records that instead of inventing one. ⚠️ The filter trigger reading «Filtri» with no count is **not** in this list: it is row 18 and it is`accettato`, with its reason in the table
|
|
20
|
+
- **AppDataGrid**: ⚠️ **NO LONGER TRUE as of the AUDIT 2026-09-09 fixes below — kept here because a consumer who read it needs to know it changed.** The whole lane surface now leaves the package barrel: `src/components/organisms/index.ts` re-exports **all 114** names of `src/components/ui/app-data-grid/index.ts` (verified with the TypeScript AST, not a grep: 0 missing, 0 collisions), so every prop listed below can now be annotated and not merely passed. What follows is the original entry, left for the record — Part of the grid's surface was reachable only from `src/components/ui/app-data-grid/index.ts` and not yet from the package barrel — `grep -c '<name>' src/components/organisms/index.ts` returns 0 for `GridColumnAggregate`, `GridFilterMode`, `GridFilterType`, `GridSelectionSlots`, `GridSelectionAction`, `GridInitialOpenGroups`, the `GridFilterControl` family (`GridFilterControl`, `GridFilterControlProps`, `GridFilterControlRegistry`) and the two runtime values `createGridCustomFilterBadge` and `GRID_CUSTOM_FILTER_COLUMN_PREFIX`, so the props typed by them (`aggregate`, `filterMode`, `filterType`, `selection`, `filterControls`, `initialOpenGroups`) can be passed but not annotated by a consumer of the package
|
|
21
|
+
|
|
22
|
+
### 🗑️ Removed
|
|
23
|
+
|
|
24
|
+
- **AppDataGrid**: ⚠️ **BREAKING — the density switcher is gone from the grid, and `toolbar.density` is gone from the type.** `AppDataGrid` no longer renders any density control in any configuration: the key was **deleted** from `GridToolbarSlots` rather than moved to the opt-in family, so `toolbar={{ density: {} }}` is now `error TS2353: Object literal may only specify known properties, and 'density' does not exist in type 'GridToolbarSlots'` — measured on `tsc -p tsconfig.probe.json`, not guessed. Row height is set by the `density` prop, **default `"default"` (standard, 36px), unchanged**. ⚠️ **This RETRACTS the P6 rule for that one key**: the «always rendered» family drops from three to two (`search`, `fullscreen`), for which decision 26 still holds in full. The retired argument was «a table whose density changes only if the call-site remembered a boolean is a table whose density, in practice, never changes» — a rule about the end user's experience, imposed on every call-site; density is view configuration, and whoever mounts the table is who knows whether its rows should be tight or roomy. ⚠️ **Two consequences that are not cosmetic.** First, `changeDensity` was the ONLY caller of the `view/density` dispatch, so with the control gone **omitting the `density` prop freezes density at `"default"` for good** — the reducer slice is now written only by `applyPreset`; density is de facto always controlled. Second, `onDensityChange` had no caller left at all, which would have made it a public, documented prop the component never invokes: it is now fired on **preset application** (`onDensityChange?.(preset.view.density)`), a gap that existed before this change and was masked by the toolbar control. ⚠️ **Declared limit, not fixed here:** on a grid with `density` passed, a preset's density cannot win — the render picks the prop — so the preset _announces_ its density without imposing it; who should win is an open product decision. ⚠️ **The type probes were REWRITTEN, not disabled**, and the distinction is the point: `ProbeShellSlotsDensitaBooleano` and `ProbeShellSlotsDensitaRenderVuoto` would have stayed red — hence the gate green — but for the WRONG reason (unknown property instead of incompatible value), i.e. they would have kept printing «N probe su N» while measuring nothing about decision 26. They are replaced by one `ProbeShellSlotsDensitaChiaveInesistente`, which must use an **inline literal** because `GridToolbarSlots` has only optional keys and an extra property arriving from a typed variable stays assignable — excess-property checking applies to fresh literals only. Its negative proof was **executed**: re-adding `density?: GridToolbarAlwaysSlot` turns `scripts/check-grouping-server-unrepresentable.mjs` red on that probe by name, and reverting turns it green (16 probes / 16). The gate itself grew one field for this — the expected type name, defaulting to `"AppDataGridProps"` — because a `TS2353` names `GridToolbarSlots` instead, and the hard-coded string would have reported a falling probe as compiling
|
|
25
|
+
|
|
26
|
+
- **AppDataGrid**: The «N filters out of view» toolbar indicator is **gone**, together with everything that fed it — the `[data-grid-filter-row-offscreen]` button, the per-scroll geometry measurement of the zero row's cells, the reveal arithmetic (`gridFilterRowRevealScrollLeft` and its `GridFilterRowRevealInput`), the `GridFilterRowOffscreenIndicator` component, and **two i18n keys**: `filters.filterRowOffscreen` from `GridI18n` (the optional field, the `Required<Pick<…>>` list of `defaultGridI18n` and its English value) and its Italian counterpart in the stories' `gridI18nIt`. ⚠️ **This is a source-breaking change for anyone who passes a full `i18n` object literal**: an object that still carries `filters.filterRowOffscreen` no longer compiles, because TypeScript's excess-property check rejects an unknown key on an object literal; delete the key, there is no replacement. ⚠️ **And it is a WITHDRAWAL of a signed criterion, not a cleanup** — criterion 7 of Phase 9 is now revoked in both halves (`adg-r13` took the chip band, this change takes the indicator), so constraints scrolled out of the horizontal viewport are no longer named and reaching them is scrolling. The reason is toolbar width: the button cost **110px** on a row already over budget. The revocation, its measurements and what is lost with it — including the story's independent oracle for that criterion — are written up in the «Fase 9» section and in deviation **37** of `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`
|
|
27
|
+
|
|
28
|
+
### 🐛 Fixed
|
|
29
|
+
|
|
30
|
+
- **AppDataGrid — ⚠️ SECONDA PASSATA DEL DEBOUNCE: ORA ASPETTA ANCHE IL FILTRAGGIO, E IN ENTRAMBE LE
|
|
31
|
+
MODALITÀ.** La voce qui sotto aveva coalescito la sola **emissione**: la richiesta al server partiva una
|
|
32
|
+
volta, ma la tabella continuava a rifiltrare **a ogni tasto** — e in modalità **client**, dove nessuna
|
|
33
|
+
richiesta esiste, il ritardo non comprava niente. Misurato su una griglia client: battendo in una cella di
|
|
34
|
+
filtro le righe passavano da 10 a 0 **entro 60ms**. Ora la griglia tiene **due slice**: quella **viva**, che
|
|
35
|
+
gli input mostrano (la searchbar è controllata da `state.query.search`, gli editor leggono
|
|
36
|
+
`state.query.filters`), e quella **confermata**, che filtra le righe e viene emessa — commit ed emissione
|
|
37
|
+
sono **lo stesso evento**, così la tabella comincia a filtrare con lo stesso scatto di query che il consumer
|
|
38
|
+
riceve. Misurato dopo: sei battute lasciano il conteggio righe **fermo** (un solo valore distinto su sei
|
|
39
|
+
campioni) e l'input mostra già il testo intero; alla chiusura della finestra le righe si muovono una volta.
|
|
40
|
+
⚠️ **TanStack riceve lo scatto INTERO** — ordinamento, filtro globale e paginazione insieme — perché
|
|
41
|
+
`query/search` e `query/filters` azzerano `pageIndex` e un misto mostrerebbe la pagina 0 dell'insieme non
|
|
42
|
+
filtrato per un frame. ⚠️ **NUOVA PROP `debounceTime` SUL RAMO BASE** (default 200, `0` disattiva), perché
|
|
43
|
+
`serverConfig.serverDebounceTime` vive sul solo ramo server e una griglia client ora ne ha bisogno; con
|
|
44
|
+
entrambe vince `debounceTime`. ⚠️ **`useGridFilters` ESPONE `committedBadges`**, additivo: si passa `badges`
|
|
45
|
+
alla griglia (gli editor devono restare vivi) e si filtra con `committedBadges`. Chi resta su `badges` non
|
|
46
|
+
si rompe — semplicemente non ottiene il differimento, ed è il modo di fallire che è stato **scelto**: rendere
|
|
47
|
+
`badges` il valore confermato avrebbe dato il differimento a tutti senza toccare una riga, ma avrebbe fatto
|
|
48
|
+
**scattare la digitazione in produzione** a chi non passasse la bozza agli editor. ⚠️ **E DENTRO IL HOOK LA
|
|
49
|
+
DIVISIONE È PER GESTO, non per nome del mutatore** — `updateBadge` (un valore che si digita o si trascina) è
|
|
50
|
+
continuo, `addBadge` è **discreto**, perché è anche il modo in cui un badge nasce da uno switch, da un
|
|
51
|
+
`AppSelect` o da un chip: la prima stesura lo aveva classificato continuo e, misurato, lo switch di
|
|
52
|
+
`FiltriSoloFuori` non annunciava più il proprio filtro entro il frame. Il prezzo dichiarato è che un filtro
|
|
53
|
+
di **testo nuovo** conferma sul primo carattere e coalesce il resto — due conferme per parola invece di una;
|
|
54
|
+
la ricerca globale, che non passa dal hook, resta a una. **Quattro spec riscritte** una per una sul contratto
|
|
55
|
+
nuovo, nessuna allentata (`filters-lifted` — che dichiarava a parole «il debounce è affare del consumer» —,
|
|
56
|
+
`riga-zero`, `completa/FiltriSoloFuori` e `gruppi-iniziali`, dove «una battuta, una emissione» diventa un
|
|
57
|
+
delta esatto di 1 e la prova sulla risemina si affila), più **due story nuove** che misurano il caso client
|
|
58
|
+
e la sua via d'uscita
|
|
59
|
+
- **AppDataGrid — ⚠️ CAMBIO DI COMPORTAMENTO PER OGNI CONSUMER IN MODALITÀ SERVER, anche chi non passa
|
|
60
|
+
niente.** `serverConfig.serverDebounceTime` **è implementata**, con **default 200ms**, e le due azioni
|
|
61
|
+
continue — `query/search` e `query/filters` — vengono **coalescite**: una parola di sei lettere nella
|
|
62
|
+
searchbar è ora **una** chiamata a `onQueryChange` invece di sei, col payload finale. Fino a oggi quel
|
|
63
|
+
campo era dichiarato nel tipo, il suo TSDoc diceva «NON IMPLEMENTATO» e **nessuna riga lo leggeva**: chi lo
|
|
64
|
+
passava otteneva silenziosamente niente. Misurato nel browser prima e dopo, non dedotto: sei battute
|
|
65
|
+
ravvicinate danno `data-emissions` fermo durante la raffica e **+1** allo scadere della finestra, mentre con
|
|
66
|
+
`serverDebounceTime: 0` — che è lo stesso ramo sincrono di prima — le stesse sei battute danno **+6**.
|
|
67
|
+
⚠️ **SOLO L'EMISSIONE È DIFFERITA, MAI LO STATO**: la tabella si aggiorna sulla battuta (la searchbar è
|
|
68
|
+
controllata da `state.query.search`, e in modalità client il filtro locale resta istantaneo), quindi il
|
|
69
|
+
campo compra meno richieste e non una tabella più lenta — misurato leggendo il valore dell'`<input>` nello
|
|
70
|
+
stesso istante in cui il contatore è ancora a zero. ⚠️ **LE AZIONI DISCRETE EMETTONO SUBITO E ANNULLANO LA
|
|
71
|
+
COALESCENZA IN SOSPESO** invece di accodarsi (ordinamento, pagina, dimensione pagina, load-more, preset):
|
|
72
|
+
altrimenti un gesto metterebbe **due** richieste in volo, che è il difetto che il debounce dovrebbe ridurre
|
|
73
|
+
— misurato strumentando `setTimeout`/`clearTimeout`, il timer della battuta risulta cancellato e mai
|
|
74
|
+
scaduto e il contatore sale di uno al click, portando **dentro** la ricerca battuta nel frattempo.
|
|
75
|
+
⚠️ **RITARDO ZERO SENZA EMETTITORE**: il ramo client dichiara `onQueryChange?: never`, quindi in client non
|
|
76
|
+
c'è niente da coalescere e il numero di emissioni per gesto **non cambia** (misurato: tre battute, tre
|
|
77
|
+
emissioni immediate). ⚠️ **DUE LIMITI DICHIARATI**: coalescere riduce le richieste ma **non ne sequenzia le
|
|
78
|
+
risposte**, che restano affare del consumer; e con i filtri **SOLLEVATI** il canale è `onFiltersChange`, la
|
|
79
|
+
callback del consumer chiamata in modo sincrono dentro l'azione, che **non** passa da `applyAndEmit` e
|
|
80
|
+
quindi resta una chiamata per tasto. ⚠️ **E LA PROP È OMONIMA MA NON GEMELLA** di quella del `DataTable`
|
|
81
|
+
congelato, dove (default 750) ritarda solo la comparsa dell'overlay di caricamento e non coalesce nessuna
|
|
82
|
+
richiesta — `DataTable.md` afferma il contrario ed è falso su quel componente. **Cinque story nuove** in
|
|
83
|
+
`AppDataGrid/Emettitore (P2)`: la searchbar, il default, l'azione discreta, `0`, e la **riga dei filtri** —
|
|
84
|
+
quest'ultima perché ricerca e filtri sono due rami diversi di `isContinuousQueryAction` e una correzione
|
|
85
|
+
che ne coprisse uno solo resterebbe verde su tutto il resto. La falsificazione è stata eseguita sul regime
|
|
86
|
+
a ritardo zero, che è byte-per-byte il ramo di prima: là le due asserzioni discriminanti leggono 6 invece
|
|
87
|
+
di 0 e 1
|
|
88
|
+
- **AppDataGrid (story, non il componente)**: nella scena `AppDataGrid/Completa → DueCaricamenti` il server
|
|
89
|
+
finto **risponde dopo mezzo secondo** invece che nello stesso commit, quindi il caricamento si può
|
|
90
|
+
finalmente guardare. ⚠️ **Nessuna riga del componente cambia**: è il banco della story. Il difetto era che
|
|
91
|
+
la scena aveva due regimi e in **nessuno dei due** un caricamento durava un tempo osservabile — con lo
|
|
92
|
+
switch «Blocca le risposte» attivo non finisce mai, e con lo switch spento rispondeva **sincrono**
|
|
93
|
+
(`setCaricaInfinita(true)` e la consegna nello stesso gesto), quindi gli scheletri nascevano e morivano
|
|
94
|
+
nello stesso commit. Misurato nel browser sulla story viva, non dedotto dalla costante: **615ms** di
|
|
95
|
+
scheletri sulla griglia infinita (dieci scheletri accodati, righe 40 → 50) e **514ms** sulla paginata (dieci
|
|
96
|
+
scheletri, righe a **0** durante l'attesa — la semantica firmata «accoda contro sostituisce» resta intatta).
|
|
97
|
+
Congelare a metà volo **annulla** la risposta in corso invece di lasciarla arrivare a switch chiuso
|
|
98
|
+
(misurato: scheletri ancora a schermo dopo 1500ms, cioè tre volte il ritardo), e nessun timer sopravvive
|
|
99
|
+
allo smontaggio (misurato strumentando `setTimeout`/`clearTimeout` e facendo cambiare story a Storybook nel
|
|
100
|
+
medesimo documento: il timer pendente risulta cancellato e mai scaduto). ⚠️ **REVOCA metà di una frase
|
|
101
|
+
firmata nel docblock della scena** — «non è un `delayMs`: un ritardo rende la play una gara coi tempi,
|
|
102
|
+
mentre un server che non risponde è uno stato stabile che si misura». La seconda metà vale ancora, ed è il
|
|
103
|
+
motivo per cui il congelamento resta e per cui le due misure firmate si prendono tutte in quel regime; la
|
|
104
|
+
prima era una conclusione troppo larga. Il ritardo attraversa la **sola** fase 3 della play (lo sblocco),
|
|
105
|
+
dove le attese portano ora un `timeout` dichiarato, derivato dalla costante con un margine di 6×, invece
|
|
106
|
+
del default di `waitFor`
|
|
107
|
+
- **AppDataGrid**: con più di una riga di intestazione, **fra le righe dell'header non passa più un pixel
|
|
108
|
+
del corpo che scorre**. Segnalato con uno screenshot e riprodotto prima di essere corretto. Le righe di
|
|
109
|
+
header intermedie portavano `border-d-border border-b` sulla `<tr>`: sotto `border-collapse: collapse`
|
|
110
|
+
quel bordo viene collassato nella griglia dei bordi della **tabella** — che non è sticky — quindi scorre
|
|
111
|
+
via col corpo, e la striscia da 1px che occupava non è coperta da niente, né dalla riga sopra, né da
|
|
112
|
+
quella sotto, né dal fondo del `<thead>`. ⚠️ **Misurato, non dedotto** (Chrome, `devicePixelRatio` 2,
|
|
113
|
+
scena `AppDataGrid/Header multilivello (P5) → Manuale`, `scrollTop: 137.5`, corpo dipinto `#ff0000`,
|
|
114
|
+
lettura del pixel sul PNG del compositor): **670 campioni rossi su 888 a ciascuno dei due confini interni
|
|
115
|
+
del `<thead>`** e zero dentro le righe; dopo il fix, **zero in tutto il blocco**. Un fondo sul `<thead>`
|
|
116
|
+
non è il rimedio, ed è misurato anche questo: con `thead { background: #00ff00 !important }` il verde non
|
|
117
|
+
appare **mai** al confine, che resta rosso — il fondo di un row-group non copre quella striscia. Il filo
|
|
118
|
+
fra due livelli è ora `box-shadow: inset 0 -1px 0 0 var(--d-border)` (`gridHeaderRowSeparatorShadow`), la
|
|
119
|
+
stessa forma con cui la Fase 2 aveva già risolto la separazione header→corpo e quella del footer, e vive
|
|
120
|
+
in **due** posti per necessità: sulla `<tr>`, l'unico elemento che si estende su tutta la larghezza della
|
|
121
|
+
tabella colonna fantasma inclusa, e — composto attraverso `gridPinnedOffsetStyle`, mai come una seconda
|
|
122
|
+
dichiarazione — sulle celle **opache** di quelle righe, perché il `bg-d-background` di una cella pinnata
|
|
123
|
+
dipinge sopra l'ombra della riga (misurato: con la sola ombra della `<tr>` il filo correva da css x 73
|
|
124
|
+
invece che da 25). ⚠️ **Effetto collaterale visibile nel contratto delle sonde**: una cella-etichetta di
|
|
125
|
+
gruppo pinnata di una riga intermedia porta ora **un `box-shadow` layer in più** (4 invece di 3 a
|
|
126
|
+
sinistra, 3 invece di 2 a destra), con la geometria nuova `0px -1px 0px 0px inset` — le due spec che
|
|
127
|
+
contano gli strati sono state aggiornate nominando il quarto, non allentando il conteggio. ⚠️ **E chiude
|
|
128
|
+
una domanda che il repo dichiarava aperta per iscritto** (`parts/grid-filter-row.tsx`): un `box-shadow` su
|
|
129
|
+
una `<tr>` sotto `border-collapse` **si rende**. Story di regressione:
|
|
130
|
+
`AppDataGrid/Header multilivello (P5) → Fra due righe di header non passa il corpo che scorre`, i cui tre
|
|
131
|
+
predicati sono stati falsificati contro il regime pre-correzione (bordi `1px`, ombra di riga `none`, e le
|
|
132
|
+
due celle-segnaposto delle spunte senza filo)
|
|
133
|
+
|
|
134
|
+
> **AUDIT 2026-09-09.** Le voci di questo blocco chiudono i rilievi confermati dell'audit di
|
|
135
|
+
> `AppDataGrid` (`docs/analysis/datatable-v2/AUDIT-BUG-2026-09-09.md`: 8 corsie di lettura, 4
|
|
136
|
+
> verificatori avversari, 47 rilievi confermati su 53). ⚠️ **Il contesto che le rende leggibili:** la
|
|
137
|
+
> suite era **verde** prima dell'audit — 730 test su 102 file — quindi nessuno di questi difetti era
|
|
138
|
+
> una regressione recente: erano zone che nessuna asserzione guardava. Dopo: **765 test su 110 file**
|
|
139
|
+
> (766 da quando la voce qui sopra ha aggiunto la sua story di regressione), e ogni fix ha una story che
|
|
140
|
+
> cade se il fix viene revocato.
|
|
141
|
+
|
|
142
|
+
- **AppDataGrid — ⚠️ BREAKING per chi confronta il payload carattere per carattere.** Il valore di un
|
|
143
|
+
filtro **numerico** parte come `number` e non più come `string` (`{importo: {operator: "gt", value: 100}}`,
|
|
144
|
+
non `"100"`), e il valore di un filtro **data** è composto dai getter LOCALI — `"YYYY-MM-DD"` per
|
|
145
|
+
`date`, `"YYYY-MM-DDTHH:mm:ss"` per `datetime` — invece di `toISOString()`: nessuno `Z`, nessun
|
|
146
|
+
istante UTC.
|
|
147
|
+
⚠️ **DUE PRECISAZIONI, aggiunte dopo la verifica avversaria perché la prima stesura di questa voce
|
|
148
|
+
prometteva più di quanto consegna.** (1) **Il numero vale per l'editor SINGOLO, non per il compositore
|
|
149
|
+
«da…a…»**: `GridFilterRangeSide.value` è tipata `string` in `app-data-grid.types.ts`, quindi un
|
|
150
|
+
intervallo numerico spedisce ancora due stringhe. Sono due forme dello stesso valore nello stesso
|
|
151
|
+
modulo, ed è un debito aperto: allargare quel tipo cambia il contratto di `onApply`, quindi non è stato
|
|
152
|
+
deciso qui. (2) **La forma `datetime` NON è la parità letterale con la tabella congelata**, che appende
|
|
153
|
+
sei decimali — `const microseconds = "000000"` in
|
|
154
|
+
`data-table/components/data-table-filter-inputs.tsx`. La griglia emette `"YYYY-MM-DDTHH:mm:ss"` senza
|
|
155
|
+
di essi. Il difetto corretto è il **fuso** — non c'è più uno `Z` né un giorno sbagliato — non la
|
|
156
|
+
coincidenza carattere per carattere, e un backend che facesse parsing rigido dei microsecondi va
|
|
157
|
+
verificato. Erano entrambi scarti dalla parità col payload della `DataTable` congelata che il
|
|
158
|
+
documento promette. Nel verso opposto, un valore di sola data nella forma che la congelata produce
|
|
159
|
+
(`"2026-09-09"`) viene riletto come mezzanotte **locale** e non più UTC, così il picker non mostra
|
|
160
|
+
più il giorno prima. Il campo numerico svuotato emette `undefined` e non `0`: `Number("")` avrebbe
|
|
161
|
+
reso una cancellazione un filtro «maggiore di 0» che nessuno ha scritto
|
|
162
|
+
- **AppDataGrid**: il chip di un filtro **booleano** porta il valore tradotto — «Chiusa Uguale a: Vero»
|
|
163
|
+
/ «: Falso» — dove prima «è vero» perdeva il valore (il chip era testualmente indistinguibile da un
|
|
164
|
+
badge senza valore) e «è falso» stampava il letterale non tradotto. Gli operatori senza valore si
|
|
165
|
+
riconoscono ora dall'**operatore** (`GRID_VALUELESS_OPERATORS`, la stessa sorgente che usa
|
|
166
|
+
`grid-filter-compile`) e non dal valore, quindi «è vuoto» resta nudo
|
|
167
|
+
- **AppDataGrid**: l'editor a lista non fa più cadere il select sul segnaposto quando il valore del
|
|
168
|
+
badge non è più fra le opzioni della colonna: rende un'`<option>` sintetica marcata
|
|
169
|
+
`data-grid-filter-editor-unknown` — **nuovo aggancio DOM** — così un filtro attivo resta visibile
|
|
170
|
+
invece di sembrare vuoto
|
|
171
|
+
- **AppDataGrid**: la **sorgente infinita** non si congela più quando un filtro viene applicato e poi
|
|
172
|
+
rimosso: la chiave della guardia si azzera a ogni ripartenza, dove prima una firma che TORNAVA a un
|
|
173
|
+
valore già consumato lasciava la lista ferma per sempre. E una griglia infinita montata **senza box
|
|
174
|
+
di layout** (`display:none`, un pannello chiuso) non incatena più l'intera sorgente credendosi a
|
|
175
|
+
fondo lista: non chiede nessuna pagina e la chiede all'apertura del pannello. ⚠️ Conseguenza per il
|
|
176
|
+
consumer: la prima pagina di una griglia dentro un pannello chiuso arriva **all'apertura**, non al
|
|
177
|
+
montaggio. Il ritorno della vista in cima dopo un cambio di query non è più confinato alla modalità
|
|
178
|
+
infinita: vale per ogni griglia, paginata compresa
|
|
179
|
+
- **AppDataGrid**: il **raggruppamento** su una colonna con valori assenti non collassa più i livelli.
|
|
180
|
+
`keyOf(null)` vale `""`, e la guardia del separatore di percorso scattava una seconda volta al
|
|
181
|
+
livello successivo **sostituendo** il percorso invece di concatenarlo: due gruppi diversi si
|
|
182
|
+
fondevano, il conteggio raddoppiava e l'aggregato sommava la stessa riga N volte — un numero
|
|
183
|
+
sbagliato mostrato all'utente. Un gruppo con chiave assente si rende ora con «—» come etichetta
|
|
184
|
+
(la sua chiave resta la stringa vuota), e `initialOpenGroups` riconosce un percorso il cui antenato
|
|
185
|
+
ha la chiave assente. ⚠️ **Limite che resta aperto e va dichiarato:** `null`, `undefined` e la
|
|
186
|
+
stringa vuota condividono la chiave `""`, quindi tre assenze semanticamente diverse confluiscono in
|
|
187
|
+
un solo gruppo. Con `groupLabelPlacement="first-column"` la prima colonna non perde più il proprio
|
|
188
|
+
`aggregatedCell`
|
|
189
|
+
- **AppDataGrid**: in modalità infinita l'annuncio «Altre righe caricate» era diventato **permanente**
|
|
190
|
+
dopo il primo caricamento, coprendo le quattro clausole della riga di stato (selezione, conteggio,
|
|
191
|
+
ordinamento, filtri) nella regione live e — con `statusRowVisible` — anche a schermo. Ora è uno
|
|
192
|
+
scatto: torna alla riga di stato al primo cambio di stato dopo l'atterraggio della pagina
|
|
193
|
+
- **AppDataGrid**: a portata di selezione **promossa** la barra non sparisce più cambiando pagina, così
|
|
194
|
+
il ritorno alla pagina e le azioni di massa restano raggiungibili; e la conferma di un'azione
|
|
195
|
+
distruttiva non sopravvive più allo svuotamento della selezione per riarmarsi alla spunta successiva
|
|
196
|
+
(prima solo un array `selection.actions` RICOSTRUITO a ogni render la annullava: con `useMemo` o una
|
|
197
|
+
costante di modulo restava armata). ⚠️ Il contratto «a selezione vuota la barra non si rende
|
|
198
|
+
affatto» ha ora **una** eccezione dichiarata: con la promozione offerta e la portata `"all"` viva la
|
|
199
|
+
barra resta resa a zero spunte, perché è l'unica superficie che governa quella portata — quindi
|
|
200
|
+
`[data-grid-selection-count]` può valere `0`
|
|
201
|
+
- **AppDataGrid**: `readGridViewPreset` rifiuta un payload v1 **troncato** con
|
|
202
|
+
`GridViewPresetVersionError` invece di validare solo `version` e lasciare esplodere il motore su
|
|
203
|
+
`query.sorting.map` dentro un effetto — cioè una schermata bianca invece di un errore dichiarato
|
|
204
|
+
- **AppDataGrid (a11y)**: una colonna con `header` vuoto e senza `headerLabel` prende il proprio `id`
|
|
205
|
+
come nome accessibile. Prima l'`aria-label` vuoto veniva **saltato** da accname e il nome del
|
|
206
|
+
`columnheader` si ricomponeva dai contenuti del `<th>`, **larghezza della maniglia di resize
|
|
207
|
+
compresa**: il nome accessibile della colonna era un numero di pixel
|
|
208
|
+
- **AppSelect (a11y, WCAG 4.1.2)**: il trigger `role="combobox"` senza `label` ricade sul proprio
|
|
209
|
+
`placeholder` come nome accessibile. La catena è `aria-labelledby` → `aria-label` → `placeholder`,
|
|
210
|
+
**mai il valore corrente**. ⚠️ Perché conta oltre `AppSelect`: i **dieci** `<AppSelect>` che la
|
|
211
|
+
`DataTable` congelata rende non passano né `label` né prop ARIA, e dopo il passaggio del trigger a
|
|
212
|
+
`role="combobox"` + `tabIndex` erano diventati dieci combobox focalizzabili **senza alcun nome**, in
|
|
213
|
+
diciotto repo consumer. La cartella congelata non è modificabile (`check-datatable-freeze.mjs`),
|
|
214
|
+
quindi il ripiego nel primitivo condiviso è l'unico punto raggiungibile e li copre tutti e dieci.
|
|
215
|
+
⚠️ **Chi asseriva `aria-label` assente in quella forma va aggiornato** (è il caso della story
|
|
216
|
+
`SenzaEtichettaNomeAccessibile`, misurata rossa e corretta)
|
|
217
|
+
- **AppDataGrid**: `rowMenu` che ritorna un elenco **vuoto** non rende più il grilletto «⋮», come il
|
|
218
|
+
contratto dichiarato nel tipo diceva già
|
|
219
|
+
- **AppDataGrid**: `initialState.pagination.pageIndex` è **onorato**. `GridPagination` lo dichiara
|
|
220
|
+
obbligatorio, quindi ogni call-site è costretto a scriverlo, e la griglia lo scartava montando
|
|
221
|
+
sempre a pagina 1: il paginatore stampava «1 / N» sulla terza pagina di dati. E il **termine di
|
|
222
|
+
ricerca iniziale** non si perde più: il motore leggeva `initialState.searchbarFilter` al primo
|
|
223
|
+
livello, mentre il builder e la tabella congelata lo scrivono dentro `initialState.filters`
|
|
224
|
+
- **AppDataGrid**: `onFiltersChange` e l'incremento di `emissions` escono ora **fuori** dall'updater
|
|
225
|
+
di `setState`, così il contratto osservabile è «una mutazione = un annuncio» anche sotto
|
|
226
|
+
`StrictMode`, dove prima era doppio
|
|
227
|
+
|
|
228
|
+
### 🐛 Fixed — quello che l'audit ha trovato e che NON è chiuso
|
|
229
|
+
|
|
230
|
+
- **AppDataGrid**: ⚠️ **`serverConfig.serverDebounceTime` è dichiarato nel tipo, promesso dal proprio
|
|
231
|
+
TSDoc («Millisecondi di attesa prima di emettere») e non letto da nessuna riga di codice**:
|
|
232
|
+
l'emettitore è sincrono, quindi sei battute in una searchbar sono sei richieste HTTP senza garanzia
|
|
233
|
+
d'ordine. Non chiuso qui perché le due strade sono decisioni: implementarlo nell'emettitore cambia
|
|
234
|
+
il contratto di `onQueryChange`, e togliere il campo dal tipo è un breaking change per diciotto
|
|
235
|
+
repo. Oggi il debounce lo deve fare il consumer
|
|
236
|
+
- **AppDataGrid**: ⚠️ **con i filtri SOLLEVATI la prima pagina della lista nuova è chiesta col
|
|
237
|
+
contatore della lista vecchia** (misurato: `pageIndex: 3` per un insieme in cui nessuna riga è stata
|
|
238
|
+
consegnata), quindi un consumer che deriva `offset = pageIndex * pageSize` salta le prime pagine del
|
|
239
|
+
risultato filtrato. **Tentato e revocato**, e la ragione va letta prima di ritentare: azzerare
|
|
240
|
+
`requestCount` in `effectiveQuery` fa andare rossa `FiltriSollevatiConSorgenteInfinita` con
|
|
241
|
+
«expected 30 to be 60» — una scena verde nella baseline il cui harness dichiara il disegno opposto,
|
|
242
|
+
cioè che la ripartenza la fa il **consumer** in `onFiltersChange` e la griglia deve poi emettere un
|
|
243
|
+
indice che **avanza**. I due contratti sono incompatibili e la griglia non può distinguerli: chi
|
|
244
|
+
possiede il segnale di ripartenza è una decisione di prodotto. Scritta per intero sul commento di
|
|
245
|
+
`effectiveQuery` in `engine/use-grid-engine.ts`, e inchiodata dalla story
|
|
246
|
+
`DEBITO motore-1 — col chip la pagina chiesta è quella stantia, non la zero`
|
|
247
|
+
- **AppDataGrid**: ⚠️ con `groupBy` + `resultTotal` gli `aria-rowindex` **sfondano** `aria-rowcount`
|
|
248
|
+
(misurato: indici 1..6 contro un totale di 3), perché il conteggio torna a contare le sole righe di
|
|
249
|
+
dati mentre gli indici contano anche le righe di gruppo. Il rimedio è in `app-data-grid.tsx`
|
|
250
|
+
- **AppDataGrid**: ⚠️ tre rilievi in `parts/grid-filter-panel.tsx` restano **non corretti per scelta**:
|
|
251
|
+
il file è nel perimetro congelato e il pannello è deprecato e non montato da nessuna story —
|
|
252
|
+
`adg-r18` ha già registrato che «la sua superficie non va migrata, va lasciata morire»
|
|
253
|
+
- **AppDataGrid**: ⚠️ `check-datatable-shared-surface` resta **rosso**, e non è chiudibile da codice:
|
|
254
|
+
il gate confronta il merge-base con l'albero di lavoro, quindi il rimedio che nomina — separare la
|
|
255
|
+
corsia dai primitivi condivisi — richiede **git**, cioè due commit o due PR
|
|
256
|
+
|
|
257
|
+
### 🔧 Changed
|
|
258
|
+
|
|
259
|
+
- **AppDataGrid**: The **25 «intermediate» story files** — every one except the three showcase files (`AppDataGrid/Mockup`, `AppDataGrid/Completa`, `AppDataGrid/Confronto`) — moved from `title: "AppDataGrid/<name>"` to `title: "CI-CD/AppDataGrid/<name>"`, so the regression specs collect under one `CI-CD` sidebar node and the showcase keeps `AppDataGrid/` to itself. ⚠️ **Each one also pins `id:` in its own meta, and that is the whole point of the change being safe.** A CSF component id is DERIVED from the title (`toId`), so the retitle alone would have moved the URL of every story in those files — and those URLs are cited OUTSIDE the source: 12 files under `.laif/tasks/to-validate` (tasks awaiting human signature, naming the page to open for a criterion not yet signed) and 11 append-only run logs, which are not rewritable, so any id change would leave permanently dead links in the audit trail. `id?: ComponentId` is the documented clause for exactly this (`node_modules/storybook/dist/csf/index.d.ts:876`, «used for URLs. By default is inferred from sanitizing the title»); Storybook in use is 10.0.8. ⚠️ **MEASURED on Storybook's own `/index.json`, before and after — not deduced from the source**: `appdatagrid-*` stories 319 → **319**; ids that disappeared (broken URLs) **0**; unexpected new ids **0**; stories whose title changed at an **unchanged id** **305** (the remaining 14 are the three showcase files); total index 725 → **725**. Citations to intermediate specs that no longer resolve: **2 before, the same 2 after** — `atomo-di-filtro-c7--inventario` and `selezione-c6--barra-di-selezione` were ALREADY dangling before the rename, so the retitle broke nothing. `npm run spec` stays **102 files / 725 tests, 0 failed** (it filters by file path, not by title) and all **14** `scripts/check-*.mjs` gates exit 0 (they read paths and sources; none reads a title). ⚠️ **The id values were COPIED from the index, never retyped**: one of the twenty-five is `appdatagrid-densità-e-schermo-intero-p6`, accent included — retyping it would have normalised the accent and silently broken a URL that works today. ⚠️ **Accepted consequence: title and URL now diverge for good** — a future title rename will silently keep the pinned id, which is why every one of the 25 metas carries the reason inline and points at the «Il nodo CI-CD» section of `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`. ⚠️ **The seven `Manuale` stories move too, and it is stated rather than hidden**: they are the deliberately play-less bench for human task validation, and in CSF a story cannot override its meta's title, so keeping them under `AppDataGrid/` would have meant splitting files — not done. Their URLs are unchanged, so every citation by id still resolves; only their sidebar location moved
|
|
260
|
+
- **AppDataGrid**: ⚠️ **What the reorganisation deliberately did NOT do, after an audit that measured it.** All 25 intermediate files were audited one by one (25 recon passes, then three adversarial skeptics on every proposal to cut) and **none is deletable**: 23 came back «keep» outright and the 2 «reduce» proposals were refuted **3 votes out of 3**. The reason is structural and measurable — 24 of the 25 are the only place in the repo where at least one public prop, DOM selector or exported function is exercised (`infinite`, `filterControls`, `validateGridColumnTree`, `readGridViewPreset`, `th[scope="row"]`, the `data-grid-column-registry-*` family) — and, decisively, **deleting a story turns nothing red**: `npm run check` does not run the plays, and `spec` is chained to neither `check` nor `pr-checks.yml` for two reasons the repo itself already states in `package.json` (`_comment:spec`). The lane has also already paid for one pruning (`dtv2-f1-potatura-e-spacchettamento-story` deleted ten stories and with them the source three later tasks needed). Still open and NOT addressed here: the «show/hide specs» switch (`tags: ["spec"]` plus a `sidebar.filters` filter in `.storybook/manager.ts`), which must go in a separate PR because `scripts/check-datatable-freeze.mjs` scopes lane purity on `NEW_TABLE_PATHS`, which covers the story folder but not `.storybook/`
|
|
261
|
+
- **DataTable**: Deprecated **in the documentation only**, in favour of `AppDataGrid` — the entry is in `src/components/documents/components-list.md` under «Deprecated Components» and nothing is removed: `DataTable` is still exported and every existing call-site keeps compiling, `grep -rn '@deprecated' src/components/ui/tables/data-table/ | wc -l` returns 0 on purpose (a JSDoc tag would trip the consumer lint rules that ban deprecated APIs), the two components import nothing from each other and can be mounted on the same page, and `scripts/codemod/rename-map.json` records name by name which old export maps to which new one and which parts of the new surface have no old equivalent
|
|
262
|
+
- **AppDataGrid**: `AppDataGrid.mockup.stories.tsx` stays published in Storybook as `AppDataGrid/Mockup`, frozen: it is the reference the delivery is signed against, and it is not public API — what leaves it is the stale «this mockup is not a component» banner (verifiable by difference: `grep -c 'non è un componente' src/components/stories/AppDataGrid.mockup.stories.tsx` went 1 → 0), not the story, because the delivery is signed by opening the two target scenes — `?path=/story/appdatagrid-completa--modalita-filtri` and `?path=/story/appdatagrid-completa--raggruppata` — next to `?path=/story/appdatagrid-mockup--modalita-filtri` from the Storybook manager, both of them, since groups and pager cannot be represented together and the mockup's single scene therefore has two counterparts
|
|
263
|
+
- **AppDataGrid**: ⚠️ **Known limitation, open** — the two-value operator composer opens from the «Modifica valore…» menu item but **not** from the operator radio path of the filter inventory: the closing `DropdownMenu`'s `DismissableLayer` swallows the `Popover` that mounts in the same frame, and on the radio path the dismissal window outlives a single `requestAnimationFrame` because changing the group's value adds a render before the unmount (tried in four shapes, `modal={false}` included). The only known remedy is a 200ms `setTimeout`, which this lane's «zero setTimeout» convention rejects. Measured and written up in `src/components/documents/components/AppDataGrid.md` and at the `modal={false}` comment in `src/components/ui/app-data-grid/parts/grid-filter-editor.tsx`
|
|
264
|
+
- **Gantt**: Confirmed `react-window` and `@types/react-window` stay as direct dependencies — `VariableSizeList`/`ListOnItemsRenderedProps` are imported directly by `src/components/ui/gantt/types/GanttContext.tsx`, `components/Chart/Chart.tsx`, `components/Chart/Scale/Scale.tsx`, `components/Chart/Tree/Tree.tsx`, `components/Chart/Bars/Bars.tsx` and `components/Gantt/Gantt.tsx`; the only other match in `src/` is a textual mention inside a mock message in `message-list.stories.tsx`, not an import
|
|
265
|
+
|
|
266
|
+
## [laif-ds@1.0.0]
|
|
267
|
+
|
|
268
|
+
### 🚀 Added
|
|
269
|
+
|
|
270
|
+
- **AppSidebar**: Below 768px, the sidebar now renders a Settings-style drill-down menu instead of the desktop tree in a full-screen sheet — grouped cards with dividers, 44px rows with a leading icon and a chevron only where there are children, and items with `subItems` open a level-1 screen (with a `‹ Menu` back control) instead of expanding inline. This replaces the previous mobile behavior unconditionally; there is no prop to opt out, and `collapsible` has no effect below 768px
|
|
271
|
+
- **AppSidebar**: Added `navigationDisplay` prop (`"list" | "tab"`, default `"list"`) — in `"tab"` mode the `navigation` groups render as a tab bar at the top of the sidebar and show one group at a time, instead of stacking every group under its own label. Only the selected tab shows its label, animating open to fit it exactly; every other tab is icon-only. `navigationFooter` is never tabbed and always stays visible at the bottom. A lone `navigation` group still renders without a label (in both displays and on mobile), and with a single group the tab bar is dropped entirely. Collapsed to the icon rail, the bar becomes a single group selector — the active group's icon with a chevron — that opens a `menuitemradio` menu. `NavGroup` gained an optional `iconName`, read only in tab mode
|
|
272
|
+
- **AppBottomNav**: Added a new mobile bottom navigation bar (visible below 768px, `md:hidden`), meant to be mounted as the last child of `SidebarInset`, in the normal flow and never `fixed`. The bar is full-bleed and attached to the bottom edge — `bg-d-card` with a top border only, rounded top corners, `overflow-hidden`, no shadow — for a total height of 55px plus the safe-area inset, painted as bar surface with no minimum floor. It renders up to 5 destinations (`BottomNavItem[]`, already filtered, sorted and route-matched by the app) with 16px icons over a 12px label — deliberately below both native tab-bar baselines, label-forward — plus a Menu cell that opens `AppSidebar`'s mobile panel (`showMenu={false}` drops it and spends the freed slot on a 6th destination instead). A cell with its own `subItems` opens the panel already drilled into that level instead of navigating, via the sidebar context's new `mobileNavTarget`. The active cell is marked with a rounded pill using the sidebar's own active-row colors (`bg-d-sidebar-accent` / `text-d-sidebar-accent-foreground` / `font-bold`, no border) instead of a solid brand fill — the same low-contrast tradeoff the desktop sidebar's active row has always carried on that surface (~1.1–1.2:1, under the 3:1 WCAG 1.4.11 floor for a non-text indicator), carried here by weight rather than tint; label and icon stay at 10:1+ regardless. Hover is limited to fine-pointer, hover-capable devices so a tap can't leave a stuck hover tint next to the active cell
|
|
273
|
+
- **design-tokens**: Added `designTokens.sizes.touch` (44px), `designTokens.motion` (shared easing/duration for the sidebar's tab animation), and `designTokens.navItem` (selected-row styling built on foreground weight and a leading bar for the sidebar's own rows, plus a `basePill`/`selectedPill` variant carrying the rounded-pill treatment for `AppBottomNav`'s active cell)
|
|
274
|
+
|
|
275
|
+
### 🔧 Changed
|
|
276
|
+
|
|
277
|
+
- **Sidebar**: On mobile, the full-screen `Sheet` branch now takes precedence over `collapsible="none"` — previously, pinning `collapsible="none"` left the trigger toggling `openMobile` with no sheet listening to it, making navigation unreachable below 768px. The mobile sheet now also takes focus on open (instead of landing on whatever `headerContent` renders first, which could raise the keyboard over the menu) and restores focus to the trigger on close
|
|
278
|
+
- **Sheet**: `side="full"` no longer zooms under `prefers-reduced-motion` — the fade stays
|
|
279
|
+
|
|
280
|
+
### 🐛 Fixed
|
|
281
|
+
|
|
282
|
+
- **AppSidebar**: Mobile row labels now wrap instead of clamping to two lines, so long labels remain fully readable at 200% text zoom (WCAG 1.4.4)
|
|
283
|
+
- **AppSidebar**: Mobile group titles no longer render at a 3.94:1 contrast ratio (muted text on a muted panel)
|
|
284
|
+
- **AppSidebar**: In `navigationDisplay="tab"` mode, focus now survives collapsing and re-expanding the sidebar while a tab is focused, and the selected tab is tracked via React state instead of Radix's `data-state` — `TooltipTrigger asChild` was silently overwriting that attribute on the trigger, which had been killing every active-state rule keyed off it
|
|
285
|
+
|
|
286
|
+
## [laif-ds@0.2.92]
|
|
287
|
+
|
|
288
|
+
### 🐛 Fixed
|
|
289
|
+
|
|
290
|
+
- **Badge / Alert**: Fixed `variant="success"` and `variant="warning"` rendering with insufficient text contrast — the `bg-d-success-background` and `bg-d-warning-background` utilities were painting the foreground (text) color as the background instead of the intended surface color, collapsing contrast against the paired `-foreground` text. Contrast now resolves correctly to surface + on-surface in both light and dark themes
|
|
291
|
+
|
|
8
292
|
## [laif-ds@0.2.91]
|
|
9
293
|
|
|
10
294
|
### 🚀 Added
|