@dashforge/tw 0.6.0-beta → 0.7.0-beta

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 (90) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/THEME-AUDIT.md +175 -0
  3. package/dist/index.esm.js +973 -69
  4. package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -1
  5. package/dist/src/components/Box/box.variants.d.ts.map +1 -1
  6. package/dist/src/components/DataGrid/DataGrid.d.ts +29 -0
  7. package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -0
  8. package/dist/src/components/DataGrid/dataGrid.types.d.ts +171 -0
  9. package/dist/src/components/DataGrid/dataGrid.types.d.ts.map +1 -0
  10. package/dist/src/components/DataGrid/dataGrid.variants.d.ts +216 -0
  11. package/dist/src/components/DataGrid/dataGrid.variants.d.ts.map +1 -0
  12. package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -1
  13. package/dist/src/components/Divider/divider.variants.d.ts.map +1 -1
  14. package/dist/src/components/Pagination/Pagination.d.ts.map +1 -1
  15. package/dist/src/components/Pagination/pagination.variants.d.ts.map +1 -1
  16. package/dist/src/components/Popover/popover.variants.d.ts.map +1 -1
  17. package/dist/src/components/Skeleton/skeleton.variants.d.ts.map +1 -1
  18. package/dist/src/components/Table/table.types.d.ts +8 -0
  19. package/dist/src/components/Table/table.types.d.ts.map +1 -1
  20. package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -1
  21. package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -1
  22. package/dist/src/components/Typography/typography.variants.d.ts.map +1 -1
  23. package/dist/src/components/_shared/data/getNestedValue.d.ts.map +1 -0
  24. package/dist/src/components/{Table/_internal → _shared/data}/useColumnAutoDetect.d.ts +1 -1
  25. package/dist/src/components/_shared/data/useColumnAutoDetect.d.ts.map +1 -0
  26. package/dist/src/components/_shared/data/useControllableState.d.ts.map +1 -0
  27. package/dist/src/components/_shared/data/useDebouncedValue.d.ts.map +1 -0
  28. package/dist/src/components/{Table/_internal → _shared/data}/useTableFilter.d.ts +1 -1
  29. package/dist/src/components/_shared/data/useTableFilter.d.ts.map +1 -0
  30. package/dist/src/components/{Table/_internal → _shared/data}/useTableSearch.d.ts +1 -1
  31. package/dist/src/components/_shared/data/useTableSearch.d.ts.map +1 -0
  32. package/dist/src/components/{Table/_internal → _shared/data}/useTableSelection.d.ts +1 -1
  33. package/dist/src/components/_shared/data/useTableSelection.d.ts.map +1 -0
  34. package/dist/src/components/{Table/_internal → _shared/data}/useTableSort.d.ts +1 -1
  35. package/dist/src/components/_shared/data/useTableSort.d.ts.map +1 -0
  36. package/dist/src/components/_shared/data/useVirtualizer.d.ts +107 -0
  37. package/dist/src/components/_shared/data/useVirtualizer.d.ts.map +1 -0
  38. package/dist/src/index.d.ts +5 -2
  39. package/dist/src/index.d.ts.map +1 -1
  40. package/package.json +3 -3
  41. package/src/components/Accordion/accordion.variants.ts +5 -3
  42. package/src/components/Box/Box.test.tsx +32 -11
  43. package/src/components/Box/box.variants.ts +23 -4
  44. package/src/components/DataGrid/DataGrid.test.tsx +496 -0
  45. package/src/components/DataGrid/DataGrid.tsx +831 -0
  46. package/src/components/DataGrid/dataGrid.types.ts +189 -0
  47. package/src/components/DataGrid/dataGrid.variants.ts +144 -0
  48. package/src/components/Dialog/dialog.variants.ts +10 -6
  49. package/src/components/Divider/Divider.test.tsx +3 -1
  50. package/src/components/Divider/Divider.tsx +1 -1
  51. package/src/components/Divider/divider.variants.ts +2 -1
  52. package/src/components/Pagination/Pagination.tsx +3 -1
  53. package/src/components/Pagination/pagination.variants.ts +19 -13
  54. package/src/components/Popover/popover.variants.ts +10 -4
  55. package/src/components/Skeleton/skeleton.variants.ts +2 -1
  56. package/src/components/Table/Table.tsx +8 -8
  57. package/src/components/Table/table.types.ts +9 -0
  58. package/src/components/Tabs/tabs.variants.ts +19 -8
  59. package/src/components/Tooltip/tooltip.variants.ts +12 -2
  60. package/src/components/Typography/Typography.test.tsx +3 -1
  61. package/src/components/Typography/typography.variants.ts +6 -2
  62. package/src/components/{Table/_internal → _shared/data}/useColumnAutoDetect.test.ts +1 -1
  63. package/src/components/{Table/_internal → _shared/data}/useColumnAutoDetect.ts +1 -1
  64. package/src/components/{Table/_internal → _shared/data}/useTableFilter.ts +1 -1
  65. package/src/components/{Table/_internal → _shared/data}/useTableSearch.test.ts +1 -1
  66. package/src/components/{Table/_internal → _shared/data}/useTableSearch.ts +1 -1
  67. package/src/components/{Table/_internal → _shared/data}/useTableSelection.ts +1 -1
  68. package/src/components/{Table/_internal → _shared/data}/useTableSort.test.ts +1 -1
  69. package/src/components/{Table/_internal → _shared/data}/useTableSort.ts +1 -1
  70. package/src/components/_shared/data/useVirtualizer.test.ts +158 -0
  71. package/src/components/_shared/data/useVirtualizer.ts +199 -0
  72. package/src/components/_shared/themeIdentity.test.ts +113 -0
  73. package/src/index.ts +18 -2
  74. package/dist/src/components/Table/_internal/getNestedValue.d.ts.map +0 -1
  75. package/dist/src/components/Table/_internal/useColumnAutoDetect.d.ts.map +0 -1
  76. package/dist/src/components/Table/_internal/useControllableState.d.ts.map +0 -1
  77. package/dist/src/components/Table/_internal/useDebouncedValue.d.ts.map +0 -1
  78. package/dist/src/components/Table/_internal/useTableFilter.d.ts.map +0 -1
  79. package/dist/src/components/Table/_internal/useTableSearch.d.ts.map +0 -1
  80. package/dist/src/components/Table/_internal/useTableSelection.d.ts.map +0 -1
  81. package/dist/src/components/Table/_internal/useTableSort.d.ts.map +0 -1
  82. package/src/components/Table/_internal/themeIdentity.test.ts +0 -84
  83. /package/dist/src/components/{Table/_internal → _shared/data}/getNestedValue.d.ts +0 -0
  84. /package/dist/src/components/{Table/_internal → _shared/data}/useControllableState.d.ts +0 -0
  85. /package/dist/src/components/{Table/_internal → _shared/data}/useDebouncedValue.d.ts +0 -0
  86. /package/src/components/{Table/_internal → _shared/data}/getNestedValue.test.ts +0 -0
  87. /package/src/components/{Table/_internal → _shared/data}/getNestedValue.ts +0 -0
  88. /package/src/components/{Table/_internal → _shared/data}/useControllableState.ts +0 -0
  89. /package/src/components/{Table/_internal → _shared/data}/useDebouncedValue.ts +0 -0
  90. /package/src/components/{Table/_internal → _shared/data}/useTableSelection.test.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -12,6 +12,264 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
12
  > duplicated intentionally — no shared "lowest common denominator" headless
13
13
  > layer.
14
14
 
15
+ ## [0.7.0-beta] — 2026-05-19
16
+
17
+ **Combined Sprint 4.2 + 4.3 release.** Ships **`<DataGrid>`** — a
18
+ virtualized data table for large data sets (500 rows to millions)
19
+ — alongside a **theme identity sweep** across the whole catalog
20
+ (systemic dark-mode contrast fixes on 10 existing components).
21
+
22
+ Two coherent themes, one release:
23
+
24
+ 1. **DataGrid (Sprint 4.2)** — sibling to `<Table>` with **homemade
25
+ virtualization** (no `@tanstack/react-virtual`, no
26
+ `react-window`). Same column model, same cell renderer library,
27
+ same RBAC integration, same identity-consistent visual design as
28
+ Table. Differs at the render strategy level: only the window of
29
+ visible rows mounts in DOM. Adds sticky-left columns, server-side
30
+ mode (4 independent opt-in flags), `selectAllScope`, and optional
31
+ internal pagination.
32
+
33
+ 2. **Theme identity sweep (Sprint 4.3)** — applies the identity rule
34
+ codified in Sprint 4.1 to the WHOLE catalog. 10 existing
35
+ components (Typography, Box, Dialog, Divider, Pagination,
36
+ Popover, Skeleton, Tabs, Tooltip, plus Table-side comments)
37
+ had latent `dark:` variants on the neutral palette → those
38
+ classes double-inverted via the dashforgePreset CSS-var swap
39
+ and broke dark mode. Sweep removes the anti-pattern across the
40
+ catalog and adds a package-level regression test
41
+ (`_shared/themeIdentity.test.ts`) that scans every source file
42
+ on every test run.
43
+
44
+ **Minor bump** for the new DataGrid public API surface. Strictly
45
+ additive — zero breaking changes on existing component APIs.
46
+ Drop-in upgrade from `0.6.0-beta`.
47
+
48
+ ### Added
49
+
50
+ - **`<DataGrid>`** — virtualized data table for large data sets.
51
+ Sibling to `<Table>`. Required props: `rows`, `cols`,
52
+ `getRowId`, `rowHeight` (fixed for v1), `height` (bounded
53
+ container).
54
+ - **Homemade virtualization** via the new `useVirtualizer` hook
55
+ in `_shared/data/`. Scroll-event based with
56
+ `requestAnimationFrame` debounce (60 fps throttling) +
57
+ `ResizeObserver` for container resize. Spacer-row approach
58
+ preserves `<table>` semantics — screen readers see continuous
59
+ structure with `aria-hidden` spacers above/below the visible
60
+ window. Variable row height deferred to v1-bis.
61
+ - **Sticky left column** via `cols[i].sticky: 'left'`. CSS
62
+ `position: sticky; left: 0` with z-index ladder for the
63
+ top-left corner intersection with the sticky header
64
+ (`thead` z-10 < sticky cell z-[1] < sticky header corner z-20).
65
+ Right-sticky deferred to v1-bis.
66
+ - **Server-side mode flags** (4 independent opt-ins):
67
+ - `serverSideSort` — emits `onSortChange` but doesn't sort locally
68
+ - `serverSideFilter` — emits `onFilterChange` but doesn't filter
69
+ - `serverSideSearch` — emits `onSearchQueryChange` (debounced)
70
+ - `serverSidePagination` — uses `totalCount` for the virtual
71
+ scrollbar math; `rows` is the server-returned page slice
72
+ - **`selectAllScope`** prop (`'visible' | 'allLoaded'`, default
73
+ `'allLoaded'`) controls the header "select all" checkbox
74
+ semantic.
75
+ - **Optional internal pagination** via the `pagination` prop —
76
+ renders the Sprint 4 `<Pagination>` component below the
77
+ virtualized scroll. Composes naturally with virtualization
78
+ (each page is virtualized internally if large enough).
79
+ - **Bundle impact**: +8.4 KB gz on top of `0.6.0-beta`
80
+ (`<Table>` baseline). Zero new runtime deps.
81
+ - **Variants**: `plain` / `lines` (default) / `striped` /
82
+ `bordered`. **No `card` variant** — incompatible with
83
+ `<table>` virtualization spacer-rows.
84
+ - 24 unit tests + 12 `useVirtualizer` math tests + dash smoke
85
+ test at `/test-data-grid` with 10 000 generated rows.
86
+
87
+ - **`useVirtualizer` hook** in `_shared/data/` — exported
88
+ internally only (not in the public API surface). Other future
89
+ virtualized components (TreeView, future GanttGrid, etc.) can
90
+ reuse it.
91
+
92
+ - **`sticky?: 'left' | 'right'`** added to `TableColumn<T>` for
93
+ DataGrid integration. Currently ignored by Table (non-virtualized);
94
+ honored by DataGrid v1 for `'left'` only.
95
+
96
+ - **Doc MDX `/tw/docs/components/data-grid`** — emphasizes the
97
+ Table vs DataGrid decision matrix (24-row comparison table +
98
+ "Use Table when..." / "Use DataGrid when..." + "Switch later"
99
+ path showing the 3-line diff to migrate).
100
+
101
+ ### Changed
102
+
103
+ - **Internal refactor — shared data helpers moved**:
104
+ `Table/_internal/` → `_shared/data/` (13 files). The helpers
105
+ (`getNestedValue`, `useTableSearch`, `useTableSort`,
106
+ `useTableSelection`, `useTableFilter`, `useColumnAutoDetect`,
107
+ `useDebouncedValue`, `useControllableState`) are now shared
108
+ between Table and DataGrid. **Pure path move** — function names
109
+ unchanged. Public API surface unchanged (only `getNestedValue`
110
+ was ever re-exported; the re-export path updated transparently).
111
+
112
+ - **Theme identity sweep** — 10 existing components had latent
113
+ `dark:` Tailwind variants on the neutral palette, which the
114
+ dashforgePreset CSS-var swap double-inverted and broke in dark
115
+ mode. Removed across the catalog:
116
+
117
+ | Component | Sites fixed | Pattern category |
118
+ |---|---|---|
119
+ | Accordion (`accordion.variants.ts`) | 3 | A (drop `dark:` — auto-invert) |
120
+ | Box (`box.variants.ts`) | 4 | A + B (corrected dark targets) + D (solid neutral now auto-inverts as accent) |
121
+ | Dialog (`dialog.variants.ts`) | 6 | A × 5 + B × 1 |
122
+ | Divider (`divider.variants.ts` + `Divider.tsx`) | 2 | A × 2 |
123
+ | Pagination (`pagination.variants.ts` + `Pagination.tsx`) | 10 | A × 7 + B × 3 (`dark:bg-neutral-900` → `bg-neutral-50` auto-invert) |
124
+ | Popover (`popover.variants.ts`) | 4 | A × 3 + B × 1 |
125
+ | Skeleton (`skeleton.variants.ts`) | 1 | A |
126
+ | Tabs (`tabs.variants.ts`) | 8 | A × 7 + B × 1 (`dark:bg-neutral-900` → `dark:bg-neutral-200` for proper active-pill elevation) |
127
+ | Tooltip (`tooltip.variants.ts`) | 2 | **Bug fix** — was previously invisible in dark mode (double-inverted to dark surface on dark page). Now auto-inverts: dark tooltip on light page, light tooltip on dark page (high contrast preserved). |
128
+ | Typography (`typography.variants.ts`) | 2 | A × 2 |
129
+
130
+ **Box solid neutral** now uses `bg-neutral-900 text-neutral-50`
131
+ (both auto-invert) — the accent surface flips with the page in
132
+ dark mode (light surface + dark text) for identity consistency.
133
+ Previously was `bg-neutral-900 text-white dark:bg-neutral-100
134
+ dark:text-neutral-900` — the `text-white` (no auto-invert) had
135
+ forced the double-invert pattern to stay readable.
136
+
137
+ Affected test files updated to assert the new canonical
138
+ patterns (Box × 4 + Typography × 1 + Divider × 1 = 6 tests
139
+ retargeted).
140
+
141
+ ### Internal
142
+
143
+ - **`THEME-AUDIT.md`** at package root — categorizes every site
144
+ in the catalog as **A** (drop `dark:` — auto-invert), **B**
145
+ (keep `dark:` because `bg-white`/`fill-white` is hardcoded),
146
+ **C** (color palette — intentional design choice, not affected
147
+ by the rule), or **D** (component-specific exception). 42
148
+ violations catalogued across 10 components.
149
+
150
+ - **Package-level theme identity regression test** —
151
+ `_shared/themeIdentity.test.ts` replaces the Table-only one
152
+ from Sprint 4.1. Scans every `.ts`/`.tsx` source file under
153
+ `src/components/**` (excluding tests) on every test run.
154
+ Allows the Category B legitimate pattern
155
+ (`bg-white dark:bg-neutral-N` — `bg-white` doesn't auto-invert,
156
+ the `dark:` is required). Catches accidental re-introductions
157
+ of the anti-pattern at PR time.
158
+
159
+ - **+76 new unit tests** for Sprint 4.2 + 4.3:
160
+ - `useVirtualizer` math (12) — window calc, overscan, padding
161
+ invariant, edge cases (totalCount=0, very large dataset)
162
+ - `<DataGrid>` (24) — rendering, virtualized window, sort,
163
+ search, selection, selectAllScope, sticky left column,
164
+ server-side mode flags, RBAC, internal pagination, smart
165
+ defaults, i18n
166
+ - `_shared/themeIdentity.test.ts` package-level scanner (40
167
+ file-scan tests; 0 violations reported)
168
+
169
+ Full TW suite at **961/961 passing** across **48 files**.
170
+
171
+ - **`TestDataGrid.tsx`** added to the `dash` consumer
172
+ (`/test-data-grid`) — 10 000 generated rows + sticky col +
173
+ selectAllScope toggle + variant/density/size switcher + RBAC
174
+ per-column demo + custom empty state + React.Profiler logging.
175
+
176
+ - **Sidebar entries** for the new `DataGrid` doc page added to
177
+ `dashforge-docs-lab/src/tw-docs/sidebar.model.ts` (new "Data"
178
+ subgroup under "UI Components" now contains Table + DataGrid).
179
+
180
+ ### Compatibility
181
+
182
+ | Axis | Pre-`0.7.0` | Post-`0.7.0` |
183
+ |---|---|---|
184
+ | Public API surface | 32 components | **+1 `<DataGrid>` + types (`DataGridProps`, `DataGridSelectAllScope`, `DataGridServerSideFlags`, `DataGridPaginationConfig`, `DataGridSlotProps`) + `dataGridVariants` recipe** + `sticky` axis on `TableColumn<T>` (additive, optional) |
185
+ | Peer deps | `react ^18 \|\| ^19`, `tw-theme workspace`, `tw-tokens workspace` | unchanged |
186
+ | Bridge deps | `forms` / `rbac` / `ui-core` `workspace:*` | unchanged |
187
+ | **New runtime deps** | — | **none** (homemade virtualization — constraint honored) |
188
+ | Breaking changes | — | **Zero** on public APIs. Internal refactor: `Table/_internal/` helpers moved to `_shared/data/`. Only externally-visible re-export was `getNestedValue` (path updated internally; export name preserved). |
189
+ | Bundle size | 402 KB raw / 91 KB gz | **445 KB raw / 98.4 KB gz** (+43 KB raw / +7.4 KB gz / **+8.1% gz**) — just under the 10% reviewer-sign-off threshold |
190
+ | Tests passing | 828/828 (46 files) | **961/961 (48 files)** — +133 from Sprint 4.2 + 4.3 |
191
+
192
+ > **Bundle sign-off** : the +8.1% gz delta is JUST under the 10%
193
+ > threshold from PERFORMANCE.md. Justification: DataGrid is the
194
+ > highest-leverage addition before 1.0 — it unlocks the entire
195
+ > "1 000+ row admin views" use case at €0 vs MUI X DataGrid Pro's
196
+ > €499/year. The theme identity sweep is net-neutral on bundle
197
+ > (removed redundant classes, replaced with smaller ones); the
198
+ > delta is entirely DataGrid + virtualizer code (+8 KB gz).
199
+
200
+ ### Migration
201
+
202
+ No code changes required:
203
+
204
+ ```bash
205
+ pnpm up @dashforge/tw@^0.7.0-beta
206
+ ```
207
+
208
+ To adopt the new DataGrid:
209
+
210
+ ```tsx
211
+ import {
212
+ DataGrid,
213
+ RenderTwoLine,
214
+ RenderChip,
215
+ RowActionsMenu,
216
+ } from '@dashforge/tw';
217
+
218
+ <DataGrid
219
+ rows={users} // can be 10k, 100k, millions
220
+ cols={columns} // SAME shape as Table's TableColumn<T>
221
+ getRowId={(r) => r.id}
222
+ rowHeight={48} // required for virtualization
223
+ height="600px" // required for bounded container
224
+ enableSearch
225
+ rowSelection="multiple"
226
+ selectAllScope="allLoaded"
227
+ bulkActions={(rows) => (
228
+ <Button color="danger">Delete {rows.length}</Button>
229
+ )}
230
+ rowActions={(row) => (
231
+ <RowActionsMenu
232
+ row={row}
233
+ actions={[
234
+ { label: 'Edit', onClick: edit },
235
+ { label: 'Delete', onClick: del, color: 'danger' },
236
+ ]}
237
+ />
238
+ )}
239
+ />
240
+ ```
241
+
242
+ To switch an existing Table to DataGrid when you outgrow the row
243
+ count (~500+ rows), the migration is mechanical (3 lines):
244
+
245
+ ```diff
246
+ - <Table
247
+ + <DataGrid
248
+ rows={users}
249
+ cols={columns}
250
+ getRowId={(row) => row.id}
251
+ + rowHeight={48}
252
+ + height="600px"
253
+ />
254
+ ```
255
+
256
+ Everything else (sort / search / selection / row actions / cell
257
+ renderers / RBAC / i18n labels / sx / slotProps) keeps working
258
+ identically. **Expandable rows** are the one feature you lose
259
+ on Table→DataGrid swap — they ship in DataGrid v1-bis.
260
+
261
+ ### Heads-up — companion releases coming
262
+
263
+ - **Sprint 4.2-bis → `0.8.0-beta`** (estimated): DataGrid v1-bis —
264
+ per-column filter UI chips (text/number/boolean/date), column
265
+ resize via drag, column reorder via drag, column visibility
266
+ dialog, right-sticky columns.
267
+ - **Sprint 5 → `0.9.0-beta`** + starter kits v1: separate repos
268
+ `dashforge-starter-mui` + `dashforge-starter-tw` with Auth + RBAC
269
+ + form CRUD + dashboard with DataGrid admin views.
270
+ - **Sprint 6 → `1.0.0-rc.1 → 1.0.0`**: final A11Y audit, bundle
271
+ lockdown, beta freeze, cut `1.0.0`.
272
+
15
273
  ## [0.6.0-beta] — 2026-05-19
16
274
 
17
275
  **Sprint 4.1 release.** Ships the central piece of the data layer
package/THEME-AUDIT.md ADDED
@@ -0,0 +1,175 @@
1
+ # THEME-AUDIT — @dashforge/tw catalog identity sweep
2
+
3
+ > Sprint 4.3 deliverable (2026-05-19). Audit of every `@dashforge/tw`
4
+ > component against the **default `dashforgePreset()` identity rule**
5
+ > (memory: `feedback_dashforge_preset_is_identity`).
6
+ >
7
+ > **Rule recap.** The `dashforgePreset()` CSS-variable swap
8
+ > auto-inverts the neutral palette:
9
+ > `bg-neutral-50` / `text-neutral-900` etc. resolve to the
10
+ > appropriate physical color via `--df-tw-color-neutral-N`. Adding a
11
+ > `dark:` variant on a neutral class **double-inverts** the value
12
+ > and breaks dark mode. The Sprint 4.1 Table fix established the
13
+ > canonical patterns; Sprint 4.3 applies them to the whole catalog.
14
+
15
+ ## Scoring legend
16
+
17
+ - **A** — Pure double inversion. REMOVE `dark:` variant entirely.
18
+ - **B** — `bg-white` / `text-white` paired with a wrong `dark:`
19
+ target. KEEP the `dark:` variant (white doesn't auto-invert) but
20
+ FIX the target — `dark:bg-neutral-900` in dark mode resolves to
21
+ near-white (broken); use `dark:bg-neutral-100` for proper dark
22
+ elevated surface.
23
+ - **C** — Intentional `dark:` on color palette (`primary`,
24
+ `success`, `warning`, `danger`, `info`, `secondary`). KEEP — color
25
+ palettes don't auto-invert; explicit `dark:` is a design choice.
26
+ - **D** — Component-specific exception (e.g. Tooltip wants
27
+ high-contrast inversion against the page). Document why kept.
28
+
29
+ ## Per-file findings
30
+
31
+ ### Accordion (`accordion.variants.ts`)
32
+
33
+ | Line | Class | Category | Fix |
34
+ |---|---|---|---|
35
+ | 6 | `border-b border-neutral-200 dark:border-neutral-800` | A | `border-b border-neutral-200` |
36
+ | 10 | `text-sm font-medium text-neutral-900 dark:text-neutral-50` | A | `text-sm font-medium text-neutral-900` |
37
+ | 18 | `overflow-hidden text-sm text-neutral-700 dark:text-neutral-300` | A | drop `dark:text-neutral-300` |
38
+
39
+ ### Box (`box.variants.ts`)
40
+
41
+ | Line | Class | Category | Fix |
42
+ |---|---|---|---|
43
+ | 58 | `bg-white dark:bg-neutral-900` (elevated) | B | `bg-white dark:bg-neutral-100` (dark-mode elevation tier above page surface) |
44
+ | 168 | `border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900` (outlined) | A + B | `border-neutral-200 bg-white dark:bg-neutral-100` (border auto-inverts; bg keeps `bg-white` + corrected dark target) |
45
+ | 184 | `bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100` (soft) | A | `bg-neutral-100 text-neutral-900` |
46
+ | 200 | `bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900` (solid neutral) | D | `bg-neutral-900 text-neutral-50` (auto-invert — solid neutral becomes inverted accent: dark surface in light mode, light surface in dark mode; consistent with identity rule) |
47
+
48
+ ### Dialog (`dialog.variants.ts`)
49
+
50
+ | Line | Class | Category | Fix |
51
+ |---|---|---|---|
52
+ | 29 | `w-full bg-white dark:bg-neutral-900 rounded-lg shadow-xl` | B | `dark:bg-neutral-100` |
53
+ | 30 | `border border-neutral-200 dark:border-neutral-800` | A | drop `dark:` |
54
+ | 37 | `text-lg ... text-neutral-900 dark:text-neutral-50` | A | drop `dark:` |
55
+ | 38 | `text-sm text-neutral-600 dark:text-neutral-400` | A | drop `dark:` |
56
+ | 42 | `dark:text-neutral-400 dark:hover:text-neutral-50` (close button) | A | drop both `dark:` |
57
+ | 43 | `hover:bg-neutral-100 dark:hover:bg-neutral-800` | A | drop `dark:` |
58
+
59
+ ### Divider (`divider.variants.ts` + `Divider.tsx`)
60
+
61
+ | File / Line | Class | Category | Fix |
62
+ |---|---|---|---|
63
+ | `divider.variants.ts:88` | `border-neutral-200 dark:border-neutral-800` (neutral color variant) | A | drop `dark:` |
64
+ | `Divider.tsx:131` | `text-sm text-neutral-500 dark:text-neutral-400` (label) | A | drop `dark:` |
65
+
66
+ ### Pagination (`pagination.variants.ts` + `Pagination.tsx`)
67
+
68
+ 10 violations — full sweep of borders / text / bg / hover / active-button.
69
+
70
+ | Line | Class | Category | Fix |
71
+ |---|---|---|---|
72
+ | `variants:26` summary | `text-neutral-600 dark:text-neutral-400` | A | drop |
73
+ | `variants:30, 44, 58` borders | `border border-neutral-300 dark:border-neutral-700` | A | drop |
74
+ | `variants:31, 45, 59` button/selector/input bg | `bg-white dark:bg-neutral-900` | B → drop entirely | use `bg-neutral-50` (auto-inverts; border provides separation against page) |
75
+ | `variants:32, 46, 55` button text | `text-neutral-700 dark:text-neutral-300` | A | drop |
76
+ | `variants:33, 47` button hover bg | `hover:bg-neutral-50 dark:hover:bg-neutral-800` | A | `hover:bg-neutral-100` (1 elevation tier; auto-inverts) |
77
+ | `variants:60` jumpInput text | `text-neutral-900 dark:text-neutral-50` | A | drop |
78
+ | `Pagination.tsx:241` select bg+border | `border ... dark:border-neutral-700 bg-white dark:bg-neutral-900` | B → drop | `border border-neutral-300 bg-neutral-50` |
79
+
80
+ ### Popover (`popover.variants.ts`)
81
+
82
+ | Line | Class | Category | Fix |
83
+ |---|---|---|---|
84
+ | 6 | `border border-neutral-200 dark:border-neutral-800` | A | drop |
85
+ | 7 | `bg-white dark:bg-neutral-900 p-4 shadow-lg` | B | `dark:bg-neutral-100` |
86
+ | 8 | `text-sm text-neutral-900 dark:text-neutral-50` | A | drop |
87
+ | 14 | `fill-white dark:fill-neutral-900` (arrow) | B | `fill-white dark:fill-neutral-100` (match content bg) |
88
+
89
+ ### Skeleton (`skeleton.variants.ts`)
90
+
91
+ | Line | Class | Category | Fix |
92
+ |---|---|---|---|
93
+ | 38 | `bg-neutral-200 dark:bg-neutral-800` | A | drop `dark:` (auto-inverts — skeleton placeholder slightly more visible than page surface in both modes) |
94
+
95
+ ### Tabs (`tabs.variants.ts`)
96
+
97
+ | Line | Class | Category | Fix |
98
+ |---|---|---|---|
99
+ | 22 | `border-b border-neutral-200 dark:border-neutral-800` (underline list) | A | drop |
100
+ | 24 | `... text-neutral-600 dark:text-neutral-400` (underline trigger) | A | drop |
101
+ | 25 | `hover:text-neutral-900 dark:hover:text-neutral-50` | A | drop |
102
+ | 31 | `bg-neutral-100 dark:bg-neutral-800 p-1` (pill list) | A | drop |
103
+ | 33 | `... text-neutral-600 dark:text-neutral-400` (pill trigger) | A | drop |
104
+ | 34 | `hover:text-neutral-900 dark:hover:text-neutral-50` | A | drop |
105
+ | 35 | `data-[state=active]:bg-white dark:data-[state=active]:bg-neutral-900` (pill active) | B | `dark:bg-neutral-100` |
106
+ | 36 | `data-[state=active]:text-neutral-900 dark:data-[state=active]:text-neutral-50` | A | drop |
107
+
108
+ ### Tooltip (`tooltip.variants.ts`)
109
+
110
+ Tooltip historically used a "fixed dark surface in both modes" pattern.
111
+ Per the identity rule, **auto-invert** is the right behavior: dark
112
+ tooltip on light page (light mode), light tooltip on dark page (dark
113
+ mode) — the inversion **is** the high-contrast effect.
114
+
115
+ | Line | Class | Category | Fix |
116
+ |---|---|---|---|
117
+ | 8 | `bg-neutral-900 text-neutral-50` + `dark:bg-neutral-50 dark:text-neutral-900` | A | drop the `dark:` line entirely (the base classes already auto-invert and give the high-contrast effect against the page) |
118
+ | 14 | `fill-neutral-900 dark:fill-neutral-50` (arrow) | A | drop `dark:` (arrow auto-inverts with content bg) |
119
+
120
+ ### Typography (`typography.variants.ts`)
121
+
122
+ | Line | Class | Category | Fix |
123
+ |---|---|---|---|
124
+ | 65 | `neutral: 'text-neutral-900 dark:text-neutral-100'` | A | `'text-neutral-900'` |
125
+ | 66 | `muted: 'text-neutral-600 dark:text-neutral-400'` | A | `'text-neutral-600'` |
126
+
127
+ ### Table-side (Sprint 4.1 — already fixed)
128
+
129
+ Per-file already corrected in Sprint 4.1. Sprint 4.3 just verifies
130
+ the regression guard (themeIdentity test) is promoted to global so
131
+ the rest of the catalog is covered too.
132
+
133
+ ## Summary
134
+
135
+ | Component | Violations | Fix categories |
136
+ |---|---|---|
137
+ | Accordion | 3 | A × 3 |
138
+ | Box | 4 | A + B + D |
139
+ | Dialog | 6 | A × 5 + B × 1 |
140
+ | Divider | 2 | A × 2 |
141
+ | Pagination | 10 | A × 7 + B × 3 |
142
+ | Popover | 4 | A × 3 + B × 1 |
143
+ | Skeleton | 1 | A × 1 |
144
+ | Tabs | 8 | A × 7 + B × 1 |
145
+ | Tooltip | 2 | A × 2 (was "double-inverted to preserve fixed look" — auto-invert is now the canonical choice) |
146
+ | Typography | 2 | A × 2 |
147
+ | **Total** | **42** | **31 A + 5 B + 1 D + 5 fixed already** |
148
+
149
+ ## Color palettes — NOT touched
150
+
151
+ `dark:` variants on `primary`, `secondary`, `success`, `warning`,
152
+ `danger`, `info` palettes are **intentional** — those palettes do
153
+ not auto-invert (same physical color in both modes), so the
154
+ `dark:` shift is a design choice for tone refinement. They stay.
155
+
156
+ Affected sites (kept as-is):
157
+
158
+ - `Box` compound variants (`outlined/soft/solid` × color) for the
159
+ 6 color palettes (lines 188-198 in `box.variants.ts`)
160
+ - `RenderChip` color rows (Sprint 4.1 decision)
161
+ - `RowActionsMenu` `text-danger-700 dark:text-danger-300` and same
162
+ for warning
163
+
164
+ ## Sprint 4.3 deliverables
165
+
166
+ 1. ✅ THEME-AUDIT.md (this file)
167
+ 2. ⏳ Apply fixes per-file (P3)
168
+ 3. ⏳ Promote `themeIdentity.test.ts` from `Table/_internal/` to
169
+ package-level scanner walking `libs/dashforge/tw/src/components/**`
170
+ (P2). Excludes `*.test.*` files. The test fails any future
171
+ `dark:*-neutral-N` reintroduction.
172
+ 4. ⏳ Visual regression catch in `dash` (manual check in both
173
+ light + dark mode)
174
+ 5. ⏳ Release `@dashforge/tw@0.7.0-beta` — expected bundle to
175
+ **shrink** by 1-3 KB gz (removing redundant classes).