@crisp-ui-kit/crisp 0.54.9 → 0.54.11

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 (52) hide show
  1. package/dist/cjs/components/datatable/datatable.js +175 -12
  2. package/dist/cjs/components/datatable/datatable.recipe.js +30 -0
  3. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +25 -2
  4. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +159 -63
  5. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +41 -9
  6. package/dist/cjs/components/text/text.recipe.js +17 -7
  7. package/dist/cjs/product/recordpage/column.js +19 -0
  8. package/dist/cjs/product/recordpage/recordpage.js +5 -4
  9. package/dist/cjs/product/recordpage/test-fixtures.js +55 -0
  10. package/dist/cjs/product/recordstable/index.js +1 -1
  11. package/dist/cjs/product/recordstable/recordstable.js +5 -0
  12. package/dist/cjs/product/savedviews/savedviews.recipe.js +13 -11
  13. package/dist/cjs/product/taskstable/taskstable.js +5 -0
  14. package/dist/datatable.css +17 -0
  15. package/dist/datatable.layered.css +17 -0
  16. package/dist/esm/components/datatable/datatable.js +175 -12
  17. package/dist/esm/components/datatable/datatable.recipe.js +30 -0
  18. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +25 -2
  19. package/dist/esm/components/tabletoolbar/tabletoolbar.js +159 -63
  20. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +41 -9
  21. package/dist/esm/components/text/text.recipe.js +17 -7
  22. package/dist/esm/product/recordpage/column.js +16 -0
  23. package/dist/esm/product/recordpage/recordpage.js +2 -1
  24. package/dist/esm/product/recordpage/test-fixtures.js +52 -0
  25. package/dist/esm/product/recordstable/index.js +1 -1
  26. package/dist/esm/product/recordstable/recordstable.js +5 -0
  27. package/dist/esm/product/savedviews/savedviews.recipe.js +13 -11
  28. package/dist/esm/product/taskstable/taskstable.js +5 -0
  29. package/dist/recordstable.css +19 -2
  30. package/dist/recordstable.layered.css +19 -2
  31. package/dist/savedviews.css +6 -18
  32. package/dist/savedviews.layered.css +6 -18
  33. package/dist/styles.css +78 -24
  34. package/dist/styles.layered.css +78 -24
  35. package/dist/tabletoolbar.css +35 -3
  36. package/dist/tabletoolbar.layered.css +35 -3
  37. package/dist/text.css +1 -1
  38. package/dist/text.layered.css +1 -1
  39. package/dist/types/components/datatable/datatable.d.ts +38 -3
  40. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +26 -1
  41. package/dist/types/components/text/text.recipe.d.ts +1 -1
  42. package/dist/types/product/recordpage/column.d.ts +112 -0
  43. package/dist/types/product/recordpage/recordpage.d.ts +1 -1
  44. package/dist/types/product/recordpage/test-fixtures.d.ts +3 -0
  45. package/dist/types/product/recordpanel/recordpanel.d.ts +1 -1
  46. package/dist/types/product/recordstable/index.d.ts +1 -1
  47. package/dist/types/product/recordstable/recordstable.d.ts +6 -1
  48. package/dist/types/product/taskstable/taskstable.d.ts +5 -0
  49. package/package.json +1 -1
  50. /package/dist/cjs/product/{recordstable → recordpage}/editable-field.js +0 -0
  51. /package/dist/esm/product/{recordstable → recordpage}/editable-field.js +0 -0
  52. /package/dist/types/product/{recordstable → recordpage}/editable-field.d.ts +0 -0
@@ -512,6 +512,11 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
512
512
  * editing (double-click a text/number/url/date cell), add-row and add-column,
513
513
  * and a floating bulk-action bar (delete / deselect) when rows are selected.
514
514
  * Selection can be controlled via `selectedIds`/`onSelectionChange`.
515
+ *
516
+ * @deprecated Reach for `DataTable` instead — a single Attio-exact records
517
+ * grid replaces the separate table variants. `RecordsTable` has no
518
+ * consumers left (checked GRC and the fms/Mercury admin) and is slated for
519
+ * removal in a future minor.
515
520
  */
516
521
  export function RecordsTable({ labels, columns: columnsProp = defaultColumns, rows: rowsProp = defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", emptyStateExtra, className, ...rest }) {
517
522
  const [cols, setCols] = React.useState(columnsProp);
@@ -23,24 +23,26 @@ const config = defineRecipe({
23
23
  position: relative; display: inline-flex; font-family: ${c.font.sans};
24
24
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
25
25
 
26
- /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
27
- #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
28
- screenshot/anti-aliasing noise, not a second real colour. Reusing the
29
- token (Lesson 3 — tokens, not raw px/hex) gets a correct,
30
- already-established dark-mode fill for free instead of a light-only
31
- literal; AUTHORING.md Lesson 29 is the case where that shortcut went
32
- wrong (a flat colour that stayed visible where the reference had
33
- none) — this is the opposite risk (a token that already flips per
34
- theme, applied to the closest measured match). */
26
+ /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
27
+ actual position (Attio's real view bar, next to View settings --
28
+ fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255)
29
+ exact) with elevation.field's 3-layer shadow, the same raised
30
+ chrome View settings and Import/Export both carry
31
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly
32
+ since this is a separate recipe/component, but the identical measured
33
+ values). Supersedes the original #555 measurement (rgb(246,247,247),
34
+ reused as bgRaised -- a flat grey fill with no shadow at all), which
35
+ was wrong: this trigger was never re-checked once it moved into its
36
+ real view-bar position. */
35
37
  & .crisp-savedviews-trigger {
36
38
  box-sizing: border-box; display: inline-flex; align-items: center; gap: ${c.space["1.5"]};
37
39
  height: ${c.size.row}; margin: 0; padding: ${c.space["1"]} ${c.space["1.5"]};
38
- border: none; border-radius: ${c.radius.md}; background: ${c.bgRaised};
40
+ border: none; border-radius: ${c.radius.md}; background: ${c.bgSurface}; box-shadow: ${e.field};
39
41
  font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary};
40
42
  cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
41
43
  }
42
44
  & .crisp-savedviews-trigger:hover,
43
- & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, ${c.fgPrimary} 4%, ${c.bgRaised}); }
45
+ & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, ${c.fgPrimary} 4%, ${c.bgSurface}); }
44
46
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: ${c.size.icon}; height: ${c.size.icon}; }
45
47
  & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
46
48
  & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: ${c.fgMuted}; }
@@ -11,6 +11,11 @@ function Cell({ flex, className, children, }) {
11
11
  * taskstable.recipe.ts) with a typed column/cell layer: a title+checkbox
12
12
  * row grouped into due-date sections, a due-date cell that goes red past
13
13
  * due, and consumer-supplied record/assignee chip slots.
14
+ *
15
+ * @deprecated Reach for `DataTable`'s `groupBy` instead — a single
16
+ * Attio-exact records grid replaces the separate table variants.
17
+ * `TasksTable` has no consumers left (checked GRC and the fms/Mercury
18
+ * admin) and is slated for removal in a future minor.
14
19
  */
15
20
  export function TasksTable({ columns, groups, leading, trailing, emptyLabel: emptyLabelProp, completeLabel: completeLabelProp, incompleteLabel: incompleteLabelProp, className, ...rest }) {
16
21
  const crispMessages = useCrispMessages();
@@ -8,12 +8,29 @@
8
8
  & .crisp-recordstable-search svg { flex-shrink: 0; }
9
9
  & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
10
10
  & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
11
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
11
+ & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
12
12
  & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
13
13
  & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
14
- & .crisp-recordstable-tool--active { background: var(--crisp-bg-raised); }
14
+ /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
15
+ base .tool rule above now already sets; this override existed to
16
+ distinguish active from a base state that used to be transparent.
17
+ Kept explicit (not just deleted) so a future change to the base
18
+ rule's background can't silently change this one too. */
19
+ & .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
15
20
  & .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
16
21
  & .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
22
+ /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
23
+ trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
24
+ a dashed ::before (1px dashed, border-radius: inherit, absolutely
25
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
26
+ instead (the --active modifier, unchanged) -- confirmed on Sort with
27
+ an actual sort applied. color-mix (not the raw rgba Attio computes
28
+ to) so both the border and the fill flip correctly in dark mode, the
29
+ same reasoning --ghost above already uses. Only wired into
30
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
31
+ & .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
32
+ & .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
33
+ & .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
17
34
  & .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
18
35
  & .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
19
36
  & .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
@@ -9,12 +9,29 @@
9
9
  & .crisp-recordstable-search svg { flex-shrink: 0; }
10
10
  & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
11
11
  & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
12
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
12
+ & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
13
13
  & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
14
14
  & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
15
- & .crisp-recordstable-tool--active { background: var(--crisp-bg-raised); }
15
+ /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
16
+ base .tool rule above now already sets; this override existed to
17
+ distinguish active from a base state that used to be transparent.
18
+ Kept explicit (not just deleted) so a future change to the base
19
+ rule's background can't silently change this one too. */
20
+ & .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
16
21
  & .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
17
22
  & .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
23
+ /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
24
+ trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
25
+ a dashed ::before (1px dashed, border-radius: inherit, absolutely
26
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
27
+ instead (the --active modifier, unchanged) -- confirmed on Sort with
28
+ an actual sort applied. color-mix (not the raw rgba Attio computes
29
+ to) so both the border and the fill flip correctly in dark mode, the
30
+ same reasoning --ghost above already uses. Only wired into
31
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
32
+ & .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
33
+ & .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
34
+ & .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
18
35
  & .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
19
36
  & .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
20
37
  & .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
@@ -73,24 +73,12 @@
73
73
  }
74
74
 
75
75
  .crisp-savedviews {
76
- position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
77
- #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
78
- screenshot/anti-aliasing noise, not a second real colour. Reusing the
79
- token (Lesson 3 — tokens, not raw px/hex) gets a correct,
80
- already-established dark-mode fill for free instead of a light-only
81
- literal; AUTHORING.md Lesson 29 is the case where that shortcut went
82
- wrong (a flat colour that stayed visible where the reference had
83
- none) — this is the opposite risk (a token that already flips per
84
- theme, applied to the closest measured match). */
85
- & .crisp-savedviews-trigger {
86
- box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
87
- height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
88
- border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
89
- font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
90
- cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
91
- }
92
- & .crisp-savedviews-trigger:hover,
93
- & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
76
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
77
+ actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
78
+ chrome View settings and Import/Export both carry
79
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
80
+ & .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
81
+ & .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
94
82
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
95
83
  & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
96
84
  & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
@@ -74,24 +74,12 @@
74
74
  }
75
75
 
76
76
  .crisp-savedviews {
77
- position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
78
- #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
79
- screenshot/anti-aliasing noise, not a second real colour. Reusing the
80
- token (Lesson 3 — tokens, not raw px/hex) gets a correct,
81
- already-established dark-mode fill for free instead of a light-only
82
- literal; AUTHORING.md Lesson 29 is the case where that shortcut went
83
- wrong (a flat colour that stayed visible where the reference had
84
- none) — this is the opposite risk (a token that already flips per
85
- theme, applied to the closest measured match). */
86
- & .crisp-savedviews-trigger {
87
- box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
88
- height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
89
- border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
90
- font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
91
- cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
92
- }
93
- & .crisp-savedviews-trigger:hover,
94
- & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
77
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
78
+ actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
79
+ chrome View settings and Import/Export both carry
80
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
81
+ & .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
82
+ & .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
95
83
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
96
84
  & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
97
85
  & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
package/dist/styles.css CHANGED
@@ -689,6 +689,9 @@
689
689
  & tbody tr[data-selected] td { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
690
690
  & tbody tr[data-selected]:hover td { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface))); }
691
691
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
692
+ /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%, composited OVER whatever row-state background the cell already has (hover, selected), which is why it's an ::after overlay and not a
693
+ plain background. */
694
+ & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
692
695
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
693
696
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
694
697
  /* An async onEdit's in-flight state (#385) -- the field/picker dims and stops accepting input rather than disappearing, so the just-typed or just-chosen value stays visible while its commit is pending. Same treatment Input already gives its own disabled state. */
@@ -748,6 +751,10 @@
748
751
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
749
752
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
750
753
  & .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
754
+ /* crisp/#601 supersession follow-up -- the same flex-row shape .crisp-datatable-entity gives its own avatar+name+actions, but structural only (no entity-specific colour/weight): lets onOpenComments/onOpenPreview compose alongside ANY column's own
755
+ content (a custom render, or a non-entity built-in type), not just
756
+ type: "entity"'s own inline rendering. */
757
+ & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
751
758
  /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
752
759
  hover reveal -- identical at rest and on hover: text-decoration-color
753
760
  is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text colour. The previous :hover-gated, full-contrast rule here was itself unmeasured and wrong in two ways at once. */
@@ -779,6 +786,16 @@
779
786
  /* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
780
787
  & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
781
788
  & .crisp-datatable-link:hover { text-decoration: underline; }
789
+ /* rating cell type/editor (#760) -- measured live: 16px stars on an 18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a committed/filled star at brand600 (--crisp-bg-brand-solid, an exact match to the measured #266df0), and a distinct hover-preview colour -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
790
+ (#1f5fd6); kept as its own literal rather than forced onto that token,
791
+ since the two were measured stable and different. The read-only svg
792
+ and the editable button's own inner svg both carry the crisp-datatable-rating-star class on the svg itself (not the button), so one set of fill rules covers both -- the button only adds its own reset + cursor via a separate -btn class. */
793
+ & .crisp-datatable-rating { display: inline-flex; align-items: center; gap: 2px; }
794
+ & .crisp-datatable-rating-star { display: block; }
795
+ & .crisp-datatable-rating-star path { fill: rgba(0, 0, 0, 0.1); }
796
+ & .crisp-datatable-rating-star[data-filled] path { fill: var(--crisp-bg-brand-solid); }
797
+ & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
798
+ & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
782
799
  /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
783
800
  hairline doubling as the drop indicator (it tints toward the brand
784
801
  colour as the active drop target). The affordance + geometry are
@@ -1824,12 +1841,29 @@
1824
1841
  & .crisp-tabletoolbar-search svg { flex-shrink: 0; }
1825
1842
  & .crisp-tabletoolbar-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
1826
1843
  & .crisp-tabletoolbar-search input::placeholder { color: var(--crisp-fg-muted); }
1827
- & .crisp-tabletoolbar-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
1844
+ & .crisp-tabletoolbar-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
1828
1845
  & .crisp-tabletoolbar-tool:hover { background: var(--crisp-bg-raised); }
1829
1846
  & .crisp-tabletoolbar-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
1830
- & .crisp-tabletoolbar-tool--active { background: var(--crisp-bg-raised); }
1847
+ /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
1848
+ base .tool rule above now already sets; this override existed to
1849
+ distinguish active from a base state that used to be transparent.
1850
+ Kept explicit (not just deleted) so a future change to the base
1851
+ rule's background can't silently change this one too. */
1852
+ & .crisp-tabletoolbar-tool--active { background: var(--crisp-bg-surface); }
1831
1853
  & .crisp-tabletoolbar-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
1832
1854
  & .crisp-tabletoolbar-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
1855
+ /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
1856
+ trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
1857
+ a dashed ::before (1px dashed, border-radius: inherit, absolutely
1858
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
1859
+ instead (the --active modifier, unchanged) -- confirmed on Sort with
1860
+ an actual sort applied. color-mix (not the raw rgba Attio computes
1861
+ to) so both the border and the fill flip correctly in dark mode, the
1862
+ same reasoning --ghost above already uses. Only wired into
1863
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
1864
+ & .crisp-tabletoolbar-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
1865
+ & .crisp-tabletoolbar-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
1866
+ & .crisp-tabletoolbar-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
1833
1867
  & .crisp-tabletoolbar-toolgroup { position: relative; display: inline-flex; }
1834
1868
  & .crisp-tabletoolbar-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
1835
1869
  & .crisp-tabletoolbar-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
@@ -1922,12 +1956,27 @@
1922
1956
  & .crisp-tabletoolbar-fcond-seg--val { font-weight: var(--crisp-weight-medium); max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
1923
1957
  & .crisp-tabletoolbar-fcond-seg--kebab { background: var(--crisp-bg-surface); justify-content: center; width: 28px; padding: 0; border-radius: 0 var(--crisp-radius-md) var(--crisp-radius-md) 0; color: var(--crisp-fg-muted); }
1924
1958
  & .crisp-tabletoolbar-fcond-seg--kebab:hover { color: var(--crisp-fg-primary); }
1959
+ /* #754: the sort row's own field trigger, replacing a plain Select
1960
+ with the same nested-drill-down popover Filter's field picker already has — reuses Select's own already-measured trigger chrome
1961
+ verbatim (its class is scoped under Select's own root class, so it can't be reused as a bare class name outside an actual Select tree;
1962
+ these values are copied, not re-measured). */
1963
+ & .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
1964
+ & .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
1965
+ & .crisp-tabletoolbar-sort-field-trigger:hover { background: var(--crisp-bg-raised); }
1966
+ & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
1967
+ & .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
1968
+ & .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
1969
+ & .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
1925
1970
  & .crisp-tabletoolbar-menu--val { min-width: 134px; }
1926
1971
  & .crisp-tabletoolbar-datepop { width: 300px; }
1927
1972
  & .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); padding: 0 var(--crisp-space-1); }
1928
1973
  & .crisp-tabletoolbar-datepop-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
1929
1974
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
1930
- & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface-muted); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; cursor: pointer; }
1975
+
1976
+ & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; cursor: pointer; }
1977
+
1978
+ & .crisp-tabletoolbar-sorted-prefix { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
1979
+ & .crisp-tabletoolbar-sorted-count { font-size: var(--crisp-text-sm); color: color-mix(in srgb, var(--crisp-fg-primary) 73%, transparent); }
1931
1980
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
1932
1981
  }
1933
1982
 
@@ -1971,7 +2020,7 @@
1971
2020
  }
1972
2021
 
1973
2022
  .crisp-text {
1974
- display: inline;
2023
+ vertical-align: baseline;
1975
2024
  }
1976
2025
  .crisp-text--tone_default {
1977
2026
  color: var(--crisp-fg-primary);
@@ -2948,12 +2997,29 @@
2948
2997
  & .crisp-recordstable-search svg { flex-shrink: 0; }
2949
2998
  & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
2950
2999
  & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
2951
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
3000
+ & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
2952
3001
  & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
2953
3002
  & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2954
- & .crisp-recordstable-tool--active { background: var(--crisp-bg-raised); }
3003
+ /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
3004
+ base .tool rule above now already sets; this override existed to
3005
+ distinguish active from a base state that used to be transparent.
3006
+ Kept explicit (not just deleted) so a future change to the base
3007
+ rule's background can't silently change this one too. */
3008
+ & .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
2955
3009
  & .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
2956
3010
  & .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
3011
+ /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
3012
+ trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
3013
+ a dashed ::before (1px dashed, border-radius: inherit, absolutely
3014
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
3015
+ instead (the --active modifier, unchanged) -- confirmed on Sort with
3016
+ an actual sort applied. color-mix (not the raw rgba Attio computes
3017
+ to) so both the border and the fill flip correctly in dark mode, the
3018
+ same reasoning --ghost above already uses. Only wired into
3019
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
3020
+ & .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
3021
+ & .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
3022
+ & .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2957
3023
  & .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
2958
3024
  & .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
2959
3025
  & .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
@@ -3306,24 +3372,12 @@
3306
3372
  }
3307
3373
 
3308
3374
  .crisp-savedviews {
3309
- position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
3310
- #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
3311
- screenshot/anti-aliasing noise, not a second real colour. Reusing the
3312
- token (Lesson 3 — tokens, not raw px/hex) gets a correct,
3313
- already-established dark-mode fill for free instead of a light-only
3314
- literal; AUTHORING.md Lesson 29 is the case where that shortcut went
3315
- wrong (a flat colour that stayed visible where the reference had
3316
- none) — this is the opposite risk (a token that already flips per
3317
- theme, applied to the closest measured match). */
3318
- & .crisp-savedviews-trigger {
3319
- box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
3320
- height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
3321
- border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
3322
- font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
3323
- cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
3324
- }
3325
- & .crisp-savedviews-trigger:hover,
3326
- & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
3375
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
3376
+ actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
3377
+ chrome View settings and Import/Export both carry
3378
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
3379
+ & .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
3380
+ & .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
3327
3381
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
3328
3382
  & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
3329
3383
  & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
@@ -690,6 +690,9 @@
690
690
  & tbody tr[data-selected] td { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
691
691
  & tbody tr[data-selected]:hover td { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface))); }
692
692
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
693
+ /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%, composited OVER whatever row-state background the cell already has (hover, selected), which is why it's an ::after overlay and not a
694
+ plain background. */
695
+ & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
693
696
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
694
697
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
695
698
  /* An async onEdit's in-flight state (#385) -- the field/picker dims and stops accepting input rather than disappearing, so the just-typed or just-chosen value stays visible while its commit is pending. Same treatment Input already gives its own disabled state. */
@@ -749,6 +752,10 @@
749
752
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
750
753
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
751
754
  & .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
755
+ /* crisp/#601 supersession follow-up -- the same flex-row shape .crisp-datatable-entity gives its own avatar+name+actions, but structural only (no entity-specific colour/weight): lets onOpenComments/onOpenPreview compose alongside ANY column's own
756
+ content (a custom render, or a non-entity built-in type), not just
757
+ type: "entity"'s own inline rendering. */
758
+ & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
752
759
  /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
753
760
  hover reveal -- identical at rest and on hover: text-decoration-color
754
761
  is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text colour. The previous :hover-gated, full-contrast rule here was itself unmeasured and wrong in two ways at once. */
@@ -780,6 +787,16 @@
780
787
  /* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
781
788
  & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
782
789
  & .crisp-datatable-link:hover { text-decoration: underline; }
790
+ /* rating cell type/editor (#760) -- measured live: 16px stars on an 18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a committed/filled star at brand600 (--crisp-bg-brand-solid, an exact match to the measured #266df0), and a distinct hover-preview colour -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
791
+ (#1f5fd6); kept as its own literal rather than forced onto that token,
792
+ since the two were measured stable and different. The read-only svg
793
+ and the editable button's own inner svg both carry the crisp-datatable-rating-star class on the svg itself (not the button), so one set of fill rules covers both -- the button only adds its own reset + cursor via a separate -btn class. */
794
+ & .crisp-datatable-rating { display: inline-flex; align-items: center; gap: 2px; }
795
+ & .crisp-datatable-rating-star { display: block; }
796
+ & .crisp-datatable-rating-star path { fill: rgba(0, 0, 0, 0.1); }
797
+ & .crisp-datatable-rating-star[data-filled] path { fill: var(--crisp-bg-brand-solid); }
798
+ & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
799
+ & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
783
800
  /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
784
801
  hairline doubling as the drop indicator (it tints toward the brand
785
802
  colour as the active drop target). The affordance + geometry are
@@ -1825,12 +1842,29 @@
1825
1842
  & .crisp-tabletoolbar-search svg { flex-shrink: 0; }
1826
1843
  & .crisp-tabletoolbar-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
1827
1844
  & .crisp-tabletoolbar-search input::placeholder { color: var(--crisp-fg-muted); }
1828
- & .crisp-tabletoolbar-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
1845
+ & .crisp-tabletoolbar-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
1829
1846
  & .crisp-tabletoolbar-tool:hover { background: var(--crisp-bg-raised); }
1830
1847
  & .crisp-tabletoolbar-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
1831
- & .crisp-tabletoolbar-tool--active { background: var(--crisp-bg-raised); }
1848
+ /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
1849
+ base .tool rule above now already sets; this override existed to
1850
+ distinguish active from a base state that used to be transparent.
1851
+ Kept explicit (not just deleted) so a future change to the base
1852
+ rule's background can't silently change this one too. */
1853
+ & .crisp-tabletoolbar-tool--active { background: var(--crisp-bg-surface); }
1832
1854
  & .crisp-tabletoolbar-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
1833
1855
  & .crisp-tabletoolbar-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
1856
+ /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
1857
+ trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
1858
+ a dashed ::before (1px dashed, border-radius: inherit, absolutely
1859
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
1860
+ instead (the --active modifier, unchanged) -- confirmed on Sort with
1861
+ an actual sort applied. color-mix (not the raw rgba Attio computes
1862
+ to) so both the border and the fill flip correctly in dark mode, the
1863
+ same reasoning --ghost above already uses. Only wired into
1864
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
1865
+ & .crisp-tabletoolbar-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
1866
+ & .crisp-tabletoolbar-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
1867
+ & .crisp-tabletoolbar-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
1834
1868
  & .crisp-tabletoolbar-toolgroup { position: relative; display: inline-flex; }
1835
1869
  & .crisp-tabletoolbar-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
1836
1870
  & .crisp-tabletoolbar-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
@@ -1923,12 +1957,27 @@
1923
1957
  & .crisp-tabletoolbar-fcond-seg--val { font-weight: var(--crisp-weight-medium); max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
1924
1958
  & .crisp-tabletoolbar-fcond-seg--kebab { background: var(--crisp-bg-surface); justify-content: center; width: 28px; padding: 0; border-radius: 0 var(--crisp-radius-md) var(--crisp-radius-md) 0; color: var(--crisp-fg-muted); }
1925
1959
  & .crisp-tabletoolbar-fcond-seg--kebab:hover { color: var(--crisp-fg-primary); }
1960
+ /* #754: the sort row's own field trigger, replacing a plain Select
1961
+ with the same nested-drill-down popover Filter's field picker already has — reuses Select's own already-measured trigger chrome
1962
+ verbatim (its class is scoped under Select's own root class, so it can't be reused as a bare class name outside an actual Select tree;
1963
+ these values are copied, not re-measured). */
1964
+ & .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
1965
+ & .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
1966
+ & .crisp-tabletoolbar-sort-field-trigger:hover { background: var(--crisp-bg-raised); }
1967
+ & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
1968
+ & .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
1969
+ & .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
1970
+ & .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
1926
1971
  & .crisp-tabletoolbar-menu--val { min-width: 134px; }
1927
1972
  & .crisp-tabletoolbar-datepop { width: 300px; }
1928
1973
  & .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); padding: 0 var(--crisp-space-1); }
1929
1974
  & .crisp-tabletoolbar-datepop-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
1930
1975
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
1931
- & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface-muted); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; cursor: pointer; }
1976
+
1977
+ & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; cursor: pointer; }
1978
+
1979
+ & .crisp-tabletoolbar-sorted-prefix { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
1980
+ & .crisp-tabletoolbar-sorted-count { font-size: var(--crisp-text-sm); color: color-mix(in srgb, var(--crisp-fg-primary) 73%, transparent); }
1932
1981
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
1933
1982
  }
1934
1983
 
@@ -1972,7 +2021,7 @@
1972
2021
  }
1973
2022
 
1974
2023
  .crisp-text {
1975
- display: inline;
2024
+ vertical-align: baseline;
1976
2025
  }
1977
2026
  .crisp-text--tone_default {
1978
2027
  color: var(--crisp-fg-primary);
@@ -2949,12 +2998,29 @@
2949
2998
  & .crisp-recordstable-search svg { flex-shrink: 0; }
2950
2999
  & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
2951
3000
  & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
2952
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
3001
+ & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
2953
3002
  & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
2954
3003
  & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2955
- & .crisp-recordstable-tool--active { background: var(--crisp-bg-raised); }
3004
+ /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
3005
+ base .tool rule above now already sets; this override existed to
3006
+ distinguish active from a base state that used to be transparent.
3007
+ Kept explicit (not just deleted) so a future change to the base
3008
+ rule's background can't silently change this one too. */
3009
+ & .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
2956
3010
  & .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
2957
3011
  & .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
3012
+ /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
3013
+ trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
3014
+ a dashed ::before (1px dashed, border-radius: inherit, absolutely
3015
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
3016
+ instead (the --active modifier, unchanged) -- confirmed on Sort with
3017
+ an actual sort applied. color-mix (not the raw rgba Attio computes
3018
+ to) so both the border and the fill flip correctly in dark mode, the
3019
+ same reasoning --ghost above already uses. Only wired into
3020
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
3021
+ & .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
3022
+ & .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
3023
+ & .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2958
3024
  & .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
2959
3025
  & .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
2960
3026
  & .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
@@ -3307,24 +3373,12 @@
3307
3373
  }
3308
3374
 
3309
3375
  .crisp-savedviews {
3310
- position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Trigger fill measured rgb(246,247,247); bgRaised's light value is
3311
- #f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
3312
- screenshot/anti-aliasing noise, not a second real colour. Reusing the
3313
- token (Lesson 3 — tokens, not raw px/hex) gets a correct,
3314
- already-established dark-mode fill for free instead of a light-only
3315
- literal; AUTHORING.md Lesson 29 is the case where that shortcut went
3316
- wrong (a flat colour that stayed visible where the reference had
3317
- none) — this is the opposite risk (a token that already flips per
3318
- theme, applied to the closest measured match). */
3319
- & .crisp-savedviews-trigger {
3320
- box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
3321
- height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
3322
- border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
3323
- font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
3324
- cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
3325
- }
3326
- & .crisp-savedviews-trigger:hover,
3327
- & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
3376
+ position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
3377
+ actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
3378
+ chrome View settings and Import/Export both carry
3379
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
3380
+ & .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
3381
+ & .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
3328
3382
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
3329
3383
  & .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
3330
3384
  & .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }