@estiva-app/ui 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +13 -1
  2. package/dist/AttachmentCard.d.ts.map +1 -1
  3. package/dist/Button.d.ts.map +1 -1
  4. package/dist/Card.d.ts.map +1 -1
  5. package/dist/Chip.d.ts.map +1 -1
  6. package/dist/ChipInput.d.ts.map +1 -1
  7. package/dist/CommandPalette.d.ts.map +1 -1
  8. package/dist/DialogShell.d.ts.map +1 -1
  9. package/dist/Lightbox.d.ts.map +1 -1
  10. package/dist/Menu.d.ts.map +1 -1
  11. package/dist/NavItem.d.ts.map +1 -1
  12. package/dist/PersonTrigger.d.ts.map +1 -1
  13. package/dist/Reaction.d.ts.map +1 -1
  14. package/dist/SearchInput.d.ts.map +1 -1
  15. package/dist/SectionLabel.d.ts +9 -2
  16. package/dist/SectionLabel.d.ts.map +1 -1
  17. package/dist/Select.d.ts.map +1 -1
  18. package/dist/TextInput.d.ts.map +1 -1
  19. package/dist/Textarea.d.ts.map +1 -1
  20. package/dist/Tooltip.d.ts.map +1 -1
  21. package/dist/eslint/index.d.ts +4 -0
  22. package/dist/eslint/index.d.ts.map +1 -1
  23. package/dist/eslint/index.js +449 -28
  24. package/dist/eslint/index.js.map +4 -4
  25. package/dist/eslint/looks-of.d.ts +54 -0
  26. package/dist/eslint/looks-of.d.ts.map +1 -0
  27. package/dist/eslint/no-copied-look.d.ts +3 -0
  28. package/dist/eslint/no-copied-look.d.ts.map +1 -0
  29. package/dist/eslint/no-native-title.d.ts +31 -0
  30. package/dist/eslint/no-native-title.d.ts.map +1 -0
  31. package/dist/gates/index.js +1 -1
  32. package/dist/gates/index.js.map +2 -2
  33. package/dist/index.js +171 -133
  34. package/dist/index.js.map +3 -3
  35. package/dist/looks.d.ts +34 -0
  36. package/dist/looks.d.ts.map +1 -0
  37. package/dist/registry/app-3ALZE6XO.js +10 -0
  38. package/dist/registry/app.d.ts.map +1 -1
  39. package/dist/registry/{build-LGFCCOLR.js → build-NDXMVFQ5.js} +3 -3
  40. package/dist/registry/build.d.ts.map +1 -1
  41. package/dist/registry/{chunk-XCBN2EKW.js → chunk-3WJ7ZZ7Z.js} +8 -4
  42. package/dist/registry/chunk-3WJ7ZZ7Z.js.map +7 -0
  43. package/dist/registry/{chunk-E4JNV7PC.js → chunk-PZO4BVV3.js} +2 -1
  44. package/dist/registry/chunk-PZO4BVV3.js.map +7 -0
  45. package/dist/registry/chunk-XVII7P6G.js +595 -0
  46. package/dist/registry/chunk-XVII7P6G.js.map +7 -0
  47. package/dist/registry/cli.js +3 -3
  48. package/dist/registry/index.js +3 -3
  49. package/dist/registry/schema.d.ts +8 -0
  50. package/dist/registry/schema.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/registry.json +347 -90
  53. package/src/AttachmentCard.tsx +2 -0
  54. package/src/Button.tsx +1 -0
  55. package/src/Card.stories.tsx +2 -2
  56. package/src/Card.tsx +1 -0
  57. package/src/Chip.tsx +2 -1
  58. package/src/ChipInput.tsx +4 -2
  59. package/src/CommandPalette.tsx +2 -0
  60. package/src/ContainerHeader.stories.tsx +2 -1
  61. package/src/DialogShell.tsx +1 -0
  62. package/src/Divider.stories.tsx +12 -10
  63. package/src/Lightbox.tsx +1 -0
  64. package/src/Link.stories.tsx +5 -4
  65. package/src/Menu.tsx +3 -1
  66. package/src/NavItem.tsx +1 -0
  67. package/src/PersonTrigger.tsx +1 -0
  68. package/src/Reaction.tsx +3 -1
  69. package/src/ReactionPicker.stories.tsx +3 -2
  70. package/src/ScrollArea.stories.tsx +13 -12
  71. package/src/SearchInput.tsx +5 -4
  72. package/src/SectionLabel.mdx +4 -2
  73. package/src/SectionLabel.stories.tsx +13 -0
  74. package/src/SectionLabel.tsx +19 -2
  75. package/src/Select.tsx +6 -4
  76. package/src/TextInput.tsx +6 -6
  77. package/src/Textarea.tsx +6 -4
  78. package/src/Tooltip.mdx +1 -0
  79. package/src/Tooltip.stories.tsx +3 -2
  80. package/src/Tooltip.tsx +2 -1
  81. package/src/eslint/index.test.ts +16 -9
  82. package/src/eslint/index.ts +19 -6
  83. package/src/eslint/looks-of.ts +302 -0
  84. package/src/eslint/no-copied-look.test.ts +155 -0
  85. package/src/eslint/no-copied-look.ts +203 -0
  86. package/src/eslint/no-native-title.test.ts +67 -0
  87. package/src/eslint/no-native-title.ts +72 -0
  88. package/src/eslint/no-restyled-part.ts +1 -1
  89. package/src/gates/app-checks.ts +1 -1
  90. package/src/gates/create-app.test.ts +1 -1
  91. package/src/gates/gates.test.ts +1 -1
  92. package/src/looks.ts +42 -0
  93. package/src/registry/app.ts +3 -0
  94. package/src/registry/build.ts +8 -0
  95. package/src/registry/registry.test.ts +1 -1
  96. package/src/registry/schema.ts +9 -0
  97. package/dist/registry/app-FXN4EI34.js +0 -10
  98. package/dist/registry/chunk-E4JNV7PC.js.map +0 -7
  99. package/dist/registry/chunk-NJN4MQAM.js +0 -397
  100. package/dist/registry/chunk-NJN4MQAM.js.map +0 -7
  101. package/dist/registry/chunk-XCBN2EKW.js.map +0 -7
  102. /package/dist/registry/{app-FXN4EI34.js.map → app-3ALZE6XO.js.map} +0 -0
  103. /package/dist/registry/{build-LGFCCOLR.js.map → build-NDXMVFQ5.js.map} +0 -0
package/dist/index.js CHANGED
@@ -248,7 +248,10 @@ function Card({
248
248
  /* @__PURE__ */ jsx5(Link, { href, variant: "plain", ref, className: classes, ...anchorProps, children })
249
249
  );
250
250
  }
251
- return /* @__PURE__ */ jsx5("div", { className: classes, ...props, children });
251
+ return (
252
+ // @estiva-escape: N6 (Katerina, 24 September: leave alone): a card that cuts its content to its corners shares four words with the palette's window by coincidence
253
+ /* @__PURE__ */ jsx5("div", { className: classes, ...props, children })
254
+ );
252
255
  }
253
256
 
254
257
  // src/IconButton.tsx
@@ -281,6 +284,20 @@ function Kbd({ children, className }) {
281
284
  );
282
285
  }
283
286
 
287
+ // src/looks.ts
288
+ var FIELD_BOX_CLASSES = "bg-bg-inset border border-border-default hover:border-border-strong focus:border-border-focus rounded-lg";
289
+ var FIELD_TEXT_CLASSES = "text-text-primary placeholder:text-text-muted";
290
+ var FIELD_DISABLED_CLASSES = "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled";
291
+ var FIELD_FOCUS_RING_CLASSES = "signal:transition-shadow signal:focus:shadow-focus-ring";
292
+ var FIELD_SIZE_CLASSES = {
293
+ default: "px-3 py-2 text-input-value",
294
+ small: "h-6 min-h-6 px-2 text-caption"
295
+ };
296
+ var FIELD_SHELL_CLASSES = "bg-bg-inset border border-border-default hover:border-border-strong rounded-lg transition-colors";
297
+ var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
298
+ var CHIP_TEXT_CLASSES = "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums";
299
+ var BARE_INPUT_CLASSES = "bg-transparent text-text-primary placeholder:text-text-muted outline-none";
300
+
284
301
  // src/Tooltip.tsx
285
302
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
286
303
  var OPEN_DELAY = 300;
@@ -288,7 +305,7 @@ function TooltipProvider({ delay = OPEN_DELAY, closeDelay, timeout, children })
288
305
  return /* @__PURE__ */ jsx7(BaseTooltip.Provider, { delay, closeDelay, timeout, children });
289
306
  }
290
307
  function Tooltip({ label, shortcut, className, ...props }) {
291
- return /* @__PURE__ */ jsxs4("div", { role: "tooltip", className: cn("bg-bg-elevated border border-border-default rounded-lg min-h-[30px] max-w-80 flex items-center justify-center gap-1.5 px-2 py-1 shadow-lg", className), ...props, children: [
308
+ return /* @__PURE__ */ jsxs4("div", { role: "tooltip", className: cn(FLOATING_SURFACE_CLASSES, "min-h-[30px] max-w-80 flex items-center justify-center gap-1.5 px-2 py-1", className), ...props, children: [
292
309
  /* @__PURE__ */ jsx7("span", { className: "min-w-0 text-caption text-text-primary break-words", children: label }),
293
310
  shortcut && /* @__PURE__ */ jsx7(Kbd, { children: shortcut })
294
311
  ] });
@@ -635,7 +652,8 @@ function AttachmentCard({
635
652
  }
636
653
  )
637
654
  ] }),
638
- onRemove && /* @__PURE__ */ jsx10(
655
+ onRemove && // @estiva-escape: N4 (Katerina, 24 September: record only): the small round button Lightbox's close also draws; a part if a third appears
656
+ /* @__PURE__ */ jsx10(
639
657
  BaseButton2,
640
658
  {
641
659
  type: "button",
@@ -858,6 +876,7 @@ function Button({
858
876
  size === "default" && (hasLeadingIcon ? "pl-2 pr-3" : "px-2"),
859
877
  size === "small" && (hasLeadingIcon ? "pl-1.5 pr-2" : "px-1.5"),
860
878
  !state.disabled && (variant === "primary" || variant === "resolve") && "bg-accent-primary hover:bg-accent-hover text-text-inverse cursor-pointer signal:font-semibold",
879
+ // @estiva-escape: W2 (Katerina, 24 September: leave alone): an outlined button shares four words with Reaction's pill by coincidence
861
880
  !state.disabled && variant === "outlined" && "border border-border-default hover:bg-bg-hover text-text-primary cursor-pointer",
862
881
  !state.disabled && variant === "muted" && "hover:bg-bg-hover text-text-primary cursor-pointer",
863
882
  !state.disabled && variant === "destructive" && "hover:bg-error-muted text-error-default cursor-pointer",
@@ -981,7 +1000,7 @@ function Chip({ type = "neutral", label, leadingIcon, trailingIcon, className })
981
1000
  // the screenshot diff caught every descender cut off. `clip` is the
982
1001
  // one overflow that leaves the other axis visible. A chip with no cap
983
1002
  // draws exactly as before.
984
- /* @__PURE__ */ jsx16("span", { className: "min-w-0 overflow-x-clip text-ellipsis whitespace-nowrap text-chip signal:font-mono signal:text-small signal:font-semibold signal:tracking-wide signal:tabular-nums", children: label }),
1003
+ /* @__PURE__ */ jsx16("span", { className: cn("min-w-0 overflow-x-clip text-ellipsis whitespace-nowrap signal:tracking-wide", CHIP_TEXT_CLASSES), children: label }),
985
1004
  trailingIcon && /* @__PURE__ */ jsx16("span", { className: "flex size-3 shrink-0 items-center justify-center", children: trailingIcon })
986
1005
  ] });
987
1006
  }
@@ -1051,13 +1070,19 @@ function triggerDisabled(trigger) {
1051
1070
 
1052
1071
  // src/SectionLabel.tsx
1053
1072
  import { jsx as jsx18 } from "react/jsx-runtime";
1054
- function SectionLabel({ children, tone = "primary", className }) {
1073
+ function SectionLabel({
1074
+ children,
1075
+ tone = "primary",
1076
+ truncate = false,
1077
+ className
1078
+ }) {
1055
1079
  return /* @__PURE__ */ jsx18(
1056
1080
  "span",
1057
1081
  {
1058
1082
  className: cn(
1059
1083
  "text-h5 leading-3 signal:font-mono signal:text-small signal:uppercase signal:tracking-widest",
1060
- tone === "secondary" ? "text-text-secondary" : "text-text-primary",
1084
+ tone === "secondary" ? "text-text-secondary" : tone === "muted" ? "text-text-muted" : "text-text-primary",
1085
+ truncate && "min-w-0 truncate",
1061
1086
  className
1062
1087
  ),
1063
1088
  children
@@ -1073,7 +1098,8 @@ function MenuPanel({ children, className, ...props }) {
1073
1098
  "div",
1074
1099
  {
1075
1100
  className: cn(
1076
- "flex flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg",
1101
+ "flex flex-col p-2",
1102
+ FLOATING_SURFACE_CLASSES,
1077
1103
  /*
1078
1104
  * A divider in a menu runs the width of the rows it separates.
1079
1105
  *
@@ -1374,7 +1400,7 @@ function ChipInput({
1374
1400
  filter,
1375
1401
  itemToStringLabel: (option) => option.label,
1376
1402
  children: [
1377
- /* @__PURE__ */ jsxs14(Combobox.Chips, { ref: setBox, className: "bg-bg-inset border border-border-default hover:border-border-strong focus-within:border-border-focus focus-within:hover:border-border-focus rounded-lg px-3 py-1.5 flex flex-wrap items-center gap-1.5 transition-colors min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring", children: [
1403
+ /* @__PURE__ */ jsxs14(Combobox.Chips, { ref: setBox, className: cn(FIELD_SHELL_CLASSES, "focus-within:border-border-focus focus-within:hover:border-border-focus px-3 py-1.5 flex flex-wrap items-center gap-1.5 min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring"), children: [
1378
1404
  value.map((option) => /* @__PURE__ */ jsxs14(Combobox.Chip, { className: cn(CHIP_BOX, chipPadding(!!chipLeading, true)), children: [
1379
1405
  chipLeading && /* @__PURE__ */ jsx20("span", { className: "flex shrink-0 items-center", children: chipLeading(option) }),
1380
1406
  /* @__PURE__ */ jsx20("span", { className: CHIP_LABEL, children: option.label }),
@@ -1388,7 +1414,7 @@ function ChipInput({
1388
1414
  "aria-required": aria["aria-required"],
1389
1415
  ...naming,
1390
1416
  onKeyDown: onInputKeyDown,
1391
- className: "flex-1 min-w-[120px] bg-transparent text-body-2 text-text-primary placeholder:text-text-muted outline-none border-none"
1417
+ className: cn("flex-1 min-w-[120px] text-body-2 border-none", BARE_INPUT_CLASSES)
1392
1418
  }
1393
1419
  )
1394
1420
  ] }),
@@ -1943,28 +1969,31 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
1943
1969
  }
1944
1970
  );
1945
1971
  }
1946
- return /* @__PURE__ */ jsxs21(
1947
- BaseButton5,
1948
- {
1949
- type: "button",
1950
- "aria-haspopup": "menu",
1951
- "aria-expanded": open,
1952
- className: cn(
1953
- "flex h-8 min-h-8 cursor-pointer items-center gap-1.5 rounded-md pl-1.5 pr-1.5 text-body-2 text-text-primary transition-colors hover:bg-bg-hover",
1954
- open && "bg-bg-hover",
1955
- // Under a `Menu` the open state is Base UI's, not a prop: the trigger
1956
- // carries `data-popup-open` while its menu is up, and sets its own
1957
- // `aria-expanded`. Both spellings hold the fill, so this works whether
1958
- // the caller drives it or the menu does.
1959
- "data-[popup-open]:bg-bg-hover",
1960
- className
1961
- ),
1962
- ...props,
1963
- children: [
1964
- /* @__PURE__ */ jsx28(Person, { name, picture, fallback, size: size ?? 22 }),
1965
- /* @__PURE__ */ jsx28(IconChevronDown, { size: 14, stroke: 1.5, className: "shrink-0 text-text-muted" })
1966
- ]
1967
- }
1972
+ return (
1973
+ // @estiva-escape: N5 (Katerina, 24 September: record only): a row that fills on hover, as NavItem also does
1974
+ /* @__PURE__ */ jsxs21(
1975
+ BaseButton5,
1976
+ {
1977
+ type: "button",
1978
+ "aria-haspopup": "menu",
1979
+ "aria-expanded": open,
1980
+ className: cn(
1981
+ "flex h-8 min-h-8 cursor-pointer items-center gap-1.5 rounded-md pl-1.5 pr-1.5 text-body-2 text-text-primary transition-colors hover:bg-bg-hover",
1982
+ open && "bg-bg-hover",
1983
+ // Under a `Menu` the open state is Base UI's, not a prop: the trigger
1984
+ // carries `data-popup-open` while its menu is up, and sets its own
1985
+ // `aria-expanded`. Both spellings hold the fill, so this works whether
1986
+ // the caller drives it or the menu does.
1987
+ "data-[popup-open]:bg-bg-hover",
1988
+ className
1989
+ ),
1990
+ ...props,
1991
+ children: [
1992
+ /* @__PURE__ */ jsx28(Person, { name, picture, fallback, size: size ?? 22 }),
1993
+ /* @__PURE__ */ jsx28(IconChevronDown, { size: 14, stroke: 1.5, className: "shrink-0 text-text-muted" })
1994
+ ]
1995
+ }
1996
+ )
1968
1997
  );
1969
1998
  }
1970
1999
 
@@ -2089,48 +2118,51 @@ var VIEWPORT_PAD4 = 8;
2089
2118
  function Select({ value, onChange, options, size = "default", ariaLabel, placeholder = "Select\u2026", disabled, disabledReason, className, ...aria }) {
2090
2119
  const selected = options.find((o) => o.value === value);
2091
2120
  const held = Boolean(disabledReason);
2092
- const trigger = /* @__PURE__ */ jsxs23(
2093
- BaseSelect.Trigger,
2094
- {
2095
- "aria-label": ariaLabel,
2096
- "aria-required": aria["aria-required"],
2097
- "aria-disabled": held || void 0,
2098
- className: cn(
2099
- /*
2100
- * `min-w-0 max-w-full`: a trigger must never outgrow its container
2101
- * (Katerina, 2026-09-01 — a long label stretched the files panel's
2102
- * Lead row past its card). In a flex row the default min-width:auto
2103
- * forbids shrinking below the label's width, which is what kept
2104
- * `truncate` from ever engaging; in a block container max-w-full is
2105
- * the cap. Full-width callers are unaffected.
2106
- */
2107
- "flex w-full min-w-0 max-w-full items-center justify-between gap-2 rounded-lg border bg-bg-inset text-left",
2108
- "border-border-default text-text-primary outline-none transition-colors",
2109
- // The focused border survives a hover: hover alone strengthens the
2110
- // hairline, but hover while focused must not grey the focus colour —
2111
- // the stacked variant outranks plain hover by specificity.
2112
- !held && "hover:border-border-strong focus-visible:hover:border-border-focus aria-expanded:hover:border-border-focus",
2113
- "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled",
2114
- held && "cursor-not-allowed bg-bg-disabled text-text-disabled",
2115
- "focus-visible:border-border-focus aria-expanded:border-border-focus",
2116
- "signal:transition-shadow signal:focus-visible:shadow-focus-ring",
2117
- size === "default" && "px-3 py-2 text-input-value",
2118
- size === "small" && "h-6 min-h-6 px-2 text-caption",
2119
- className
2120
- ),
2121
- children: [
2122
- /* @__PURE__ */ jsxs23("span", { className: cn("flex min-w-0 items-center gap-2", !selected && "text-text-muted"), children: [
2123
- selected?.leading && /* @__PURE__ */ jsx31("span", { className: "flex shrink-0 items-center", children: selected.leading }),
2124
- /* @__PURE__ */ jsx31(BaseSelect.Value, { className: "truncate", children: () => selected?.label ?? placeholder })
2125
- ] }),
2126
- /* @__PURE__ */ jsx31(
2127
- BaseSelect.Icon,
2128
- {
2129
- render: /* @__PURE__ */ jsx31(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
2130
- }
2131
- )
2132
- ]
2133
- }
2121
+ const trigger = (
2122
+ // @estiva-escape: N1 (Katerina, 24 September: record only): Select's box drops the hover while held, so it cannot take FIELD_SHELL_CLASSES as it is
2123
+ /* @__PURE__ */ jsxs23(
2124
+ BaseSelect.Trigger,
2125
+ {
2126
+ "aria-label": ariaLabel,
2127
+ "aria-required": aria["aria-required"],
2128
+ "aria-disabled": held || void 0,
2129
+ className: cn(
2130
+ /*
2131
+ * `min-w-0 max-w-full`: a trigger must never outgrow its container
2132
+ * (Katerina, 2026-09-01 — a long label stretched the files panel's
2133
+ * Lead row past its card). In a flex row the default min-width:auto
2134
+ * forbids shrinking below the label's width, which is what kept
2135
+ * `truncate` from ever engaging; in a block container max-w-full is
2136
+ * the cap. Full-width callers are unaffected.
2137
+ */
2138
+ "flex w-full min-w-0 max-w-full items-center justify-between gap-2 rounded-lg border bg-bg-inset text-left",
2139
+ "border-border-default outline-none transition-colors",
2140
+ FIELD_TEXT_CLASSES,
2141
+ // The focused border survives a hover: hover alone strengthens the
2142
+ // hairline, but hover while focused must not grey the focus colour —
2143
+ // the stacked variant outranks plain hover by specificity.
2144
+ !held && "hover:border-border-strong focus-visible:hover:border-border-focus aria-expanded:hover:border-border-focus",
2145
+ FIELD_DISABLED_CLASSES,
2146
+ held && "cursor-not-allowed bg-bg-disabled text-text-disabled",
2147
+ "focus-visible:border-border-focus aria-expanded:border-border-focus",
2148
+ "signal:transition-shadow signal:focus-visible:shadow-focus-ring",
2149
+ FIELD_SIZE_CLASSES[size],
2150
+ className
2151
+ ),
2152
+ children: [
2153
+ /* @__PURE__ */ jsxs23("span", { className: cn("flex min-w-0 items-center gap-2", !selected && "text-text-muted"), children: [
2154
+ selected?.leading && /* @__PURE__ */ jsx31("span", { className: "flex shrink-0 items-center", children: selected.leading }),
2155
+ /* @__PURE__ */ jsx31(BaseSelect.Value, { className: "truncate", children: () => selected?.label ?? placeholder })
2156
+ ] }),
2157
+ /* @__PURE__ */ jsx31(
2158
+ BaseSelect.Icon,
2159
+ {
2160
+ render: /* @__PURE__ */ jsx31(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
2161
+ }
2162
+ )
2163
+ ]
2164
+ }
2165
+ )
2134
2166
  );
2135
2167
  return /* @__PURE__ */ jsxs23(
2136
2168
  BaseSelect.Root,
@@ -2202,14 +2234,13 @@ var TextInput = forwardRef2(function TextInput2({ className, type = "text", size
2202
2234
  // The border strengthens on hover, as Select's and ChipInput's do (Katerina, 16 September:
2203
2235
  // "aren't there hover states in text input and text area?"). Focus comes after hover in
2204
2236
  // Tailwind's order, so a focused field keeps the focus border under the pointer.
2205
- "bg-bg-inset border border-border-default hover:border-border-strong focus:border-border-focus rounded-lg",
2237
+ FIELD_BOX_CLASSES,
2206
2238
  // The small size is the small Select's trigger, class for class.
2207
- size === "default" && "px-3 py-2 text-input-value",
2208
- size === "small" && "h-6 min-h-6 px-2 text-caption",
2209
- "text-text-primary placeholder:text-text-muted",
2239
+ FIELD_SIZE_CLASSES[size],
2240
+ FIELD_TEXT_CLASSES,
2210
2241
  "outline-none transition-colors",
2211
- "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled",
2212
- "signal:transition-shadow signal:focus:shadow-focus-ring",
2242
+ FIELD_DISABLED_CLASSES,
2243
+ FIELD_FOCUS_RING_CLASSES,
2213
2244
  className
2214
2245
  ),
2215
2246
  ...props
@@ -2231,11 +2262,12 @@ var Textarea = forwardRef3(function Textarea2({ className, ...props }, ref) {
2231
2262
  // The border strengthens on hover, as Select's and ChipInput's do (Katerina, 16 September:
2232
2263
  // "aren't there hover states in text input and text area?"). Focus comes after hover in
2233
2264
  // Tailwind's order, so a focused field keeps the focus border under the pointer.
2234
- "bg-bg-inset border border-border-default hover:border-border-strong focus:border-border-focus rounded-lg px-3 py-2",
2235
- "text-input-value text-text-primary placeholder:text-text-muted",
2265
+ FIELD_BOX_CLASSES,
2266
+ FIELD_SIZE_CLASSES.default,
2267
+ FIELD_TEXT_CLASSES,
2236
2268
  "resize-none outline-none transition-colors",
2237
- "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled",
2238
- "signal:transition-shadow signal:focus:shadow-focus-ring",
2269
+ FIELD_DISABLED_CLASSES,
2270
+ FIELD_FOCUS_RING_CLASSES,
2239
2271
  className
2240
2272
  ),
2241
2273
  ...props
@@ -2579,26 +2611,29 @@ function Breadcrumb({ items, className }) {
2579
2611
  // src/NavItem.tsx
2580
2612
  import { jsx as jsx41, jsxs as jsxs28 } from "react/jsx-runtime";
2581
2613
  function NavItem({ label, href, count, countLabel, active = false, icon, className, ...props }) {
2582
- return /* @__PURE__ */ jsxs28(
2583
- "a",
2584
- {
2585
- href,
2586
- "aria-current": active ? "page" : void 0,
2587
- className: cn(
2588
- // shrink-0: the row keeps its 32px inside an overflowing flex column
2589
- // — without it the list compresses instead of scrolling (the missing-
2590
- // shrink-0 family; Katerina, 2026-09-02).
2591
- "flex h-8 min-w-0 shrink-0 items-center gap-2 rounded-md px-2 text-body-2 transition-colors",
2592
- active ? "bg-bg-active text-text-primary" : "text-text-secondary hover:bg-bg-hover hover:text-text-primary",
2593
- className
2594
- ),
2595
- ...props,
2596
- children: [
2597
- icon && /* @__PURE__ */ jsx41("span", { className: "flex shrink-0 items-center", children: icon }),
2598
- /* @__PURE__ */ jsx41("span", { className: "flex-1 truncate", children: label }),
2599
- count ? /* @__PURE__ */ jsx41(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx41("span", { className: "min-w-4 shrink-0 text-center font-mono text-caption tabular-nums text-text-muted", children: count }) }) : null
2600
- ]
2601
- }
2614
+ return (
2615
+ // @estiva-escape: N5 (Katerina, 24 September: record only): a row that fills on hover, as PersonTrigger's row also does
2616
+ /* @__PURE__ */ jsxs28(
2617
+ "a",
2618
+ {
2619
+ href,
2620
+ "aria-current": active ? "page" : void 0,
2621
+ className: cn(
2622
+ // shrink-0: the row keeps its 32px inside an overflowing flex column
2623
+ // — without it the list compresses instead of scrolling (the missing-
2624
+ // shrink-0 family; Katerina, 2026-09-02).
2625
+ "flex h-8 min-w-0 shrink-0 items-center gap-2 rounded-md px-2 text-body-2 transition-colors",
2626
+ active ? "bg-bg-active text-text-primary" : "text-text-secondary hover:bg-bg-hover hover:text-text-primary",
2627
+ className
2628
+ ),
2629
+ ...props,
2630
+ children: [
2631
+ icon && /* @__PURE__ */ jsx41("span", { className: "flex shrink-0 items-center", children: icon }),
2632
+ /* @__PURE__ */ jsx41("span", { className: "flex-1 truncate", children: label }),
2633
+ count ? /* @__PURE__ */ jsx41(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx41("span", { className: "min-w-4 shrink-0 text-center font-mono text-caption tabular-nums text-text-muted", children: count }) }) : null
2634
+ ]
2635
+ }
2636
+ )
2602
2637
  );
2603
2638
  }
2604
2639
 
@@ -2847,32 +2882,35 @@ function Property({ label, layout = "row", children, className }) {
2847
2882
  import { Toggle } from "@base-ui/react/toggle";
2848
2883
  import { jsx as jsx50, jsxs as jsxs33 } from "react/jsx-runtime";
2849
2884
  function Reaction({ emoji, count, pressed = false, className, ...props }) {
2850
- return /* @__PURE__ */ jsxs33(
2851
- Toggle,
2852
- {
2853
- pressed,
2854
- className: cn(
2855
- // Chip's pill at a control's height, so a reaction and a status chip
2856
- // read as the same family — 24px matches Button `small`.
2857
- "inline-flex h-6 min-h-6 items-center justify-center gap-1.5 rounded-full px-2",
2858
- "border transition-colors",
2859
- "disabled:cursor-not-allowed disabled:opacity-50",
2860
- pressed ? (
2861
- /* In the ship theme the accent on its own wash measures 2.70:1
2862
- (Finding 4), and the count was the thing that vanished
2863
- (Katerina, 2026-09-08). The number reads in the text colour
2864
- there; the accent keeps the edge and the fill. Signal keeps
2865
- Peek's blue-on-wash, which reads. */
2866
- "border-accent-primary bg-accent-muted text-accent-primary hover:border-accent-hover ship:text-text-primary"
2867
- ) : "border-border-default bg-bg-inset text-text-primary hover:border-border-strong hover:bg-bg-hover",
2868
- className
2869
- ),
2870
- ...props,
2871
- children: [
2872
- /* @__PURE__ */ jsx50("span", { "aria-hidden": "true", className: "shrink-0 text-body-1 leading-none", children: emoji }),
2873
- /* @__PURE__ */ jsx50("span", { className: "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums", children: count })
2874
- ]
2875
- }
2885
+ return (
2886
+ // @estiva-escape: W1 (Katerina, 24 September: leave alone): a reaction pill shares four words with a field's box by coincidence
2887
+ /* @__PURE__ */ jsxs33(
2888
+ Toggle,
2889
+ {
2890
+ pressed,
2891
+ className: cn(
2892
+ // Chip's pill at a control's height, so a reaction and a status chip
2893
+ // read as the same family — 24px matches Button `small`.
2894
+ "inline-flex h-6 min-h-6 items-center justify-center gap-1.5 rounded-full px-2",
2895
+ "border transition-colors",
2896
+ "disabled:cursor-not-allowed disabled:opacity-50",
2897
+ pressed ? (
2898
+ /* In the ship theme the accent on its own wash measures 2.70:1
2899
+ (Finding 4), and the count was the thing that vanished
2900
+ (Katerina, 2026-09-08). The number reads in the text colour
2901
+ there; the accent keeps the edge and the fill. Signal keeps
2902
+ Peek's blue-on-wash, which reads. */
2903
+ "border-accent-primary bg-accent-muted text-accent-primary hover:border-accent-hover ship:text-text-primary"
2904
+ ) : "border-border-default bg-bg-inset text-text-primary hover:border-border-strong hover:bg-bg-hover",
2905
+ className
2906
+ ),
2907
+ ...props,
2908
+ children: [
2909
+ /* @__PURE__ */ jsx50("span", { "aria-hidden": "true", className: "shrink-0 text-body-1 leading-none", children: emoji }),
2910
+ /* @__PURE__ */ jsx50("span", { className: CHIP_TEXT_CLASSES, children: count })
2911
+ ]
2912
+ }
2913
+ )
2876
2914
  );
2877
2915
  }
2878
2916
 
@@ -2885,18 +2923,18 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
2885
2923
  "div",
2886
2924
  {
2887
2925
  className: cn(
2888
- "flex gap-2 items-center px-3 py-2 rounded-lg",
2889
- "bg-bg-inset border border-border-default",
2926
+ "flex gap-2 items-center px-3 py-2",
2927
+ FIELD_SHELL_CLASSES,
2890
2928
  // Hover strengthens the border as it does on every other field (16 September). Here that
2891
2929
  // is also the focus look, which was the stronger border before hover existed.
2892
- "hover:border-border-strong focus-within:border-border-strong transition-colors",
2930
+ "focus-within:border-border-strong",
2893
2931
  className
2894
2932
  ),
2895
2933
  children: [
2896
2934
  /* @__PURE__ */ jsx51(
2897
2935
  Input3,
2898
2936
  {
2899
- className: "flex-1 min-w-0 bg-transparent text-input-value text-text-primary placeholder:text-text-muted outline-none",
2937
+ className: cn("flex-1 min-w-0 text-input-value", BARE_INPUT_CLASSES),
2900
2938
  placeholder,
2901
2939
  ...props
2902
2940
  }