@ai-matrx/design-system 0.86.355 → 0.86.357

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 (88) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/auth-gate-dialog.js +2 -2
  3. package/dist/carousel.js +8 -7
  4. package/dist/carousel.js.map +1 -1
  5. package/dist/{chunk-UWTODOMD.js → chunk-2BQAHLU3.js} +4 -4
  6. package/dist/chunk-5Z5GTXKL.js +79 -0
  7. package/dist/chunk-5Z5GTXKL.js.map +1 -0
  8. package/dist/{chunk-XXZA2JUP.js → chunk-ACZALXD6.js} +4 -4
  9. package/dist/{chunk-ZRHVA3DL.js → chunk-AV5C4YLL.js} +2 -1
  10. package/dist/{chunk-ZRHVA3DL.js.map → chunk-AV5C4YLL.js.map} +1 -1
  11. package/dist/{chunk-4WSXAP24.js → chunk-BB5LA7FQ.js} +3 -1
  12. package/dist/chunk-BB5LA7FQ.js.map +1 -0
  13. package/dist/{chunk-RLM4RSED.js → chunk-GUGBVQ4T.js} +6 -4
  14. package/dist/{chunk-RLM4RSED.js.map → chunk-GUGBVQ4T.js.map} +1 -1
  15. package/dist/{chunk-LJKF267O.js → chunk-HS6GMHKZ.js} +18 -76
  16. package/dist/chunk-HS6GMHKZ.js.map +1 -0
  17. package/dist/{chunk-ZMUPFGQO.js → chunk-TV2IDHPZ.js} +2 -2
  18. package/dist/{chunk-CFUXEGW2.js → chunk-XZGYKKCE.js} +2 -2
  19. package/dist/clipboard-fallback-dialog.js +2 -2
  20. package/dist/controls.cjs +16 -3
  21. package/dist/controls.cjs.map +1 -1
  22. package/dist/controls.js +6 -5
  23. package/dist/data-table/data-row-window.js +3 -3
  24. package/dist/data-table/drill-chart.cjs +15 -3
  25. package/dist/data-table/drill-chart.cjs.map +1 -1
  26. package/dist/data-table/drill-chart.js +2 -1
  27. package/dist/data-table/drill-chart.js.map +1 -1
  28. package/dist/data-table/index.cjs +15 -3
  29. package/dist/data-table/index.cjs.map +1 -1
  30. package/dist/data-table/index.js +8 -7
  31. package/dist/data-table/index.js.map +1 -1
  32. package/dist/desktop.cjs +26 -5
  33. package/dist/desktop.cjs.map +1 -1
  34. package/dist/desktop.js +15 -7
  35. package/dist/desktop.js.map +1 -1
  36. package/dist/email-input-dialog.cjs +15 -3
  37. package/dist/email-input-dialog.cjs.map +1 -1
  38. package/dist/email-input-dialog.js +4 -3
  39. package/dist/email-input-dialog.js.map +1 -1
  40. package/dist/error-sentence.cjs +130 -0
  41. package/dist/error-sentence.cjs.map +1 -0
  42. package/dist/error-sentence.d.cts +49 -0
  43. package/dist/error-sentence.d.ts +49 -0
  44. package/dist/error-sentence.js +22 -0
  45. package/dist/error-sentence.js.map +1 -0
  46. package/dist/index.cjs +17 -4
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +3 -1
  49. package/dist/index.d.ts +3 -1
  50. package/dist/index.js +18 -16
  51. package/dist/index.js.map +1 -1
  52. package/dist/item.js +2 -2
  53. package/dist/lite-table.cjs +15 -3
  54. package/dist/lite-table.cjs.map +1 -1
  55. package/dist/lite-table.js +3 -2
  56. package/dist/lite-table.js.map +1 -1
  57. package/dist/notice-stack.cjs +2 -0
  58. package/dist/notice-stack.cjs.map +1 -1
  59. package/dist/notice-stack.d.cts +11 -1
  60. package/dist/notice-stack.d.ts +11 -1
  61. package/dist/notice-stack.js +3 -1
  62. package/dist/record-list.cjs +15 -3
  63. package/dist/record-list.cjs.map +1 -1
  64. package/dist/record-list.js +3 -2
  65. package/dist/record-list.js.map +1 -1
  66. package/dist/sandbox-gate-dialog.js +2 -2
  67. package/dist/scope-mismatch-dialog.js +2 -2
  68. package/dist/settings.cjs +1 -0
  69. package/dist/settings.cjs.map +1 -1
  70. package/dist/settings.js +1 -1
  71. package/dist/swipe-deck.cjs +25 -4
  72. package/dist/swipe-deck.cjs.map +1 -1
  73. package/dist/swipe-deck.css +2 -0
  74. package/dist/swipe-deck.js +12 -2
  75. package/dist/swipe-deck.js.map +1 -1
  76. package/dist/text-input-dialog.cjs +15 -3
  77. package/dist/text-input-dialog.cjs.map +1 -1
  78. package/dist/text-input-dialog.js +4 -3
  79. package/dist/text-input-dialog.js.map +1 -1
  80. package/dist/value-prompts-dialog.js +2 -2
  81. package/dist/writing-field.js +2 -2
  82. package/package.json +12 -2
  83. package/dist/chunk-4WSXAP24.js.map +0 -1
  84. package/dist/chunk-LJKF267O.js.map +0 -1
  85. /package/dist/{chunk-UWTODOMD.js.map → chunk-2BQAHLU3.js.map} +0 -0
  86. /package/dist/{chunk-XXZA2JUP.js.map → chunk-ACZALXD6.js.map} +0 -0
  87. /package/dist/{chunk-ZMUPFGQO.js.map → chunk-TV2IDHPZ.js.map} +0 -0
  88. /package/dist/{chunk-CFUXEGW2.js.map → chunk-XZGYKKCE.js.map} +0 -0
@@ -6,12 +6,13 @@ import {
6
6
  ReadEmpty,
7
7
  ReadStaleNotice,
8
8
  readOf
9
- } from "./chunk-RLM4RSED.js";
9
+ } from "./chunk-GUGBVQ4T.js";
10
10
  import {
11
11
  MAC_TABLE_ATTRIBUTE,
12
12
  MAC_TABLE_SLOTS
13
13
  } from "./chunk-5JZSBNEY.js";
14
- import "./chunk-LJKF267O.js";
14
+ import "./chunk-5Z5GTXKL.js";
15
+ import "./chunk-HS6GMHKZ.js";
15
16
  import {
16
17
  getTapTargetLinkComponent
17
18
  } from "./chunk-WFBPCMPF.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lite-table/LiteTable.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * LiteTable — THE LIGHT SORTABLE TABLE (2026-10-17, Arman: \"so there is no\n * excuse for creating hand-rolled versions in the app\").\n *\n * For a small, fully loaded set (up to a few hundred rows) where the full\n * `MatrxDataTable` is more than the page needs: declared columns, click-to-sort\n * headers, the honest read states, a row that opens or links, copy a row, two\n * densities, and a phone layout (each row a labeled record).\n *\n * It takes the SAME `MatrxColumnDef<T>` and the same core props as\n * `MatrxDataTable` (`data`, `columns`, `getRowId`, `read`, `emptyState`,\n * `defaultSort`, `onRowOpen`, `getRowHref`, `density`, `rowClassName`), so a\n * page graduates to the full table by changing one import. Column fields it\n * does not draw (filters, editing, pinning, KPIs …) are ignored, never wrong.\n *\n * It imports only TYPES from the data table: the bundle never carries the grid\n * (guarded by `scripts/section-isolation.json`).\n *\n * THE MAC TABLE (2026-10-17, Arman: \"I love the mac table\"): it draws the desktop app's\n * `DataTable` — NSTableView's 24 pt rows under a 22 pt header, 13 pt, stripes, hairlines, the\n * accent selection — from the same `data-slot` vocabulary and the same values\n * (`../mac-table/tokens.ts`, drawn by `mac-table.css`; `mac-table-parity.test.ts` keeps them equal).\n */\n\nimport {\n useCallback,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { useIsMobile } from \"@ai-matrx/kit/media-query\";\nimport { copyText } from \"@ai-matrx/kit/clipboard\";\nimport { openOutbound } from \"@ai-matrx/kit/outbound\";\nimport type {\n MatrxColumnDef,\n MatrxDataTableDensity,\n MatrxDataTableEmptyState,\n SortState,\n} from \"../data-table/types\";\nimport { ReadEmpty, ReadStaleNotice, type ReadOutcome } from \"../read-state/read-gate\";\nimport { ListEmptyView } from \"../read-state/list-empty-view\";\nimport { getTapTargetLinkComponent } from \"../tap-target/link\";\nimport { cn } from \"../cn\";\nimport { Button } from \"../controls/controls\";\nimport { ChevronDownIcon, CheckIcon, CopyIcon } from \"../icons\";\nimport { MAC_TABLE_ATTRIBUTE, MAC_TABLE_SLOTS } from \"../mac-table/tokens\";\n\nexport interface LiteTableProps<T> {\n data: T[];\n columns: MatrxColumnDef<T>[];\n getRowId: (row: T) => string;\n /** The read behind `data`: the empty view shows only after a read that succeeded. */\n read?: ReadOutcome | undefined;\n /** Shown after a successful read with no rows. The full table's object form, or any node. */\n emptyState?: MatrxDataTableEmptyState | ReactNode;\n /** First sort (uncontrolled). */\n defaultSort?: SortState | null | undefined;\n /** Controlled sort. */\n sort?: SortState | null | undefined;\n onSortChange?: ((sort: SortState | null) => void) | undefined;\n /** Plain row click / Enter. */\n onRowOpen?: ((row: T) => void) | undefined;\n /** The row's page: its first column becomes a real link; a plain row click follows it. */\n getRowHref?: ((row: T) => string | undefined) | undefined;\n /** `condensed` (Finder's 20 pt rows) · `normal` (AppKit's 24 pt, default) · `spacious` (36 pt). */\n density?: MatrxDataTableDensity | undefined;\n /** The current row (drawn selected: gray, the accent while the table has focus). */\n selectedKey?: string | null | undefined;\n /** A copy button on each row (its visible values, one \"Label: value\" per line). Default true. */\n copyRows?: boolean | undefined;\n /** Accessible name of the table. */\n ariaLabel?: string | undefined;\n /** Scroll inside this height with the header held. */\n maxHeight?: number | string | undefined;\n /** Phones: `cards` (default) draws each row as a labeled record; `scroll` keeps the table. */\n mobile?: \"cards\" | \"scroll\" | undefined;\n rowClassName?: ((row: T, index: number) => string | undefined) | undefined;\n className?: string | undefined;\n}\n\n/** A column's stable id: `id`, else `accessorKey`, else its position. */\nexport function liteColumnId<T>(column: MatrxColumnDef<T>, index: number): string {\n return column.id ?? column.accessorKey ?? `col-${index}`;\n}\n\n/** A column's plain-text name. */\nexport function liteColumnLabel<T>(column: MatrxColumnDef<T>, index: number): string {\n if (column.label) return column.label;\n if (typeof column.header === \"string\") return column.header;\n return liteColumnId(column, index);\n}\n\nfunction rawValue<T>(column: MatrxColumnDef<T>, row: T): unknown {\n if (column.accessorFn) return column.accessorFn(row);\n if (column.accessorKey) return (row as Record<string, unknown>)[column.accessorKey];\n return undefined;\n}\n\nfunction hasValue<T>(column: MatrxColumnDef<T>): boolean {\n return Boolean(column.sortValue || column.accessorFn || column.accessorKey);\n}\n\nfunction isSortable<T>(column: MatrxColumnDef<T>): boolean {\n return column.sortable !== false && hasValue(column);\n}\n\nfunction sortKey<T>(column: MatrxColumnDef<T>, row: T): unknown {\n return column.sortValue ? column.sortValue(row) : rawValue(column, row);\n}\n\nfunction asText(value: unknown): string {\n if (value == null) return \"\";\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map(asText).filter(Boolean).join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/** Null/empty last in both directions; numbers and dates by value; text naturally (2 before 10). */\nexport function compareLiteValues(a: unknown, b: unknown): number {\n const aEmpty = a == null || a === \"\";\n const bEmpty = b == null || b === \"\";\n if (aEmpty || bEmpty) return aEmpty === bEmpty ? 0 : aEmpty ? 1 : -1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return Number(a) - Number(b);\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n return asText(a).localeCompare(asText(b), undefined, { numeric: true, sensitivity: \"base\" });\n}\n\n/** Rows sorted by `sort` (a copy; stable). Unknown ids leave the order alone. */\nexport function sortLiteRows<T>(rows: T[], columns: MatrxColumnDef<T>[], sort: SortState | null | undefined): T[] {\n if (!sort) return rows;\n const index = columns.findIndex((c, i) => liteColumnId(c, i) === sort.id);\n const column = columns[index];\n if (!column || !hasValue(column)) return rows;\n const sign = sort.direction === \"desc\" ? -1 : 1;\n return rows\n .map((row, i) => ({ row, i, key: sortKey(column, row) }))\n .sort((x, y) => {\n const xEmpty = x.key == null || x.key === \"\";\n const yEmpty = y.key == null || y.key === \"\";\n // Empty values stay last whichever way the column runs.\n if (xEmpty || yEmpty) return xEmpty === yEmpty ? x.i - y.i : xEmpty ? 1 : -1;\n return compareLiteValues(x.key, y.key) * sign || x.i - y.i;\n })\n .map((x) => x.row);\n}\n\n/** What Copy puts on the clipboard for one row: \"Label: value\" per column that has a value. */\nexport function liteRowText<T>(row: T, columns: MatrxColumnDef<T>[]): string {\n return columns\n .map((column, i) => {\n if (column.hidden) return null;\n const value = column.copyValue ? column.copyValue(row) : rawValue(column, row);\n if (!column.copyValue && !column.accessorFn && !column.accessorKey) return null;\n return `${liteColumnLabel(column, i)}: ${asText(value)}`;\n })\n .filter((line): line is string => line !== null)\n .join(\"\\n\");\n}\n\nfunction nextSort(current: SortState | null, id: string, first: \"asc\" | \"desc\"): SortState | null {\n if (!current || current.id !== id) return { id, direction: first };\n if (current.direction === first) return { id, direction: first === \"asc\" ? \"desc\" : \"asc\" };\n return null;\n}\n\nfunction CopyRowButton({ text, label }: { text: string; label: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <Button\n type=\"button\"\n variant=\"quiet\"\n aria-label={`Copy ${label}`}\n icon={copied ? <CheckIcon /> : <CopyIcon />}\n title=\"Copy row\"\n data-lite-copy=\"\"\n onClick={(event) => {\n event.stopPropagation();\n void copyText(text).then((ok) => {\n if (!ok) return;\n setCopied(true);\n setTimeout(() => setCopied(false), 1200);\n });\n }}\n className=\"shrink-0\"\n />\n );\n}\n\nexport function LiteTable<T>({\n data,\n columns,\n getRowId,\n read,\n emptyState = { title: \"Nothing here yet\" },\n defaultSort = null,\n sort: sortProp,\n onSortChange,\n onRowOpen,\n getRowHref,\n density = \"normal\",\n selectedKey,\n copyRows = true,\n ariaLabel,\n maxHeight,\n mobile = \"cards\",\n rowClassName,\n className,\n}: LiteTableProps<T>) {\n const isMobile = useIsMobile();\n const [ownSort, setOwnSort] = useState<SortState | null>(defaultSort ?? null);\n const sort = sortProp !== undefined ? sortProp : ownSort;\n const setSort = useCallback(\n (next: SortState | null) => {\n if (sortProp === undefined) setOwnSort(next);\n onSortChange?.(next);\n },\n [sortProp, onSortChange],\n );\n const visible = useMemo(\n () => columns.map((column, index) => ({ column, index, id: liteColumnId(column, index) })).filter(({ column }) => !column.hidden),\n [columns],\n );\n const rows = useMemo(() => sortLiteRows(data, columns, sort), [data, columns, sort]);\n const bodyRef = useRef<HTMLTableSectionElement | HTMLDivElement | null>(null);\n const Link = (getTapTargetLinkComponent() ?? \"a\") as unknown as React.ComponentType<React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string; prefetch?: boolean }>;\n const registeredLink = getTapTargetLinkComponent() !== null;\n const opens = Boolean(onRowOpen || getRowHref);\n\n if (rows.length === 0) {\n return (\n <div className={className} data-lite-table=\"\" {...{ [MAC_TABLE_ATTRIBUTE]: \"\" }}>\n <ReadEmpty read={read}>\n <ListEmptyView emptyState={emptyState} />\n </ReadEmpty>\n </div>\n );\n }\n\n const open = (row: T, event: MouseEvent | KeyboardEvent, rowEl: HTMLElement) => {\n const href = getRowHref?.(row);\n const modified = \"metaKey\" in event && (event.metaKey || event.ctrlKey || event.shiftKey);\n if (href && modified) {\n openOutbound(href, \"_blank\", \"noopener\");\n return;\n }\n if (onRowOpen) {\n onRowOpen(row);\n return;\n }\n // The first column's anchor carries the host's router: following it keeps client navigation.\n if (href) rowEl.querySelector<HTMLAnchorElement>(\"a[data-lite-link]\")?.click();\n };\n\n const onRowKeyDown = (row: T) => (event: KeyboardEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === \"Enter\") {\n event.preventDefault();\n open(row, event, event.currentTarget);\n return;\n }\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\" && event.key !== \"Home\" && event.key !== \"End\") return;\n const all = Array.from(bodyRef.current?.querySelectorAll<HTMLElement>(\"[data-lite-row]\") ?? []);\n const at = all.indexOf(event.currentTarget);\n const to =\n event.key === \"Home\" ? 0 : event.key === \"End\" ? all.length - 1 : event.key === \"ArrowDown\" ? at + 1 : at - 1;\n const target = all[Math.max(0, Math.min(all.length - 1, to))];\n if (target) {\n event.preventDefault();\n target.focus();\n }\n };\n\n const onRowClick = (row: T) => (event: MouseEvent<HTMLElement>) => {\n // A click on a control or link inside the row is that control's, never the row's.\n if ((event.target as HTMLElement).closest(\"a,button,input,select,textarea,[role=menuitem]\")) return;\n open(row, event, event.currentTarget);\n };\n\n const cellContent = (column: MatrxColumnDef<T>, row: T, rowIndex: number, first: boolean): ReactNode => {\n const content = column.cell ? column.cell(row, rowIndex) : asText(rawValue(column, row));\n const href = column.href?.(row) ?? (first ? getRowHref?.(row) : undefined);\n if (!href) return content;\n return (\n <Link\n href={href}\n data-lite-link=\"\"\n {...(registeredLink ? { prefetch: false } : {})}\n className=\"text-inherit underline-offset-2 hover:underline outline-none focus-visible:underline\"\n >\n {content}\n </Link>\n );\n };\n\n const copyCell = (row: T, rowId: string) =>\n copyRows ? <CopyRowButton text={liteRowText(row, columns)} label={rowId} /> : null;\n\n const macRoot = { [MAC_TABLE_ATTRIBUTE]: \"\", \"data-density\": density };\n\n if (isMobile && mobile === \"cards\") {\n const [titleCol, ...restCols] = visible.filter(({ column }) => !column.mobileHidden);\n return (\n <div className={cn(\"flex flex-col gap-2\", className)} data-lite-table=\"\" data-lite-layout=\"cards\" {...macRoot}>\n <ReadStaleNotice read={read} />\n <div ref={(el) => void (bodyRef.current = el)} role=\"list\" aria-label={ariaLabel} data-mac-cards=\"\" className=\"flex flex-col\">\n {rows.map((row, rowIndex) => {\n const rowId = getRowId(row);\n return (\n <div\n key={rowId}\n role=\"listitem\"\n data-lite-row=\"\"\n data-mac-card=\"\"\n data-opens={opens ? \"\" : undefined}\n data-selected={selectedKey === rowId ? \"\" : undefined}\n tabIndex={opens ? 0 : undefined}\n onClick={opens ? onRowClick(row) : undefined}\n onKeyDown={onRowKeyDown(row)}\n className={cn(\n \"group/lite-row\",\n opens && \"cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring\",\n rowClassName?.(row, rowIndex),\n )}\n >\n <div className=\"flex items-start gap-2\">\n <div className=\"min-w-0 flex-1 font-semibold\">\n {titleCol ? cellContent(titleCol.column, row, rowIndex, true) : null}\n </div>\n {copyCell(row, rowId)}\n </div>\n {restCols.length > 0 ? (\n <dl className=\"mt-1 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5\">\n {restCols.map(({ column, index, id }) => (\n <div key={id} className=\"contents\">\n <dt data-mac-card-label=\"\">{liteColumnLabel(column, index)}</dt>\n <dd className=\"min-w-0\">{cellContent(column, row, rowIndex, false)}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n\n let stripe = 0;\n return (\n <div className={cn(\"flex min-w-0 flex-col gap-2\", className)} data-lite-table=\"\" {...macRoot}>\n <ReadStaleNotice read={read} />\n <div\n data-slot={MAC_TABLE_SLOTS.table}\n className={cn(\"min-w-0 overflow-x-auto\", maxHeight !== undefined && \"overflow-y-auto\")}\n style={maxHeight !== undefined ? { maxHeight } : undefined}\n >\n <table aria-label={ariaLabel}>\n <thead>\n <tr data-slot={MAC_TABLE_SLOTS.header}>\n {visible.map(({ column, index, id }) => {\n const sortable = isSortable(column);\n const active = sort?.id === id ? sort.direction : null;\n const label = liteColumnLabel(column, index);\n const align = column.align ?? \"left\";\n return (\n <th\n key={id}\n scope=\"col\"\n aria-sort={active === \"asc\" ? \"ascending\" : active === \"desc\" ? \"descending\" : undefined}\n data-sorted={active ? \"\" : undefined}\n data-align={align}\n title={column.helpText}\n className={cn(\n \"relative\",\n align === \"right\" ? \"text-right\" : align === \"center\" ? \"text-center\" : \"text-left\",\n column.mobileHidden && \"max-sm:hidden\",\n column.headerClassName,\n )}\n style={{\n ...(column.width !== undefined ? { width: column.width } : {}),\n ...(column.minWidth !== undefined ? { minWidth: column.minWidth } : {}),\n }}\n >\n {sortable ? (\n <Button\n type=\"button\"\n variant=\"quiet\"\n data-lite-sort={id}\n data-mac-sort=\"\"\n aria-label={`Sort by ${label}`}\n iconEnd={active ? <ChevronDownIcon aria-hidden data-mac-sort-chevron={active} className={cn(active === \"asc\" && \"rotate-180\")} /> : undefined}\n onClick={() => setSort(nextSort(sort, id, column.defaultSortDirection ?? \"asc\"))}\n className=\"h-full\"\n >\n {column.header}\n </Button>\n ) : (\n column.header\n )}\n </th>\n );\n })}\n {copyRows ? <th aria-label=\"Row actions\" className=\"w-8\" /> : null}\n </tr>\n </thead>\n <tbody ref={(el) => void (bodyRef.current = el)}>\n {rows.map((row, rowIndex) => {\n const rowId = getRowId(row);\n const striped = stripe++ % 2 === 1;\n return (\n <tr\n key={rowId}\n data-lite-row=\"\"\n data-slot={MAC_TABLE_SLOTS.row}\n data-stripe={striped ? \"\" : undefined}\n data-opens={opens ? \"\" : undefined}\n data-selected={selectedKey === rowId ? \"\" : undefined}\n aria-selected={selectedKey !== undefined ? selectedKey === rowId : undefined}\n tabIndex={opens ? 0 : undefined}\n onClick={opens ? onRowClick(row) : undefined}\n onKeyDown={onRowKeyDown(row)}\n className={cn(\"group/lite-row\", opens && \"cursor-pointer\", rowClassName?.(row, rowIndex))}\n >\n {visible.map(({ column, id }, position) => {\n const align = column.align ?? \"left\";\n return (\n <td\n key={id}\n data-mac-numeric={align === \"right\" ? \"\" : undefined}\n className={cn(\n align === \"right\" ? \"text-right\" : align === \"center\" ? \"text-center\" : \"text-left\",\n column.mobileHidden && \"max-sm:hidden\",\n column.className,\n )}\n >\n {cellContent(column, row, rowIndex, position === 0)}\n </td>\n );\n })}\n {copyRows ? <td className=\"w-8 text-right\">{copyCell(row, rowId)}</td> : null}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lite-table/LiteTable.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * LiteTable — THE LIGHT SORTABLE TABLE (2026-10-17, Arman: \"so there is no\n * excuse for creating hand-rolled versions in the app\").\n *\n * For a small, fully loaded set (up to a few hundred rows) where the full\n * `MatrxDataTable` is more than the page needs: declared columns, click-to-sort\n * headers, the honest read states, a row that opens or links, copy a row, two\n * densities, and a phone layout (each row a labeled record).\n *\n * It takes the SAME `MatrxColumnDef<T>` and the same core props as\n * `MatrxDataTable` (`data`, `columns`, `getRowId`, `read`, `emptyState`,\n * `defaultSort`, `onRowOpen`, `getRowHref`, `density`, `rowClassName`), so a\n * page graduates to the full table by changing one import. Column fields it\n * does not draw (filters, editing, pinning, KPIs …) are ignored, never wrong.\n *\n * It imports only TYPES from the data table: the bundle never carries the grid\n * (guarded by `scripts/section-isolation.json`).\n *\n * THE MAC TABLE (2026-10-17, Arman: \"I love the mac table\"): it draws the desktop app's\n * `DataTable` — NSTableView's 24 pt rows under a 22 pt header, 13 pt, stripes, hairlines, the\n * accent selection — from the same `data-slot` vocabulary and the same values\n * (`../mac-table/tokens.ts`, drawn by `mac-table.css`; `mac-table-parity.test.ts` keeps them equal).\n */\n\nimport {\n useCallback,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { useIsMobile } from \"@ai-matrx/kit/media-query\";\nimport { copyText } from \"@ai-matrx/kit/clipboard\";\nimport { openOutbound } from \"@ai-matrx/kit/outbound\";\nimport type {\n MatrxColumnDef,\n MatrxDataTableDensity,\n MatrxDataTableEmptyState,\n SortState,\n} from \"../data-table/types\";\nimport { ReadEmpty, ReadStaleNotice, type ReadOutcome } from \"../read-state/read-gate\";\nimport { ListEmptyView } from \"../read-state/list-empty-view\";\nimport { getTapTargetLinkComponent } from \"../tap-target/link\";\nimport { cn } from \"../cn\";\nimport { Button } from \"../controls/controls\";\nimport { ChevronDownIcon, CheckIcon, CopyIcon } from \"../icons\";\nimport { MAC_TABLE_ATTRIBUTE, MAC_TABLE_SLOTS } from \"../mac-table/tokens\";\n\nexport interface LiteTableProps<T> {\n data: T[];\n columns: MatrxColumnDef<T>[];\n getRowId: (row: T) => string;\n /** The read behind `data`: the empty view shows only after a read that succeeded. */\n read?: ReadOutcome | undefined;\n /** Shown after a successful read with no rows. The full table's object form, or any node. */\n emptyState?: MatrxDataTableEmptyState | ReactNode;\n /** First sort (uncontrolled). */\n defaultSort?: SortState | null | undefined;\n /** Controlled sort. */\n sort?: SortState | null | undefined;\n onSortChange?: ((sort: SortState | null) => void) | undefined;\n /** Plain row click / Enter. */\n onRowOpen?: ((row: T) => void) | undefined;\n /** The row's page: its first column becomes a real link; a plain row click follows it. */\n getRowHref?: ((row: T) => string | undefined) | undefined;\n /** `condensed` (Finder's 20 pt rows) · `normal` (AppKit's 24 pt, default) · `spacious` (36 pt). */\n density?: MatrxDataTableDensity | undefined;\n /** The current row (drawn selected: gray, the accent while the table has focus). */\n selectedKey?: string | null | undefined;\n /** A copy button on each row (its visible values, one \"Label: value\" per line). Default true. */\n copyRows?: boolean | undefined;\n /** Accessible name of the table. */\n ariaLabel?: string | undefined;\n /** Scroll inside this height with the header held. */\n maxHeight?: number | string | undefined;\n /** Phones: `cards` (default) draws each row as a labeled record; `scroll` keeps the table. */\n mobile?: \"cards\" | \"scroll\" | undefined;\n rowClassName?: ((row: T, index: number) => string | undefined) | undefined;\n className?: string | undefined;\n}\n\n/** A column's stable id: `id`, else `accessorKey`, else its position. */\nexport function liteColumnId<T>(column: MatrxColumnDef<T>, index: number): string {\n return column.id ?? column.accessorKey ?? `col-${index}`;\n}\n\n/** A column's plain-text name. */\nexport function liteColumnLabel<T>(column: MatrxColumnDef<T>, index: number): string {\n if (column.label) return column.label;\n if (typeof column.header === \"string\") return column.header;\n return liteColumnId(column, index);\n}\n\nfunction rawValue<T>(column: MatrxColumnDef<T>, row: T): unknown {\n if (column.accessorFn) return column.accessorFn(row);\n if (column.accessorKey) return (row as Record<string, unknown>)[column.accessorKey];\n return undefined;\n}\n\nfunction hasValue<T>(column: MatrxColumnDef<T>): boolean {\n return Boolean(column.sortValue || column.accessorFn || column.accessorKey);\n}\n\nfunction isSortable<T>(column: MatrxColumnDef<T>): boolean {\n return column.sortable !== false && hasValue(column);\n}\n\nfunction sortKey<T>(column: MatrxColumnDef<T>, row: T): unknown {\n return column.sortValue ? column.sortValue(row) : rawValue(column, row);\n}\n\nfunction asText(value: unknown): string {\n if (value == null) return \"\";\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map(asText).filter(Boolean).join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/** Null/empty last in both directions; numbers and dates by value; text naturally (2 before 10). */\nexport function compareLiteValues(a: unknown, b: unknown): number {\n const aEmpty = a == null || a === \"\";\n const bEmpty = b == null || b === \"\";\n if (aEmpty || bEmpty) return aEmpty === bEmpty ? 0 : aEmpty ? 1 : -1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return Number(a) - Number(b);\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n return asText(a).localeCompare(asText(b), undefined, { numeric: true, sensitivity: \"base\" });\n}\n\n/** Rows sorted by `sort` (a copy; stable). Unknown ids leave the order alone. */\nexport function sortLiteRows<T>(rows: T[], columns: MatrxColumnDef<T>[], sort: SortState | null | undefined): T[] {\n if (!sort) return rows;\n const index = columns.findIndex((c, i) => liteColumnId(c, i) === sort.id);\n const column = columns[index];\n if (!column || !hasValue(column)) return rows;\n const sign = sort.direction === \"desc\" ? -1 : 1;\n return rows\n .map((row, i) => ({ row, i, key: sortKey(column, row) }))\n .sort((x, y) => {\n const xEmpty = x.key == null || x.key === \"\";\n const yEmpty = y.key == null || y.key === \"\";\n // Empty values stay last whichever way the column runs.\n if (xEmpty || yEmpty) return xEmpty === yEmpty ? x.i - y.i : xEmpty ? 1 : -1;\n return compareLiteValues(x.key, y.key) * sign || x.i - y.i;\n })\n .map((x) => x.row);\n}\n\n/** What Copy puts on the clipboard for one row: \"Label: value\" per column that has a value. */\nexport function liteRowText<T>(row: T, columns: MatrxColumnDef<T>[]): string {\n return columns\n .map((column, i) => {\n if (column.hidden) return null;\n const value = column.copyValue ? column.copyValue(row) : rawValue(column, row);\n if (!column.copyValue && !column.accessorFn && !column.accessorKey) return null;\n return `${liteColumnLabel(column, i)}: ${asText(value)}`;\n })\n .filter((line): line is string => line !== null)\n .join(\"\\n\");\n}\n\nfunction nextSort(current: SortState | null, id: string, first: \"asc\" | \"desc\"): SortState | null {\n if (!current || current.id !== id) return { id, direction: first };\n if (current.direction === first) return { id, direction: first === \"asc\" ? \"desc\" : \"asc\" };\n return null;\n}\n\nfunction CopyRowButton({ text, label }: { text: string; label: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <Button\n type=\"button\"\n variant=\"quiet\"\n aria-label={`Copy ${label}`}\n icon={copied ? <CheckIcon /> : <CopyIcon />}\n title=\"Copy row\"\n data-lite-copy=\"\"\n onClick={(event) => {\n event.stopPropagation();\n void copyText(text).then((ok) => {\n if (!ok) return;\n setCopied(true);\n setTimeout(() => setCopied(false), 1200);\n });\n }}\n className=\"shrink-0\"\n />\n );\n}\n\nexport function LiteTable<T>({\n data,\n columns,\n getRowId,\n read,\n emptyState = { title: \"Nothing here yet\" },\n defaultSort = null,\n sort: sortProp,\n onSortChange,\n onRowOpen,\n getRowHref,\n density = \"normal\",\n selectedKey,\n copyRows = true,\n ariaLabel,\n maxHeight,\n mobile = \"cards\",\n rowClassName,\n className,\n}: LiteTableProps<T>) {\n const isMobile = useIsMobile();\n const [ownSort, setOwnSort] = useState<SortState | null>(defaultSort ?? null);\n const sort = sortProp !== undefined ? sortProp : ownSort;\n const setSort = useCallback(\n (next: SortState | null) => {\n if (sortProp === undefined) setOwnSort(next);\n onSortChange?.(next);\n },\n [sortProp, onSortChange],\n );\n const visible = useMemo(\n () => columns.map((column, index) => ({ column, index, id: liteColumnId(column, index) })).filter(({ column }) => !column.hidden),\n [columns],\n );\n const rows = useMemo(() => sortLiteRows(data, columns, sort), [data, columns, sort]);\n const bodyRef = useRef<HTMLTableSectionElement | HTMLDivElement | null>(null);\n const Link = (getTapTargetLinkComponent() ?? \"a\") as unknown as React.ComponentType<React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string; prefetch?: boolean }>;\n const registeredLink = getTapTargetLinkComponent() !== null;\n const opens = Boolean(onRowOpen || getRowHref);\n\n if (rows.length === 0) {\n return (\n <div className={className} data-lite-table=\"\" {...{ [MAC_TABLE_ATTRIBUTE]: \"\" }}>\n <ReadEmpty read={read}>\n <ListEmptyView emptyState={emptyState} />\n </ReadEmpty>\n </div>\n );\n }\n\n const open = (row: T, event: MouseEvent | KeyboardEvent, rowEl: HTMLElement) => {\n const href = getRowHref?.(row);\n const modified = \"metaKey\" in event && (event.metaKey || event.ctrlKey || event.shiftKey);\n if (href && modified) {\n openOutbound(href, \"_blank\", \"noopener\");\n return;\n }\n if (onRowOpen) {\n onRowOpen(row);\n return;\n }\n // The first column's anchor carries the host's router: following it keeps client navigation.\n if (href) rowEl.querySelector<HTMLAnchorElement>(\"a[data-lite-link]\")?.click();\n };\n\n const onRowKeyDown = (row: T) => (event: KeyboardEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === \"Enter\") {\n event.preventDefault();\n open(row, event, event.currentTarget);\n return;\n }\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\" && event.key !== \"Home\" && event.key !== \"End\") return;\n const all = Array.from(bodyRef.current?.querySelectorAll<HTMLElement>(\"[data-lite-row]\") ?? []);\n const at = all.indexOf(event.currentTarget);\n const to =\n event.key === \"Home\" ? 0 : event.key === \"End\" ? all.length - 1 : event.key === \"ArrowDown\" ? at + 1 : at - 1;\n const target = all[Math.max(0, Math.min(all.length - 1, to))];\n if (target) {\n event.preventDefault();\n target.focus();\n }\n };\n\n const onRowClick = (row: T) => (event: MouseEvent<HTMLElement>) => {\n // A click on a control or link inside the row is that control's, never the row's.\n if ((event.target as HTMLElement).closest(\"a,button,input,select,textarea,[role=menuitem]\")) return;\n open(row, event, event.currentTarget);\n };\n\n const cellContent = (column: MatrxColumnDef<T>, row: T, rowIndex: number, first: boolean): ReactNode => {\n const content = column.cell ? column.cell(row, rowIndex) : asText(rawValue(column, row));\n const href = column.href?.(row) ?? (first ? getRowHref?.(row) : undefined);\n if (!href) return content;\n return (\n <Link\n href={href}\n data-lite-link=\"\"\n {...(registeredLink ? { prefetch: false } : {})}\n className=\"text-inherit underline-offset-2 hover:underline outline-none focus-visible:underline\"\n >\n {content}\n </Link>\n );\n };\n\n const copyCell = (row: T, rowId: string) =>\n copyRows ? <CopyRowButton text={liteRowText(row, columns)} label={rowId} /> : null;\n\n const macRoot = { [MAC_TABLE_ATTRIBUTE]: \"\", \"data-density\": density };\n\n if (isMobile && mobile === \"cards\") {\n const [titleCol, ...restCols] = visible.filter(({ column }) => !column.mobileHidden);\n return (\n <div className={cn(\"flex flex-col gap-2\", className)} data-lite-table=\"\" data-lite-layout=\"cards\" {...macRoot}>\n <ReadStaleNotice read={read} />\n <div ref={(el) => void (bodyRef.current = el)} role=\"list\" aria-label={ariaLabel} data-mac-cards=\"\" className=\"flex flex-col\">\n {rows.map((row, rowIndex) => {\n const rowId = getRowId(row);\n return (\n <div\n key={rowId}\n role=\"listitem\"\n data-lite-row=\"\"\n data-mac-card=\"\"\n data-opens={opens ? \"\" : undefined}\n data-selected={selectedKey === rowId ? \"\" : undefined}\n tabIndex={opens ? 0 : undefined}\n onClick={opens ? onRowClick(row) : undefined}\n onKeyDown={onRowKeyDown(row)}\n className={cn(\n \"group/lite-row\",\n opens && \"cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring\",\n rowClassName?.(row, rowIndex),\n )}\n >\n <div className=\"flex items-start gap-2\">\n <div className=\"min-w-0 flex-1 font-semibold\">\n {titleCol ? cellContent(titleCol.column, row, rowIndex, true) : null}\n </div>\n {copyCell(row, rowId)}\n </div>\n {restCols.length > 0 ? (\n <dl className=\"mt-1 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5\">\n {restCols.map(({ column, index, id }) => (\n <div key={id} className=\"contents\">\n <dt data-mac-card-label=\"\">{liteColumnLabel(column, index)}</dt>\n <dd className=\"min-w-0\">{cellContent(column, row, rowIndex, false)}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n\n let stripe = 0;\n return (\n <div className={cn(\"flex min-w-0 flex-col gap-2\", className)} data-lite-table=\"\" {...macRoot}>\n <ReadStaleNotice read={read} />\n <div\n data-slot={MAC_TABLE_SLOTS.table}\n className={cn(\"min-w-0 overflow-x-auto\", maxHeight !== undefined && \"overflow-y-auto\")}\n style={maxHeight !== undefined ? { maxHeight } : undefined}\n >\n <table aria-label={ariaLabel}>\n <thead>\n <tr data-slot={MAC_TABLE_SLOTS.header}>\n {visible.map(({ column, index, id }) => {\n const sortable = isSortable(column);\n const active = sort?.id === id ? sort.direction : null;\n const label = liteColumnLabel(column, index);\n const align = column.align ?? \"left\";\n return (\n <th\n key={id}\n scope=\"col\"\n aria-sort={active === \"asc\" ? \"ascending\" : active === \"desc\" ? \"descending\" : undefined}\n data-sorted={active ? \"\" : undefined}\n data-align={align}\n title={column.helpText}\n className={cn(\n \"relative\",\n align === \"right\" ? \"text-right\" : align === \"center\" ? \"text-center\" : \"text-left\",\n column.mobileHidden && \"max-sm:hidden\",\n column.headerClassName,\n )}\n style={{\n ...(column.width !== undefined ? { width: column.width } : {}),\n ...(column.minWidth !== undefined ? { minWidth: column.minWidth } : {}),\n }}\n >\n {sortable ? (\n <Button\n type=\"button\"\n variant=\"quiet\"\n data-lite-sort={id}\n data-mac-sort=\"\"\n aria-label={`Sort by ${label}`}\n iconEnd={active ? <ChevronDownIcon aria-hidden data-mac-sort-chevron={active} className={cn(active === \"asc\" && \"rotate-180\")} /> : undefined}\n onClick={() => setSort(nextSort(sort, id, column.defaultSortDirection ?? \"asc\"))}\n className=\"h-full\"\n >\n {column.header}\n </Button>\n ) : (\n column.header\n )}\n </th>\n );\n })}\n {copyRows ? <th aria-label=\"Row actions\" className=\"w-8\" /> : null}\n </tr>\n </thead>\n <tbody ref={(el) => void (bodyRef.current = el)}>\n {rows.map((row, rowIndex) => {\n const rowId = getRowId(row);\n const striped = stripe++ % 2 === 1;\n return (\n <tr\n key={rowId}\n data-lite-row=\"\"\n data-slot={MAC_TABLE_SLOTS.row}\n data-stripe={striped ? \"\" : undefined}\n data-opens={opens ? \"\" : undefined}\n data-selected={selectedKey === rowId ? \"\" : undefined}\n aria-selected={selectedKey !== undefined ? selectedKey === rowId : undefined}\n tabIndex={opens ? 0 : undefined}\n onClick={opens ? onRowClick(row) : undefined}\n onKeyDown={onRowKeyDown(row)}\n className={cn(\"group/lite-row\", opens && \"cursor-pointer\", rowClassName?.(row, rowIndex))}\n >\n {visible.map(({ column, id }, position) => {\n const align = column.align ?? \"left\";\n return (\n <td\n key={id}\n data-mac-numeric={align === \"right\" ? \"\" : undefined}\n className={cn(\n align === \"right\" ? \"text-right\" : align === \"center\" ? \"text-center\" : \"text-left\",\n column.mobileHidden && \"max-sm:hidden\",\n column.className,\n )}\n >\n {cellContent(column, row, rowIndex, position === 0)}\n </td>\n );\n })}\n {copyRows ? <td className=\"w-8 text-right\">{copyCell(row, rowId)}</td> : null}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -22,6 +22,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var notice_stack_exports = {};
23
23
  __export(notice_stack_exports, {
24
24
  NOTICE_STACK: () => NOTICE_STACK,
25
+ NOTICE_TEXT_BUDGET: () => NOTICE_TEXT_BUDGET,
25
26
  collapseNotices: () => collapseNotices,
26
27
  noticeKey: () => noticeKey,
27
28
  noticeLifetimeMs: () => noticeLifetimeMs,
@@ -40,6 +41,7 @@ var NOTICE_STACK = {
40
41
  /** The card's width: small, never the window's. */
41
42
  widthRem: 22
42
43
  };
44
+ var NOTICE_TEXT_BUDGET = { titleChars: 24, lineChars: 40 };
43
45
  function noticeKey(tone, title, message) {
44
46
  const text = (v) => typeof v === "string" || typeof v === "number" ? String(v).trim() : "";
45
47
  return `${tone}\0${text(title)}\0${text(message)}`;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/notice-stack.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/notice-stack — THE ONE NOTICE LOOK AND STACK RULE, for the web and Matrx 2.\n *\n * Arman, 2026-10-23, looking at two full-width red bars across Matrx 2 Cloud Chat: \"Let's eliminate\n * this terrible pattern of having giant errors that shit the entire ui. If we're going to have a\n * place for errors, it should be a small something that resembles errors from a normal macos\".\n *\n * Champion: the macOS notification banner. A small rounded card of translucent material in the\n * window's TOP-RIGHT corner (where macOS puts every notification, so the eye already looks there;\n * the bottom of our windows holds the composer and the list's foot, which a notice must not sit on),\n * the tone's icon, a one-line title, an optional second line, at most one action, and ×.\n *\n * The stack rule, the same on both hosts:\n * - identical notices (same tone, title and line) collapse into ONE card with a count;\n * - at most 3 cards show; the rest fold into \"N more\";\n * - info and success go after NOTICE_STACK.infoMs; errors and warnings stay until dismissed\n * or until NOTICE_STACK.errorMs (long, so a notice nobody saw does not sit there for ever).\n *\n * The desktop renders it through `Notices` + `Banner` (desktop/layout.tsx, desktop/state.tsx); the\n * web through its one sonner Toaster (matrx-frontend components/ui/sonner.tsx), which reads these\n * constants and keys every toast with `noticeKey` so the same notice never stacks twice.\n */\n\nexport const NOTICE_STACK = {\n /** The corner every notice floats in. */\n corner: \"top-right\",\n /** Cards shown at once; the rest fold into \"N more\". */\n maxVisible: 3,\n /** Info and success: gone after this long. */\n infoMs: 4000,\n /** Errors and warnings: stay until dismissed, or this long at most. */\n errorMs: 30000,\n /** The card's width: small, never the window's. */\n widthRem: 22,\n} as const;\n\nexport type NoticeStackTone = \"neutral\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\n/** The identity two notices share when they say the same thing (tone, title, line). */\nexport function noticeKey(tone: string, title: unknown, message?: unknown): string {\n const text = (v: unknown): string => (typeof v === \"string\" || typeof v === \"number\" ? String(v).trim() : \"\");\n return `${tone}\\u0000${text(title)}\\u0000${text(message)}`;\n}\n\nexport interface CollapsedNotice<T> {\n /** The latest notice of the group (its dismiss and action are the live ones). */\n readonly item: T;\n /** Every notice the group holds, oldest first. */\n readonly members: readonly T[];\n /** How many identical notices it stands for. */\n readonly count: number;\n}\n\n/**\n * Identical notices become one with a count, in the order each first appeared. A notice whose key\n * is null (a rich one that says nothing comparable) stands alone.\n */\nexport function collapseNotices<T>(items: readonly T[], keyOf: (item: T) => string | null): CollapsedNotice<T>[] {\n const groups: { key: string | null; members: T[] }[] = [];\n const byKey = new Map<string, { key: string | null; members: T[] }>();\n for (const item of items) {\n const key = keyOf(item);\n const found = key !== null ? byKey.get(key) : undefined;\n if (found) {\n found.members.push(item);\n continue;\n }\n const group = { key, members: [item] };\n groups.push(group);\n if (key !== null) byKey.set(key, group);\n }\n return groups.map((g) => ({ item: g.members[g.members.length - 1] as T, members: g.members, count: g.members.length }));\n}\n\n/** The cards on screen and how many fold away (the newest show). */\nexport function visibleNotices<T>(groups: readonly T[], max: number = NOTICE_STACK.maxVisible): { shown: T[]; folded: number } {\n if (groups.length <= max) return { shown: [...groups], folded: 0 };\n return { shown: groups.slice(groups.length - max), folded: groups.length - max };\n}\n\n/** How long a notice of this tone stays (ms; Infinity never happens: errors cap at errorMs). */\nexport function noticeLifetimeMs(tone: NoticeStackTone | \"error\"): number {\n return tone === \"danger\" || tone === \"warning\" || tone === \"error\" ? NOTICE_STACK.errorMs : NOTICE_STACK.infoMs;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBO,IAAM,eAAe;AAAA;AAAA,EAE1B,QAAQ;AAAA;AAAA,EAER,YAAY;AAAA;AAAA,EAEZ,QAAQ;AAAA;AAAA,EAER,SAAS;AAAA;AAAA,EAET,UAAU;AACZ;AAKO,SAAS,UAAU,MAAc,OAAgB,SAA2B;AACjF,QAAM,OAAO,CAAC,MAAwB,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,OAAO,CAAC,EAAE,KAAK,IAAI;AAC1G,SAAO,GAAG,IAAI,KAAS,KAAK,KAAK,CAAC,KAAS,KAAK,OAAO,CAAC;AAC1D;AAeO,SAAS,gBAAmB,OAAqB,OAAyD;AAC/G,QAAM,SAAiD,CAAC;AACxD,QAAM,QAAQ,oBAAI,IAAkD;AACpE,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,MAAM,IAAI;AACtB,UAAM,QAAQ,QAAQ,OAAO,MAAM,IAAI,GAAG,IAAI;AAC9C,QAAI,OAAO;AACT,YAAM,QAAQ,KAAK,IAAI;AACvB;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE;AACrC,WAAO,KAAK,KAAK;AACjB,QAAI,QAAQ,KAAM,OAAM,IAAI,KAAK,KAAK;AAAA,EACxC;AACA,SAAO,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,SAAS,CAAC,GAAQ,SAAS,EAAE,SAAS,OAAO,EAAE,QAAQ,OAAO,EAAE;AACxH;AAGO,SAAS,eAAkB,QAAsB,MAAc,aAAa,YAA4C;AAC7H,MAAI,OAAO,UAAU,IAAK,QAAO,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE;AACjE,SAAO,EAAE,OAAO,OAAO,MAAM,OAAO,SAAS,GAAG,GAAG,QAAQ,OAAO,SAAS,IAAI;AACjF;AAGO,SAAS,iBAAiB,MAAyC;AACxE,SAAO,SAAS,YAAY,SAAS,aAAa,SAAS,UAAU,aAAa,UAAU,aAAa;AAC3G;","names":[]}
1
+ {"version":3,"sources":["../src/notice-stack.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/notice-stack — THE ONE NOTICE LOOK AND STACK RULE, for the web and Matrx 2.\n *\n * Arman, 2026-10-23, looking at two full-width red bars across Matrx 2 Cloud Chat: \"Let's eliminate\n * this terrible pattern of having giant errors that shit the entire ui. If we're going to have a\n * place for errors, it should be a small something that resembles errors from a normal macos\".\n *\n * Champion: the macOS notification banner. A small rounded card of translucent material in the\n * window's TOP-RIGHT corner (where macOS puts every notification, so the eye already looks there;\n * the bottom of our windows holds the composer and the list's foot, which a notice must not sit on),\n * the tone's icon, a one-line title, an optional second line, at most one action, and ×.\n *\n * The stack rule, the same on both hosts:\n * - identical notices (same tone, title and line) collapse into ONE card with a count;\n * - at most 3 cards show; the rest fold into \"N more\";\n * - info and success go after NOTICE_STACK.infoMs; errors and warnings stay until dismissed\n * or until NOTICE_STACK.errorMs (long, so a notice nobody saw does not sit there for ever).\n *\n * The desktop renders it through `Notices` + `Banner` (desktop/layout.tsx, desktop/state.tsx); the\n * web through its one sonner Toaster (matrx-frontend components/ui/sonner.tsx), which reads these\n * constants and keys every toast with `noticeKey` so the same notice never stacks twice.\n */\n\nexport const NOTICE_STACK = {\n /** The corner every notice floats in. */\n corner: \"top-right\",\n /** Cards shown at once; the rest fold into \"N more\". */\n maxVisible: 3,\n /** Info and success: gone after this long. */\n infoMs: 4000,\n /** Errors and warnings: stay until dismissed, or this long at most. */\n errorMs: 30000,\n /** The card's width: small, never the window's. */\n widthRem: 22,\n} as const;\n\n/**\n * How much a card can SAY with nothing cut by \"…\" at the smallest window (900 × 560, Matrx 2 Cloud\n * Chat: the card is 324 px wide over the viewer, and an action such as Retry shares the row, leaving\n * ~180 px of text): the title is one line, the line under it at most two short ones. Measured\n * 2026-10-24 (\"Your change was not saved …\" / \"Check the connection and tr…\"). In-app text is layout.\n */\nexport const NOTICE_TEXT_BUDGET = { titleChars: 24, lineChars: 40 } as const;\n\nexport type NoticeStackTone = \"neutral\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\n/** The identity two notices share when they say the same thing (tone, title, line). */\nexport function noticeKey(tone: string, title: unknown, message?: unknown): string {\n const text = (v: unknown): string => (typeof v === \"string\" || typeof v === \"number\" ? String(v).trim() : \"\");\n return `${tone}\\u0000${text(title)}\\u0000${text(message)}`;\n}\n\nexport interface CollapsedNotice<T> {\n /** The latest notice of the group (its dismiss and action are the live ones). */\n readonly item: T;\n /** Every notice the group holds, oldest first. */\n readonly members: readonly T[];\n /** How many identical notices it stands for. */\n readonly count: number;\n}\n\n/**\n * Identical notices become one with a count, in the order each first appeared. A notice whose key\n * is null (a rich one that says nothing comparable) stands alone.\n */\nexport function collapseNotices<T>(items: readonly T[], keyOf: (item: T) => string | null): CollapsedNotice<T>[] {\n const groups: { key: string | null; members: T[] }[] = [];\n const byKey = new Map<string, { key: string | null; members: T[] }>();\n for (const item of items) {\n const key = keyOf(item);\n const found = key !== null ? byKey.get(key) : undefined;\n if (found) {\n found.members.push(item);\n continue;\n }\n const group = { key, members: [item] };\n groups.push(group);\n if (key !== null) byKey.set(key, group);\n }\n return groups.map((g) => ({ item: g.members[g.members.length - 1] as T, members: g.members, count: g.members.length }));\n}\n\n/** The cards on screen and how many fold away (the newest show). */\nexport function visibleNotices<T>(groups: readonly T[], max: number = NOTICE_STACK.maxVisible): { shown: T[]; folded: number } {\n if (groups.length <= max) return { shown: [...groups], folded: 0 };\n return { shown: groups.slice(groups.length - max), folded: groups.length - max };\n}\n\n/** How long a notice of this tone stays (ms; Infinity never happens: errors cap at errorMs). */\nexport function noticeLifetimeMs(tone: NoticeStackTone | \"error\"): number {\n return tone === \"danger\" || tone === \"warning\" || tone === \"error\" ? NOTICE_STACK.errorMs : NOTICE_STACK.infoMs;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBO,IAAM,eAAe;AAAA;AAAA,EAE1B,QAAQ;AAAA;AAAA,EAER,YAAY;AAAA;AAAA,EAEZ,QAAQ;AAAA;AAAA,EAER,SAAS;AAAA;AAAA,EAET,UAAU;AACZ;AAQO,IAAM,qBAAqB,EAAE,YAAY,IAAI,WAAW,GAAG;AAK3D,SAAS,UAAU,MAAc,OAAgB,SAA2B;AACjF,QAAM,OAAO,CAAC,MAAwB,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,OAAO,CAAC,EAAE,KAAK,IAAI;AAC1G,SAAO,GAAG,IAAI,KAAS,KAAK,KAAK,CAAC,KAAS,KAAK,OAAO,CAAC;AAC1D;AAeO,SAAS,gBAAmB,OAAqB,OAAyD;AAC/G,QAAM,SAAiD,CAAC;AACxD,QAAM,QAAQ,oBAAI,IAAkD;AACpE,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,MAAM,IAAI;AACtB,UAAM,QAAQ,QAAQ,OAAO,MAAM,IAAI,GAAG,IAAI;AAC9C,QAAI,OAAO;AACT,YAAM,QAAQ,KAAK,IAAI;AACvB;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE;AACrC,WAAO,KAAK,KAAK;AACjB,QAAI,QAAQ,KAAM,OAAM,IAAI,KAAK,KAAK;AAAA,EACxC;AACA,SAAO,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,SAAS,CAAC,GAAQ,SAAS,EAAE,SAAS,OAAO,EAAE,QAAQ,OAAO,EAAE;AACxH;AAGO,SAAS,eAAkB,QAAsB,MAAc,aAAa,YAA4C;AAC7H,MAAI,OAAO,UAAU,IAAK,QAAO,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE;AACjE,SAAO,EAAE,OAAO,OAAO,MAAM,OAAO,SAAS,GAAG,GAAG,QAAQ,OAAO,SAAS,IAAI;AACjF;AAGO,SAAS,iBAAiB,MAAyC;AACxE,SAAO,SAAS,YAAY,SAAS,aAAa,SAAS,UAAU,aAAa,UAAU,aAAa;AAC3G;","names":[]}
@@ -32,6 +32,16 @@ declare const NOTICE_STACK: {
32
32
  /** The card's width: small, never the window's. */
33
33
  readonly widthRem: 22;
34
34
  };
35
+ /**
36
+ * How much a card can SAY with nothing cut by "…" at the smallest window (900 × 560, Matrx 2 Cloud
37
+ * Chat: the card is 324 px wide over the viewer, and an action such as Retry shares the row, leaving
38
+ * ~180 px of text): the title is one line, the line under it at most two short ones. Measured
39
+ * 2026-10-24 ("Your change was not saved …" / "Check the connection and tr…"). In-app text is layout.
40
+ */
41
+ declare const NOTICE_TEXT_BUDGET: {
42
+ readonly titleChars: 24;
43
+ readonly lineChars: 40;
44
+ };
35
45
  type NoticeStackTone = "neutral" | "info" | "warning" | "danger" | "success";
36
46
  /** The identity two notices share when they say the same thing (tone, title, line). */
37
47
  declare function noticeKey(tone: string, title: unknown, message?: unknown): string;
@@ -56,5 +66,5 @@ declare function visibleNotices<T>(groups: readonly T[], max?: number): {
56
66
  /** How long a notice of this tone stays (ms; Infinity never happens: errors cap at errorMs). */
57
67
  declare function noticeLifetimeMs(tone: NoticeStackTone | "error"): number;
58
68
 
59
- export { NOTICE_STACK, collapseNotices, noticeKey, noticeLifetimeMs, visibleNotices };
69
+ export { NOTICE_STACK, NOTICE_TEXT_BUDGET, collapseNotices, noticeKey, noticeLifetimeMs, visibleNotices };
60
70
  export type { CollapsedNotice, NoticeStackTone };
@@ -32,6 +32,16 @@ declare const NOTICE_STACK: {
32
32
  /** The card's width: small, never the window's. */
33
33
  readonly widthRem: 22;
34
34
  };
35
+ /**
36
+ * How much a card can SAY with nothing cut by "…" at the smallest window (900 × 560, Matrx 2 Cloud
37
+ * Chat: the card is 324 px wide over the viewer, and an action such as Retry shares the row, leaving
38
+ * ~180 px of text): the title is one line, the line under it at most two short ones. Measured
39
+ * 2026-10-24 ("Your change was not saved …" / "Check the connection and tr…"). In-app text is layout.
40
+ */
41
+ declare const NOTICE_TEXT_BUDGET: {
42
+ readonly titleChars: 24;
43
+ readonly lineChars: 40;
44
+ };
35
45
  type NoticeStackTone = "neutral" | "info" | "warning" | "danger" | "success";
36
46
  /** The identity two notices share when they say the same thing (tone, title, line). */
37
47
  declare function noticeKey(tone: string, title: unknown, message?: unknown): string;
@@ -56,5 +66,5 @@ declare function visibleNotices<T>(groups: readonly T[], max?: number): {
56
66
  /** How long a notice of this tone stays (ms; Infinity never happens: errors cap at errorMs). */
57
67
  declare function noticeLifetimeMs(tone: NoticeStackTone | "error"): number;
58
68
 
59
- export { NOTICE_STACK, collapseNotices, noticeKey, noticeLifetimeMs, visibleNotices };
69
+ export { NOTICE_STACK, NOTICE_TEXT_BUDGET, collapseNotices, noticeKey, noticeLifetimeMs, visibleNotices };
60
70
  export type { CollapsedNotice, NoticeStackTone };
@@ -1,13 +1,15 @@
1
1
  "use client";
2
2
  import {
3
3
  NOTICE_STACK,
4
+ NOTICE_TEXT_BUDGET,
4
5
  collapseNotices,
5
6
  noticeKey,
6
7
  noticeLifetimeMs,
7
8
  visibleNotices
8
- } from "./chunk-4WSXAP24.js";
9
+ } from "./chunk-BB5LA7FQ.js";
9
10
  export {
10
11
  NOTICE_STACK,
12
+ NOTICE_TEXT_BUDGET,
11
13
  collapseNotices,
12
14
  noticeKey,
13
15
  noticeLifetimeMs,
@@ -340,6 +340,16 @@ function errorSentenceOf(error) {
340
340
  }
341
341
  var TIMED_OUT_SENTENCE = "That took too long. Nothing was lost; try again.";
342
342
  var FALLBACK_ERROR_SENTENCE = "Something went wrong. Try again.";
343
+ var CARD_ERROR_LINE = {
344
+ OFFLINE: "No connection",
345
+ TIMEOUT: "Took too long. Nothing was lost.",
346
+ UNAUTHORIZED: "Signed out. Sign in again.",
347
+ FORBIDDEN: "Not allowed for this account",
348
+ NOT_FOUND: "Not found. Refresh and retry.",
349
+ RATE_LIMIT: "Too many requests. Wait a moment.",
350
+ SERVER: "Server problem. Try again soon.",
351
+ FALLBACK: "Something went wrong"
352
+ };
343
353
  function isTimedOutError(error, message, code) {
344
354
  if (code === "57014" || code === "timed_out") return true;
345
355
  if (error && typeof error === "object") {
@@ -357,8 +367,9 @@ var ANY_HOST_CODES = /* @__PURE__ */ new Set(["OFFLINE", "TIMEOUT", "UNAUTHORIZE
357
367
  var MACHINE_PREFIX = /^\s*(?:\[[\w./-]+\]\s*|[a-z][\w]*(?:[._][\w]+)+:\s+)/;
358
368
  var RAW_TEXT = /\b(?:Type|Reference|Syntax|Range|Internal|Eval|URI)Error\b|\bat\s+[\w$.<>]+\s*\(|\.(?:[cm]?[jt]sx?|py):\d+|(?:^|[\s(])(?:\/[\w.@-]+){2,}|\bundefined is not\b|\bcannot read propert(?:y|ies)\b|\bis not a function\b|\bunexpected token\b/i;
359
369
  function peopleErrorSentence(error, message, options = {}) {
360
- if (isTimedOutError(error, message, options.code)) return TIMED_OUT_SENTENCE;
361
- const fallback = options.fallback ?? FALLBACK_ERROR_SENTENCE;
370
+ const card = options.card === true;
371
+ if (isTimedOutError(error, message, options.code)) return card ? CARD_ERROR_LINE.TIMEOUT : TIMED_OUT_SENTENCE;
372
+ const fallback = options.fallback ?? (card ? CARD_ERROR_LINE.FALLBACK : FALLBACK_ERROR_SENTENCE);
362
373
  const raw = message?.trim() || errorSentenceOf(error).trim();
363
374
  if (!raw) return fallback;
364
375
  let text = raw;
@@ -366,7 +377,8 @@ function peopleErrorSentence(error, message, options = {}) {
366
377
  if (!text) return fallback;
367
378
  if (offlineText(text) || RAW_TEXT.test(text) || text !== raw) {
368
379
  const said = describeError(text, fallback);
369
- return said.code && ANY_HOST_CODES.has(said.code) ? said.cause : fallback;
380
+ if (!said.code || !ANY_HOST_CODES.has(said.code)) return fallback;
381
+ return card ? CARD_ERROR_LINE[said.code] : said.cause;
370
382
  }
371
383
  return text;
372
384
  }