@ai-matrx/design-system 0.86.354 → 0.86.356
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -2
- package/dist/carousel.js +3 -3
- package/dist/{chunk-UGQRAQTE.js → chunk-2PO3N42Q.js} +3 -3
- package/dist/{chunk-ZRHVA3DL.js → chunk-AV5C4YLL.js} +2 -1
- package/dist/{chunk-ZRHVA3DL.js.map → chunk-AV5C4YLL.js.map} +1 -1
- package/dist/chunk-LJKF267O.js +168 -0
- package/dist/chunk-LJKF267O.js.map +1 -0
- package/dist/{chunk-CFCWQDGC.js → chunk-RLM4RSED.js} +5 -23
- package/dist/chunk-RLM4RSED.js.map +1 -0
- package/dist/{chunk-FLWSEDUW.js → chunk-ZMUPFGQO.js} +2 -2
- package/dist/controls.cjs +86 -1
- package/dist/controls.cjs.map +1 -1
- package/dist/controls.js +3 -3
- package/dist/data-table/drill-chart.cjs +85 -1
- package/dist/data-table/drill-chart.cjs.map +1 -1
- package/dist/data-table/drill-chart.js +1 -1
- package/dist/data-table/index.cjs +85 -1
- package/dist/data-table/index.cjs.map +1 -1
- package/dist/data-table/index.js +1 -1
- package/dist/desktop.cjs +159 -81
- package/dist/desktop.cjs.map +1 -1
- package/dist/desktop.css +8 -5
- package/dist/desktop.d.cts +0 -5
- package/dist/desktop.d.ts +0 -5
- package/dist/desktop.js +70 -70
- package/dist/desktop.js.map +1 -1
- package/dist/email-input-dialog.cjs +85 -1
- package/dist/email-input-dialog.cjs.map +1 -1
- package/dist/email-input-dialog.js +1 -1
- package/dist/index.cjs +114 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -22
- package/dist/index.d.ts +38 -22
- package/dist/index.js +16 -8
- package/dist/index.js.map +1 -1
- package/dist/lite-table.cjs +86 -19
- package/dist/lite-table.cjs.map +1 -1
- package/dist/lite-table.js +2 -2
- package/dist/notice-material.css +28 -0
- package/dist/record-list.cjs +86 -19
- package/dist/record-list.cjs.map +1 -1
- package/dist/record-list.js +2 -2
- package/dist/settings.cjs +1 -0
- package/dist/settings.cjs.map +1 -1
- package/dist/settings.js +1 -1
- package/dist/styles.css +1 -0
- package/dist/swipe-deck.cjs +95 -2
- package/dist/swipe-deck.cjs.map +1 -1
- package/dist/swipe-deck.css +2 -0
- package/dist/swipe-deck.js +11 -2
- package/dist/swipe-deck.js.map +1 -1
- package/dist/text-input-dialog.cjs +85 -1
- package/dist/text-input-dialog.cjs.map +1 -1
- package/dist/text-input-dialog.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-CFCWQDGC.js.map +0 -1
- package/dist/chunk-HKLIRHT2.js +0 -71
- package/dist/chunk-HKLIRHT2.js.map +0 -1
- /package/dist/{chunk-UGQRAQTE.js.map → chunk-2PO3N42Q.js.map} +0 -0
- /package/dist/{chunk-FLWSEDUW.js.map → chunk-ZMUPFGQO.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.86.356
|
|
4
|
+
|
|
5
|
+
- Slider forwards an explicitly supplied `aria-valuetext` to its focusable thumb, so controls such as PageCompare announce their formatted value. Keyboard and numeric value behavior are unchanged. Consumer action: none.
|
|
6
|
+
|
|
7
|
+
- SwipeDeck: a full deck (`fill` / `fullscreen`) takes keyboard focus back when the card changes, so the arrows keep
|
|
8
|
+
answering after an answer left focus outside the deck (a menu portal, a host control). Never from a field being
|
|
9
|
+
typed in, an open dialog or menu. The deck root carries `tabIndex={-1}` (no ring). Consumer action: none.
|
|
10
|
+
|
|
11
|
+
## 0.86.355
|
|
12
|
+
|
|
13
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
14
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
15
|
+
Diff: `git diff bd26bcae0e 83b6419fe3 -- apps/shared/design-system/src`
|
|
16
|
+
|
|
17
|
+
Changed shipped source files (9):
|
|
18
|
+
- `apps/shared/design-system/src/desktop/desktop.css`
|
|
19
|
+
- `apps/shared/design-system/src/desktop/split-view.tsx`
|
|
20
|
+
- `apps/shared/design-system/src/desktop/state.tsx`
|
|
21
|
+
- `apps/shared/design-system/src/error-box.tsx`
|
|
22
|
+
- `apps/shared/design-system/src/error-notice.tsx`
|
|
23
|
+
- `apps/shared/design-system/src/error-sentence.ts`
|
|
24
|
+
- `apps/shared/design-system/src/index.ts`
|
|
25
|
+
- `apps/shared/design-system/src/notice-material.css`
|
|
26
|
+
- `apps/shared/design-system/src/styles.css`
|
|
27
|
+
|
|
3
28
|
## 0.86.354
|
|
4
29
|
|
|
5
|
-
|
|
6
|
-
|
|
30
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
31
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
32
|
+
Diff: `git diff 9e78b5ba31 bd26bcae0e -- apps/shared/design-system/src`
|
|
33
|
+
|
|
34
|
+
Changed shipped source files (1):
|
|
35
|
+
- `apps/shared/design-system/src/contact-values/contact-values.tsx`
|
|
7
36
|
|
|
8
37
|
## 0.86.353
|
|
9
38
|
|
package/dist/carousel.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
IconButton
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2PO3N42Q.js";
|
|
5
5
|
import "./chunk-6NOLVA4E.js";
|
|
6
6
|
import "./chunk-5IR3BZOS.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-AV5C4YLL.js";
|
|
8
8
|
import "./chunk-XERGQT2M.js";
|
|
9
9
|
import "./chunk-WQ2QCMMA.js";
|
|
10
10
|
import "./chunk-YRF22M5S.js";
|
|
11
11
|
import "./chunk-GMNROZQJ.js";
|
|
12
12
|
import "./chunk-K4XCB576.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-LJKF267O.js";
|
|
14
14
|
import "./chunk-DRSBLHOF.js";
|
|
15
15
|
import "./chunk-TH6DDCWF.js";
|
|
16
16
|
import "./chunk-UWTODOMD.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-5IR3BZOS.js";
|
|
10
10
|
import {
|
|
11
11
|
Slider
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-AV5C4YLL.js";
|
|
13
13
|
import {
|
|
14
14
|
Label
|
|
15
15
|
} from "./chunk-XERGQT2M.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./chunk-YRF22M5S.js";
|
|
27
27
|
import {
|
|
28
28
|
ErrorActions
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LJKF267O.js";
|
|
30
30
|
import {
|
|
31
31
|
HostLink
|
|
32
32
|
} from "./chunk-TH6DDCWF.js";
|
|
@@ -4007,4 +4007,4 @@ export {
|
|
|
4007
4007
|
TileCard,
|
|
4008
4008
|
TileCardGrid
|
|
4009
4009
|
};
|
|
4010
|
-
//# sourceMappingURL=chunk-
|
|
4010
|
+
//# sourceMappingURL=chunk-2PO3N42Q.js.map
|
|
@@ -57,6 +57,7 @@ var Slider = React.forwardRef(({ className, size = "md", ...props }, ref) => {
|
|
|
57
57
|
{
|
|
58
58
|
"data-slot": "slider-thumb",
|
|
59
59
|
...props["aria-label"] ? { "aria-label": props["aria-label"] } : {},
|
|
60
|
+
...props["aria-valuetext"] !== void 0 ? { "aria-valuetext": props["aria-valuetext"] } : {},
|
|
60
61
|
className: cn(
|
|
61
62
|
"relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing",
|
|
62
63
|
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
@@ -79,4 +80,4 @@ Slider.displayName = SliderPrimitive.Root.displayName;
|
|
|
79
80
|
export {
|
|
80
81
|
Slider
|
|
81
82
|
};
|
|
82
|
-
//# sourceMappingURL=chunk-
|
|
83
|
+
//# sourceMappingURL=chunk-AV5C4YLL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/slider.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Slider — the range control (46 host import sites).\n *\n * Ported from matrx-frontend `components/ui/slider.tsx`. Two things the host\n * copy could not do, both fixed here rather than in a fork:\n *\n * 1. A 16px THUMB IS NOT A TOUCH TARGET. The stock thumb is `h-4 w-4`, well\n * under the 44px Apple HIG minimum, so a slider that worked with a mouse\n * was a coin-flip with a thumb. The visible thumb keeps its size (`size`\n * controls it), and an invisible `::after` pseudo-target expands the hit\n * area to 44px on coarse pointers without changing the layout — the same\n * trick `ResizableHandle` uses for mouse tolerance.\n *\n * 2. DENSITY IS A PROP, NOT A FORK (C18). `size=\"sm|md|lg\"` picks the track\n * height and thumb diameter together, so a dense admin panel and a\n * touch-first settings screen use ONE component.\n *\n * Multi-thumb ranges work: a `Thumb` is rendered per value, which is what\n * Radix requires and what the host copy (a single hardcoded Thumb) got wrong\n * for any `value` array longer than one.\n */\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\n\nexport type SliderSize = \"sm\" | \"md\" | \"lg\";\n\nconst TRACK_CLASSES: Record<SliderSize, string> = {\n sm: \"h-1\",\n md: \"h-1.5\",\n lg: \"h-2.5\",\n};\n\nconst THUMB_CLASSES: Record<SliderSize, string> = {\n sm: \"h-3.5 w-3.5\",\n md: \"h-4 w-4\",\n lg: \"h-5 w-5\",\n};\n\nexport interface SliderProps\n extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {\n /** Track + thumb scale. Default `md`. */\n size?: SliderSize;\n}\n\nconst Slider = React.forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, size = \"md\", ...props }, ref) => {\n // One Thumb per value — controlled `value` wins, then `defaultValue`, then\n // the single-thumb default. Radix renders nothing sensible if the count is\n // wrong, which is how the host copy silently dropped a range's upper thumb.\n const thumbCount = Math.max(\n 1,\n (props.value ?? props.defaultValue ?? [0]).length,\n );\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n data-slot=\"slider\"\n className={cn(\n \"relative flex w-full touch-none select-none items-center data-[disabled]:opacity-50\",\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n \"relative w-full grow overflow-hidden rounded-full bg-primary/20\",\n TRACK_CLASSES[size],\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"absolute h-full bg-primary data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }, (_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot=\"slider-thumb\"\n // The thumb is the focusable slider: it carries the control's name (a label on the root alone leaves it unnamed).\n {...(props[\"aria-label\"] ? { \"aria-label\": props[\"aria-label\"] } : {})}\n className={cn(\n \"relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n // Ruling 1 — invisible 44px target, coarse pointers only, so a\n // dense desktop row keeps its exact hit behavior.\n \"after:absolute after:left-1/2 after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2 after:content-['']\",\n \"pointer-coarse:after:h-11 pointer-coarse:after:w-11\",\n THUMB_CLASSES[size],\n )}\n />\n ))}\n </SliderPrimitive.Root>\n );\n});\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"],"mappings":";;;;;;AAwBA,YAAY,qBAAqB;AACjC,YAAY,WAAW;AAqCnB,SAkBI,KAlBJ;AA/BJ,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAQA,IAAM,SAAe,iBAGnB,CAAC,EAAE,WAAW,OAAO,MAAM,GAAG,MAAM,GAAG,QAAQ;AAI/C,QAAM,aAAa,KAAK;AAAA,IACtB;AAAA,KACC,MAAM,SAAS,MAAM,gBAAgB,CAAC,CAAC,GAAG;AAAA,EAC7C;AAEA,SACE;AAAA,IAAiB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAiB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,cAAc,IAAI;AAAA,cAClB;AAAA,YACF;AAAA,YAEA;AAAA,cAAiB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,CAAC,GAAG,UACtC;AAAA,UAAiB;AAAA,UAAhB;AAAA,YAEC,aAAU;AAAA,YAET,GAAI,MAAM,YAAY,IAAI,EAAE,cAAc,MAAM,YAAY,EAAE,IAAI,CAAC;AAAA,
|
|
1
|
+
{"version":3,"sources":["../src/slider.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Slider — the range control (46 host import sites).\n *\n * Ported from matrx-frontend `components/ui/slider.tsx`. Two things the host\n * copy could not do, both fixed here rather than in a fork:\n *\n * 1. A 16px THUMB IS NOT A TOUCH TARGET. The stock thumb is `h-4 w-4`, well\n * under the 44px Apple HIG minimum, so a slider that worked with a mouse\n * was a coin-flip with a thumb. The visible thumb keeps its size (`size`\n * controls it), and an invisible `::after` pseudo-target expands the hit\n * area to 44px on coarse pointers without changing the layout — the same\n * trick `ResizableHandle` uses for mouse tolerance.\n *\n * 2. DENSITY IS A PROP, NOT A FORK (C18). `size=\"sm|md|lg\"` picks the track\n * height and thumb diameter together, so a dense admin panel and a\n * touch-first settings screen use ONE component.\n *\n * Multi-thumb ranges work: a `Thumb` is rendered per value, which is what\n * Radix requires and what the host copy (a single hardcoded Thumb) got wrong\n * for any `value` array longer than one.\n */\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\n\nimport { cn } from \"./cn\";\n\nexport type SliderSize = \"sm\" | \"md\" | \"lg\";\n\nconst TRACK_CLASSES: Record<SliderSize, string> = {\n sm: \"h-1\",\n md: \"h-1.5\",\n lg: \"h-2.5\",\n};\n\nconst THUMB_CLASSES: Record<SliderSize, string> = {\n sm: \"h-3.5 w-3.5\",\n md: \"h-4 w-4\",\n lg: \"h-5 w-5\",\n};\n\nexport interface SliderProps\n extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {\n /** Track + thumb scale. Default `md`. */\n size?: SliderSize;\n}\n\nconst Slider = React.forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, size = \"md\", ...props }, ref) => {\n // One Thumb per value — controlled `value` wins, then `defaultValue`, then\n // the single-thumb default. Radix renders nothing sensible if the count is\n // wrong, which is how the host copy silently dropped a range's upper thumb.\n const thumbCount = Math.max(\n 1,\n (props.value ?? props.defaultValue ?? [0]).length,\n );\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n data-slot=\"slider\"\n className={cn(\n \"relative flex w-full touch-none select-none items-center data-[disabled]:opacity-50\",\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n \"relative w-full grow overflow-hidden rounded-full bg-primary/20\",\n TRACK_CLASSES[size],\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"absolute h-full bg-primary data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }, (_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot=\"slider-thumb\"\n // The thumb is the focusable slider: it carries the control's name (a label on the root alone leaves it unnamed).\n {...(props[\"aria-label\"] ? { \"aria-label\": props[\"aria-label\"] } : {})}\n {...(props[\"aria-valuetext\"] !== undefined ? { \"aria-valuetext\": props[\"aria-valuetext\"] } : {})}\n className={cn(\n \"relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n // Ruling 1 — invisible 44px target, coarse pointers only, so a\n // dense desktop row keeps its exact hit behavior.\n \"after:absolute after:left-1/2 after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2 after:content-['']\",\n \"pointer-coarse:after:h-11 pointer-coarse:after:w-11\",\n THUMB_CLASSES[size],\n )}\n />\n ))}\n </SliderPrimitive.Root>\n );\n});\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"],"mappings":";;;;;;AAwBA,YAAY,qBAAqB;AACjC,YAAY,WAAW;AAqCnB,SAkBI,KAlBJ;AA/BJ,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAQA,IAAM,SAAe,iBAGnB,CAAC,EAAE,WAAW,OAAO,MAAM,GAAG,MAAM,GAAG,QAAQ;AAI/C,QAAM,aAAa,KAAK;AAAA,IACtB;AAAA,KACC,MAAM,SAAS,MAAM,gBAAgB,CAAC,CAAC,GAAG;AAAA,EAC7C;AAEA,SACE;AAAA,IAAiB;AAAA,IAAhB;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAiB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,cAAc,IAAI;AAAA,cAClB;AAAA,YACF;AAAA,YAEA;AAAA,cAAiB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,CAAC,GAAG,UACtC;AAAA,UAAiB;AAAA,UAAhB;AAAA,YAEC,aAAU;AAAA,YAET,GAAI,MAAM,YAAY,IAAI,EAAE,cAAc,MAAM,YAAY,EAAE,IAAI,CAAC;AAAA,YACnE,GAAI,MAAM,gBAAgB,MAAM,SAAY,EAAE,kBAAkB,MAAM,gBAAgB,EAAE,IAAI,CAAC;AAAA,YAC9F,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA;AAAA;AAAA,cAGA;AAAA,cACA;AAAA,cACA,cAAc,IAAI;AAAA,YACpB;AAAA;AAAA,UAdK;AAAA,QAeP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ,CAAC;AACD,OAAO,cAA8B,qBAAK;","names":[]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/desktop/errors.ts
|
|
4
|
+
var RULES = [
|
|
5
|
+
{ code: "ENOENT", test: /\b(spawn\s+\S+\s+)?ENOENT\b|command not found|no such file or directory/i, cause: "A program or file it needs isn't on this computer. Install it or update Matrx, then try again." },
|
|
6
|
+
{ code: "EACCES", test: /\bEACCES\b|\bEPERM\b|permission denied|operation not permitted|not authori[sz]ed by the user/i, cause: "Matrx isn't allowed to do that. Grant access in System Settings \u203A Privacy & Security, then try again." },
|
|
7
|
+
{ code: "EADDRINUSE", test: /\bEADDRINUSE\b|address already in use|port .* (is )?(already )?in use/i, cause: "Another program is already using that port. Stop it, or choose another port." },
|
|
8
|
+
{ code: "ECONNREFUSED", test: /\bECONNREFUSED\b|connection refused/i, cause: "The service isn't answering. It may have stopped; start it again." },
|
|
9
|
+
{ code: "OFFLINE", test: /\bENOTFOUND\b|\bEAI_AGAIN\b|\bENETUNREACH\b|getaddrinfo|network (is )?unreachable|failed to fetch|offline/i, cause: "Can't reach the internet. Check the connection and try again." },
|
|
10
|
+
{ code: "TIMEOUT", test: /\bETIMEDOUT\b|timed? ?out|\bTIMEOUT\b|deadline exceeded/i, cause: "It took too long to answer. Try again in a moment." },
|
|
11
|
+
{ code: "ENOSPC", test: /\bENOSPC\b|no space left/i, cause: "The disk is full. Free some space, then try again." },
|
|
12
|
+
{ code: "ENOMEM", test: /\bENOMEM\b|out of memory|cannot allocate memory/i, cause: "The computer ran out of memory. Close something and try again." },
|
|
13
|
+
{ code: "UNAUTHORIZED", test: /\b401\b|unauthori[sz]ed|invalid (api )?key|not signed in|jwt expired|session expired/i, cause: "You're signed out, or the key was refused. Sign in again." },
|
|
14
|
+
{ code: "FORBIDDEN", test: /\b403\b|forbidden/i, cause: "This account isn't allowed to do that." },
|
|
15
|
+
{ code: "NOT_FOUND", test: /\b404\b|not found/i, cause: "It isn't there any more. Refresh and try again." },
|
|
16
|
+
{ code: "RATE_LIMIT", test: /\b429\b|rate.?limit|too many requests/i, cause: "Too many requests just now. Wait a moment and try again." },
|
|
17
|
+
{ code: "SERVER", test: /\b5\d\d\b|internal server error|bad gateway|service unavailable/i, cause: "The server had a problem. Try again in a moment." },
|
|
18
|
+
{ code: "EXITED", test: /exited with code|process exited|crashed|SIGKILL|SIGSEGV|SIGTERM/i, cause: "The service stopped unexpectedly. Start it again; Copy details if it keeps happening." }
|
|
19
|
+
];
|
|
20
|
+
var RAW = /\b[A-Z]{2,}[A-Z_]*\b|[\\/][\w.-]+[\\/]|\bat \S+ \(|\{|\}|errno|stack|undefined|null|\bspawn\b/;
|
|
21
|
+
function rawText(error) {
|
|
22
|
+
if (error instanceof Error) return error.message || error.name;
|
|
23
|
+
if (typeof error === "string") return error;
|
|
24
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
25
|
+
return error.message;
|
|
26
|
+
}
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
function describeError(error, fallback = "Something went wrong. Try again.") {
|
|
30
|
+
const detail = rawText(error).trim();
|
|
31
|
+
if (!detail) return { cause: fallback, detail: "" };
|
|
32
|
+
for (const rule of RULES) {
|
|
33
|
+
if (rule.test.test(detail)) return { cause: rule.cause, detail, code: rule.code };
|
|
34
|
+
}
|
|
35
|
+
if (!RAW.test(detail) && detail.length <= 160) return { cause: detail, detail: "" };
|
|
36
|
+
return { cause: fallback, detail };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/error-sentence.ts
|
|
40
|
+
function errorSentenceOf(error) {
|
|
41
|
+
if (error == null || error === true || error === false) return "";
|
|
42
|
+
if (typeof error === "string") return error;
|
|
43
|
+
if (typeof error !== "object") return String(error);
|
|
44
|
+
const message = error.message;
|
|
45
|
+
return typeof message === "string" ? message : "";
|
|
46
|
+
}
|
|
47
|
+
var TIMED_OUT_SENTENCE = "That took too long. Nothing was lost; try again.";
|
|
48
|
+
var FALLBACK_ERROR_SENTENCE = "Something went wrong. Try again.";
|
|
49
|
+
function isTimedOutError(error, message, code) {
|
|
50
|
+
if (code === "57014" || code === "timed_out") return true;
|
|
51
|
+
if (error && typeof error === "object") {
|
|
52
|
+
const o = error;
|
|
53
|
+
if (o.code === "57014" || o.code === "timed_out" || o.sqlstate === "57014") return true;
|
|
54
|
+
}
|
|
55
|
+
const text = `${message ?? ""} ${errorSentenceOf(error)}`;
|
|
56
|
+
return /canceling statement due to statement timeout/i.test(text);
|
|
57
|
+
}
|
|
58
|
+
function isOfflineError(error, message) {
|
|
59
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
|
|
60
|
+
return offlineText(`${message ?? ""} ${errorSentenceOf(error)}`);
|
|
61
|
+
}
|
|
62
|
+
function offlineText(text) {
|
|
63
|
+
const t = text.trim();
|
|
64
|
+
return t !== "" && (describeError(t).code === "OFFLINE" || /networkerror|network request failed|load failed/i.test(t));
|
|
65
|
+
}
|
|
66
|
+
var ANY_HOST_CODES = /* @__PURE__ */ new Set(["OFFLINE", "TIMEOUT", "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "RATE_LIMIT", "SERVER"]);
|
|
67
|
+
var MACHINE_PREFIX = /^\s*(?:\[[\w./-]+\]\s*|[a-z][\w]*(?:[._][\w]+)+:\s+)/;
|
|
68
|
+
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;
|
|
69
|
+
function isRawErrorText(text) {
|
|
70
|
+
return MACHINE_PREFIX.test(text) || RAW_TEXT.test(text);
|
|
71
|
+
}
|
|
72
|
+
function peopleErrorSentence(error, message, options = {}) {
|
|
73
|
+
if (isTimedOutError(error, message, options.code)) return TIMED_OUT_SENTENCE;
|
|
74
|
+
const fallback = options.fallback ?? FALLBACK_ERROR_SENTENCE;
|
|
75
|
+
const raw = message?.trim() || errorSentenceOf(error).trim();
|
|
76
|
+
if (!raw) return fallback;
|
|
77
|
+
let text = raw;
|
|
78
|
+
for (let i = 0; i < 3 && MACHINE_PREFIX.test(text); i++) text = text.replace(MACHINE_PREFIX, "").trim();
|
|
79
|
+
if (!text) return fallback;
|
|
80
|
+
if (offlineText(text) || RAW_TEXT.test(text) || text !== raw) {
|
|
81
|
+
const said = describeError(text, fallback);
|
|
82
|
+
return said.code && ANY_HOST_CODES.has(said.code) ? said.cause : fallback;
|
|
83
|
+
}
|
|
84
|
+
return text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/error-box.tsx
|
|
88
|
+
import * as React from "react";
|
|
89
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
90
|
+
var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/error-actions");
|
|
91
|
+
var registry = globalThis;
|
|
92
|
+
var ErrorActionsContext = registry[CONTEXT_KEY] ?? (registry[CONTEXT_KEY] = React.createContext(null));
|
|
93
|
+
function ErrorActionsProvider({
|
|
94
|
+
render,
|
|
95
|
+
children
|
|
96
|
+
}) {
|
|
97
|
+
return /* @__PURE__ */ jsx(ErrorActionsContext.Provider, { value: render, children });
|
|
98
|
+
}
|
|
99
|
+
var MENUS_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/error-card-menus");
|
|
100
|
+
var menusRegistry = globalThis;
|
|
101
|
+
var ErrorCardMenusContext = menusRegistry[MENUS_KEY] ?? (menusRegistry[MENUS_KEY] = React.createContext(null));
|
|
102
|
+
function ErrorCardMenusProvider({
|
|
103
|
+
menus,
|
|
104
|
+
children
|
|
105
|
+
}) {
|
|
106
|
+
return /* @__PURE__ */ jsx(ErrorCardMenusContext.Provider, { value: menus, children });
|
|
107
|
+
}
|
|
108
|
+
function useErrorCardMenus() {
|
|
109
|
+
return React.useContext(ErrorCardMenusContext);
|
|
110
|
+
}
|
|
111
|
+
function useErrorActions() {
|
|
112
|
+
return React.useContext(ErrorActionsContext);
|
|
113
|
+
}
|
|
114
|
+
function ErrorActions(props) {
|
|
115
|
+
const render = useErrorActions();
|
|
116
|
+
if (!render) return null;
|
|
117
|
+
return /* @__PURE__ */ jsx(Fragment, { children: render(props) });
|
|
118
|
+
}
|
|
119
|
+
function sayable(body, error) {
|
|
120
|
+
if (typeof body === "string") return peopleErrorSentence(error, body);
|
|
121
|
+
if (Array.isArray(body)) return body.map((b) => typeof b === "string" && b.trim() ? peopleErrorSentence(error, b) : b);
|
|
122
|
+
return body;
|
|
123
|
+
}
|
|
124
|
+
var ErrorBox = React.forwardRef(function ErrorBox2({
|
|
125
|
+
as = "p",
|
|
126
|
+
message,
|
|
127
|
+
title,
|
|
128
|
+
error,
|
|
129
|
+
operation,
|
|
130
|
+
records,
|
|
131
|
+
unsavedInput,
|
|
132
|
+
origin,
|
|
133
|
+
children,
|
|
134
|
+
...rest
|
|
135
|
+
}, ref) {
|
|
136
|
+
const Tag = as;
|
|
137
|
+
const facts = {
|
|
138
|
+
...message !== void 0 ? { message } : {},
|
|
139
|
+
...title !== void 0 ? { title } : {},
|
|
140
|
+
...error !== void 0 ? { error } : {},
|
|
141
|
+
...operation !== void 0 ? { operation } : {},
|
|
142
|
+
...records !== void 0 ? { records } : {},
|
|
143
|
+
...unsavedInput !== void 0 ? { unsavedInput } : {},
|
|
144
|
+
...origin !== void 0 ? { origin } : {}
|
|
145
|
+
};
|
|
146
|
+
return /* @__PURE__ */ jsxs(Tag, { ref, role: "alert", "data-error-box": "", ...rest, children: [
|
|
147
|
+
sayable(children ?? message, error),
|
|
148
|
+
/* @__PURE__ */ jsx(ErrorActions, { ...facts })
|
|
149
|
+
] });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
describeError,
|
|
154
|
+
errorSentenceOf,
|
|
155
|
+
TIMED_OUT_SENTENCE,
|
|
156
|
+
FALLBACK_ERROR_SENTENCE,
|
|
157
|
+
isTimedOutError,
|
|
158
|
+
isOfflineError,
|
|
159
|
+
isRawErrorText,
|
|
160
|
+
peopleErrorSentence,
|
|
161
|
+
ErrorActionsProvider,
|
|
162
|
+
ErrorCardMenusProvider,
|
|
163
|
+
useErrorCardMenus,
|
|
164
|
+
useErrorActions,
|
|
165
|
+
ErrorActions,
|
|
166
|
+
ErrorBox
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=chunk-LJKF267O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/desktop/errors.ts","../src/error-sentence.ts","../src/error-box.tsx"],"sourcesContent":["/**\n * THE error-wording layer. A failure is shown as a cause the person can act on (\"A program it\n * needs isn't installed\"), never as the raw text a process threw (\"spawn uv ENOENT\"). The raw text\n * is kept, whole, as `detail` — the banner offers it behind Copy details and a tooltip, for a bug\n * report or an agent. One table, so every screen and every service says the same thing.\n */\n\nexport interface DescribedError {\n /** One plain sentence: what went wrong and, where it helps, what to do. */\n cause: string;\n /** The raw text, unedited (empty when it would only repeat `cause`). */\n detail: string;\n /** A short machine code when one was recognized (\"ENOENT\", \"TIMEOUT\"). */\n code?: string;\n}\n\ninterface Rule {\n code: string;\n test: RegExp;\n cause: string;\n}\n\n/** Ordered: the first rule whose pattern matches the raw text wins. */\nconst RULES: ReadonlyArray<Rule> = [\n { code: \"ENOENT\", test: /\\b(spawn\\s+\\S+\\s+)?ENOENT\\b|command not found|no such file or directory/i, cause: \"A program or file it needs isn't on this computer. Install it or update Matrx, then try again.\" },\n { code: \"EACCES\", test: /\\bEACCES\\b|\\bEPERM\\b|permission denied|operation not permitted|not authori[sz]ed by the user/i, cause: \"Matrx isn't allowed to do that. Grant access in System Settings › Privacy & Security, then try again.\" },\n { code: \"EADDRINUSE\", test: /\\bEADDRINUSE\\b|address already in use|port .* (is )?(already )?in use/i, cause: \"Another program is already using that port. Stop it, or choose another port.\" },\n { code: \"ECONNREFUSED\", test: /\\bECONNREFUSED\\b|connection refused/i, cause: \"The service isn't answering. It may have stopped; start it again.\" },\n { code: \"OFFLINE\", test: /\\bENOTFOUND\\b|\\bEAI_AGAIN\\b|\\bENETUNREACH\\b|getaddrinfo|network (is )?unreachable|failed to fetch|offline/i, cause: \"Can't reach the internet. Check the connection and try again.\" },\n { code: \"TIMEOUT\", test: /\\bETIMEDOUT\\b|timed? ?out|\\bTIMEOUT\\b|deadline exceeded/i, cause: \"It took too long to answer. Try again in a moment.\" },\n { code: \"ENOSPC\", test: /\\bENOSPC\\b|no space left/i, cause: \"The disk is full. Free some space, then try again.\" },\n { code: \"ENOMEM\", test: /\\bENOMEM\\b|out of memory|cannot allocate memory/i, cause: \"The computer ran out of memory. Close something and try again.\" },\n { code: \"UNAUTHORIZED\", test: /\\b401\\b|unauthori[sz]ed|invalid (api )?key|not signed in|jwt expired|session expired/i, cause: \"You're signed out, or the key was refused. Sign in again.\" },\n { code: \"FORBIDDEN\", test: /\\b403\\b|forbidden/i, cause: \"This account isn't allowed to do that.\" },\n { code: \"NOT_FOUND\", test: /\\b404\\b|not found/i, cause: \"It isn't there any more. Refresh and try again.\" },\n { code: \"RATE_LIMIT\", test: /\\b429\\b|rate.?limit|too many requests/i, cause: \"Too many requests just now. Wait a moment and try again.\" },\n { code: \"SERVER\", test: /\\b5\\d\\d\\b|internal server error|bad gateway|service unavailable/i, cause: \"The server had a problem. Try again in a moment.\" },\n { code: \"EXITED\", test: /exited with code|process exited|crashed|SIGKILL|SIGSEGV|SIGTERM/i, cause: \"The service stopped unexpectedly. Start it again; Copy details if it keeps happening.\" },\n];\n\nconst RAW = /\\b[A-Z]{2,}[A-Z_]*\\b|[\\\\/][\\w.-]+[\\\\/]|\\bat \\S+ \\(|\\{|\\}|errno|stack|undefined|null|\\bspawn\\b/;\n\nfunction rawText(error: unknown): string {\n if (error instanceof Error) return error.message || error.name;\n if (typeof error === \"string\") return error;\n if (error && typeof error === \"object\" && \"message\" in error && typeof (error as { message: unknown }).message === \"string\") {\n return (error as { message: string }).message;\n }\n return \"\";\n}\n\n/**\n * Turn any thrown or returned error into words a person can act on. Already-humane text (a full\n * sentence the app wrote itself, with no codes, paths or stack) passes through unchanged.\n */\nexport function describeError(error: unknown, fallback = \"Something went wrong. Try again.\"): DescribedError {\n const detail = rawText(error).trim();\n if (!detail) return { cause: fallback, detail: \"\" };\n for (const rule of RULES) {\n if (rule.test.test(detail)) return { cause: rule.cause, detail, code: rule.code };\n }\n if (!RAW.test(detail) && detail.length <= 160) return { cause: detail, detail: \"\" };\n return { cause: fallback, detail };\n}\n","/**\n * THE SENTENCE A PERSON READS FOR AN ERROR — the one funnel every error surface draws through\n * (ErrorNotice, ErrorBox, the desktop Banner). Raw error text never reaches the screen: no\n * \"TypeError: Failed to fetch\", no `sidebar_conversations:` RPC prefix, no stack frame or file\n * path (Arman, 2026-10-24: notices are small and plain). The raw error still travels to the\n * Alchemy Menu / AI payload through the `error` prop; only the screen gets the sentence.\n *\n * The wording table is desktop/errors.ts `describeError` (one table for every screen); this file\n * adds what decides WHEN a sentence must go through it, narrow enough that an app's own sentence\n * (\"PDF too large\") passes untouched.\n */\nimport { describeError } from \"./desktop/errors\";\n\n/**\n * The sentence an error carries: its own message (string, Error, or any\n * object with a `message`). Empty when it has none.\n */\nexport function errorSentenceOf(error: unknown): string {\n if (error == null || error === true || error === false) return \"\";\n if (typeof error === \"string\") return error;\n if (typeof error !== \"object\") return String(error);\n const message = (error as { message?: unknown }).message;\n return typeof message === \"string\" ? message : \"\";\n}\n\n/**\n * What a person reads when the store cancelled a call at its statement timeout\n * (SQLSTATE 57014). Postgres' own \"canceling statement due to statement\n * timeout\" is about the database, not the person; the cancelled call rolled\n * back, so nothing was written (lane AT-SLOW, 2026-10-19). The same sentence\n * @ai-matrx/records' `TIMED_OUT_MESSAGE` gives.\n */\nexport const TIMED_OUT_SENTENCE = \"That took too long. Nothing was lost; try again.\";\n\n/** What a person reads when an error says nothing a person can use. */\nexport const FALLBACK_ERROR_SENTENCE = \"Something went wrong. Try again.\";\n\n/** True when an error, its code, or its sentence is the store's statement timeout. */\nexport function isTimedOutError(error: unknown, message?: string | null, code?: string | number): boolean {\n if (code === \"57014\" || code === \"timed_out\") return true;\n if (error && typeof error === \"object\") {\n const o = error as { code?: unknown; sqlstate?: unknown };\n if (o.code === \"57014\" || o.code === \"timed_out\" || o.sqlstate === \"57014\") return true;\n }\n const text = `${message ?? \"\"} ${errorSentenceOf(error)}`;\n return /canceling statement due to statement timeout/i.test(text);\n}\n\n/** True when the call failed because the device is offline or the network never answered. */\nexport function isOfflineError(error: unknown, message?: string | null): boolean {\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n return offlineText(`${message ?? \"\"} ${errorSentenceOf(error)}`);\n}\n\nfunction offlineText(text: string): boolean {\n const t = text.trim();\n return t !== \"\" && (describeError(t).code === \"OFFLINE\" || /networkerror|network request failed|load failed/i.test(t));\n}\n\n/** describeError's causes that hold on every host (its process codes — ENOENT, EACCES, ports — are\n * the desktop's words for a local program and would mislead on a database refusal). */\nconst ANY_HOST_CODES = new Set([\"OFFLINE\", \"TIMEOUT\", \"UNAUTHORIZED\", \"FORBIDDEN\", \"NOT_FOUND\", \"RATE_LIMIT\", \"SERVER\"]);\n\n/** A leading machine label: `sidebar_conversations: …`, `chat.rpc_name: …`, `[chat-sidebar] …`. */\nconst MACHINE_PREFIX = /^\\s*(?:\\[[\\w./-]+\\]\\s*|[a-z][\\w]*(?:[._][\\w]+)+:\\s+)/;\n/** Text no person should read: JS error class names, stack frames, source paths. */\nconst RAW_TEXT =\n /\\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;\n\n/** True when a sentence carries raw error text or a code path (what the guard refuses on screen). */\nexport function isRawErrorText(text: string): boolean {\n return MACHINE_PREFIX.test(text) || RAW_TEXT.test(text);\n}\n\n/**\n * The sentence for the screen: the timeout sentence for a statement timeout; a caller's plain\n * sentence as is; a machine label stripped from the front; anything raw or offline worded by\n * `describeError` (its table, else `fallback`).\n */\nexport function peopleErrorSentence(\n error: unknown,\n message?: string | null,\n options: { code?: string | number | undefined; fallback?: string | undefined } = {},\n): string {\n if (isTimedOutError(error, message, options.code)) return TIMED_OUT_SENTENCE;\n const fallback = options.fallback ?? FALLBACK_ERROR_SENTENCE;\n const raw = message?.trim() || errorSentenceOf(error).trim();\n if (!raw) return fallback;\n let text = raw;\n for (let i = 0; i < 3 && MACHINE_PREFIX.test(text); i++) text = text.replace(MACHINE_PREFIX, \"\").trim();\n if (!text) return fallback;\n if (offlineText(text) || RAW_TEXT.test(text) || text !== raw) {\n const said = describeError(text, fallback);\n return said.code && ANY_HOST_CODES.has(said.code) ? said.cause : fallback;\n }\n return text;\n}\n","/**\n * ErrorBox — THE one way a package draws an error on screen, and the seam a\n * host uses to put its own actions on every one of them.\n *\n * Every error sentence this package (and every sibling package that depends\n * on it) shows is an `ErrorBox`. The box is a plain `role=\"alert\"` element —\n * its tag and classes are the caller's, so nothing about the look changes — and\n * after its children it renders whatever the host's `ErrorActionsProvider`\n * returns for that error. With no provider it renders nothing extra.\n *\n * Why a slot and not a menu: the package cannot know the host's AI copy, bug\n * report, or support flow (Matrx fills it with its Alchemy copy-for-AI menu at\n * the app root). The package supplies the error's facts — the sentence, the\n * raw error, the operation, the records — and the host decides the actions.\n *\n * Guard: `error-box.census.test.ts` fails when a package file renders\n * `role=\"alert\"` for an error without going through `ErrorBox` (or, for the\n * `Alert` family, `ErrorActions`).\n */\nimport * as React from \"react\";\nimport { peopleErrorSentence } from \"./error-sentence\";\n\nexport interface ErrorActionsRecord {\n type: string;\n id: string;\n label?: string;\n}\n\n/** The facts a package knows about an error it is showing. All optional. */\nexport interface ErrorActionsInput {\n /** The sentence shown. Omit when the words are only in the rendered box. */\n message?: string | undefined;\n title?: string | undefined;\n /** The raw error (code/status/stack stay available to the host). */\n error?: unknown | undefined;\n /** What was being attempted (\"Load saved views\"). */\n operation?: string | undefined;\n records?: readonly ErrorActionsRecord[] | undefined;\n /** The person's input that did not land. */\n unsavedInput?: unknown | undefined;\n /** The package + component that drew it (\"design-system/SavedViewsControl\"). */\n origin?: string | undefined;\n /** Extra structured facts for the AI payload (never shown). */\n details?: Record<string, unknown> | undefined;\n code?: string | number | undefined;\n status?: number | undefined;\n /** The tables / RPCs the failed call used — pins its captured request as the cause. */\n calls?: readonly string[] | undefined;\n /** Where it was drawn: an `ErrorNotice` card/line (\"inline\") or a package alert box (\"alert\", the default). */\n source?: \"inline\" | \"alert\" | undefined;\n}\n\nexport type ErrorActionsRenderer = (input: ErrorActionsInput) => React.ReactNode;\n\n/**\n * ONE context for every copy of this module. tsup builds each entry point\n * (`.`, `./data-table`, …) without code splitting, so this file is inlined into\n * several bundles; a plain `createContext` would give each bundle its own\n * context and the host's provider (imported from `.`) would never reach an\n * error drawn by the data table. The registry key makes them one.\n */\nconst CONTEXT_KEY = Symbol.for(\"@ai-matrx/design-system/error-actions\");\ntype ContextRegistry = Record<symbol, React.Context<ErrorActionsRenderer | null> | undefined>;\nconst registry = globalThis as unknown as ContextRegistry;\nconst ErrorActionsContext: React.Context<ErrorActionsRenderer | null> =\n registry[CONTEXT_KEY] ??\n (registry[CONTEXT_KEY] = React.createContext<ErrorActionsRenderer | null>(null));\n\n/** Mount once at the host's app root; every ErrorBox below renders `render(...)`. */\nexport function ErrorActionsProvider({\n render,\n children,\n}: {\n render: ErrorActionsRenderer;\n children: React.ReactNode;\n}) {\n return (\n <ErrorActionsContext.Provider value={render}>{children}</ErrorActionsContext.Provider>\n );\n}\n\n/**\n * The host's content menu around an error card (`ErrorNotice`): the corner ⋯\n * button and the right-click boundary that show the host's content actions\n * over the error's sentence. Read-only — nothing here may change a record.\n * Without a provider the card draws neither (the actions slot still renders).\n */\nexport interface ErrorCardMenus {\n /** The corner ⋯ button over `{ title, text }`. */\n Button: React.ComponentType<{ title: string; text: string; className?: string }>;\n /** Wraps the whole card so right-click opens the same menu. */\n Boundary: React.ComponentType<{ title: string; text: string; children: React.ReactNode }>;\n}\n\nconst MENUS_KEY = Symbol.for(\"@ai-matrx/design-system/error-card-menus\");\ntype MenusRegistry = Record<symbol, React.Context<ErrorCardMenus | null> | undefined>;\nconst menusRegistry = globalThis as unknown as MenusRegistry;\nconst ErrorCardMenusContext: React.Context<ErrorCardMenus | null> =\n menusRegistry[MENUS_KEY] ??\n (menusRegistry[MENUS_KEY] = React.createContext<ErrorCardMenus | null>(null));\n\n/** Mount once at the host's app root beside `ErrorActionsProvider`. */\nexport function ErrorCardMenusProvider({\n menus,\n children,\n}: {\n menus: ErrorCardMenus;\n children: React.ReactNode;\n}) {\n return <ErrorCardMenusContext.Provider value={menus}>{children}</ErrorCardMenusContext.Provider>;\n}\n\nexport function useErrorCardMenus(): ErrorCardMenus | null {\n return React.useContext(ErrorCardMenusContext);\n}\n\nexport function useErrorActions(): ErrorActionsRenderer | null {\n return React.useContext(ErrorActionsContext);\n}\n\n/**\n * The host's actions for one error, placed wherever the caller puts this\n * element. Renders nothing without a provider. Use inside a box you do not\n * render yourself (the `Alert` family); everything else uses `ErrorBox`.\n */\nexport function ErrorActions(props: ErrorActionsInput) {\n const render = useErrorActions();\n if (!render) return null;\n return <>{render(props)}</>;\n}\n\ntype ErrorBoxTag = \"p\" | \"div\" | \"span\" | \"section\" | \"li\";\n\nexport interface ErrorBoxProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"role\" | \"title\">,\n ErrorActionsInput {\n /** The element drawn — the caller's existing tag, so layout never shifts. */\n as?: ErrorBoxTag;\n}\n\n/** A plain-string body goes through the one funnel (error-sentence.ts); rich children are the caller's. */\nfunction sayable(body: React.ReactNode, error: unknown): React.ReactNode {\n if (typeof body === \"string\") return peopleErrorSentence(error, body);\n if (Array.isArray(body)) return body.map((b) => (typeof b === \"string\" && b.trim() ? peopleErrorSentence(error, b) : b));\n return body;\n}\n\nexport const ErrorBox = React.forwardRef<HTMLElement, ErrorBoxProps>(function ErrorBox(\n {\n as = \"p\",\n message,\n title,\n error,\n operation,\n records,\n unsavedInput,\n origin,\n children,\n ...rest\n },\n ref,\n) {\n // A component type, never a bare ElementType: an app compiling this source may carry global JSX\n // elements (react-three-fiber) that make a bare ElementType's props `never` (2026-10-22).\n const Tag = as as unknown as React.ComponentType<React.HTMLAttributes<HTMLElement> & { ref?: React.Ref<HTMLElement> }>;\n const facts: ErrorActionsInput = {\n ...(message !== undefined ? { message } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(error !== undefined ? { error } : {}),\n ...(operation !== undefined ? { operation } : {}),\n ...(records !== undefined ? { records } : {}),\n ...(unsavedInput !== undefined ? { unsavedInput } : {}),\n ...(origin !== undefined ? { origin } : {}),\n };\n return (\n <Tag ref={ref} role=\"alert\" data-error-box=\"\" {...rest}>\n {sayable(children ?? message, error)}\n <ErrorActions {...facts} />\n </Tag>\n );\n});\n"],"mappings":";;;AAuBA,IAAM,QAA6B;AAAA,EACjC,EAAE,MAAM,UAAU,MAAM,4EAA4E,OAAO,iGAAiG;AAAA,EAC5M,EAAE,MAAM,UAAU,MAAM,iGAAiG,OAAO,6GAAwG;AAAA,EACxO,EAAE,MAAM,cAAc,MAAM,0EAA0E,OAAO,+EAA+E;AAAA,EAC5L,EAAE,MAAM,gBAAgB,MAAM,wCAAwC,OAAO,oEAAoE;AAAA,EACjJ,EAAE,MAAM,WAAW,MAAM,8GAA8G,OAAO,gEAAgE;AAAA,EAC9M,EAAE,MAAM,WAAW,MAAM,4DAA4D,OAAO,qDAAqD;AAAA,EACjJ,EAAE,MAAM,UAAU,MAAM,6BAA6B,OAAO,qDAAqD;AAAA,EACjH,EAAE,MAAM,UAAU,MAAM,oDAAoD,OAAO,iEAAiE;AAAA,EACpJ,EAAE,MAAM,gBAAgB,MAAM,yFAAyF,OAAO,4DAA4D;AAAA,EAC1L,EAAE,MAAM,aAAa,MAAM,sBAAsB,OAAO,yCAAyC;AAAA,EACjG,EAAE,MAAM,aAAa,MAAM,sBAAsB,OAAO,kDAAkD;AAAA,EAC1G,EAAE,MAAM,cAAc,MAAM,0CAA0C,OAAO,2DAA2D;AAAA,EACxI,EAAE,MAAM,UAAU,MAAM,oEAAoE,OAAO,mDAAmD;AAAA,EACtJ,EAAE,MAAM,UAAU,MAAM,oEAAoE,OAAO,wFAAwF;AAC7L;AAEA,IAAM,MAAM;AAEZ,SAAS,QAAQ,OAAwB;AACvC,MAAI,iBAAiB,MAAO,QAAO,MAAM,WAAW,MAAM;AAC1D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,SAAS,OAAO,UAAU,YAAY,aAAa,SAAS,OAAQ,MAA+B,YAAY,UAAU;AAC3H,WAAQ,MAA8B;AAAA,EACxC;AACA,SAAO;AACT;AAMO,SAAS,cAAc,OAAgB,WAAW,oCAAoD;AAC3G,QAAM,SAAS,QAAQ,KAAK,EAAE,KAAK;AACnC,MAAI,CAAC,OAAQ,QAAO,EAAE,OAAO,UAAU,QAAQ,GAAG;AAClD,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,KAAK,KAAK,MAAM,EAAG,QAAO,EAAE,OAAO,KAAK,OAAO,QAAQ,MAAM,KAAK,KAAK;AAAA,EAClF;AACA,MAAI,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,UAAU,IAAK,QAAO,EAAE,OAAO,QAAQ,QAAQ,GAAG;AAClF,SAAO,EAAE,OAAO,UAAU,OAAO;AACnC;;;AC9CO,SAAS,gBAAgB,OAAwB;AACtD,MAAI,SAAS,QAAQ,UAAU,QAAQ,UAAU,MAAO,QAAO;AAC/D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,QAAM,UAAW,MAAgC;AACjD,SAAO,OAAO,YAAY,WAAW,UAAU;AACjD;AASO,IAAM,qBAAqB;AAG3B,IAAM,0BAA0B;AAGhC,SAAS,gBAAgB,OAAgB,SAAyB,MAAiC;AACxG,MAAI,SAAS,WAAW,SAAS,YAAa,QAAO;AACrD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,WAAW,EAAE,SAAS,eAAe,EAAE,aAAa,QAAS,QAAO;AAAA,EACrF;AACA,QAAM,OAAO,GAAG,WAAW,EAAE,IAAI,gBAAgB,KAAK,CAAC;AACvD,SAAO,gDAAgD,KAAK,IAAI;AAClE;AAGO,SAAS,eAAe,OAAgB,SAAkC;AAC/E,MAAI,OAAO,cAAc,eAAe,UAAU,WAAW,MAAO,QAAO;AAC3E,SAAO,YAAY,GAAG,WAAW,EAAE,IAAI,gBAAgB,KAAK,CAAC,EAAE;AACjE;AAEA,SAAS,YAAY,MAAuB;AAC1C,QAAM,IAAI,KAAK,KAAK;AACpB,SAAO,MAAM,OAAO,cAAc,CAAC,EAAE,SAAS,aAAa,mDAAmD,KAAK,CAAC;AACtH;AAIA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,WAAW,WAAW,gBAAgB,aAAa,aAAa,cAAc,QAAQ,CAAC;AAGvH,IAAM,iBAAiB;AAEvB,IAAM,WACJ;AAGK,SAAS,eAAe,MAAuB;AACpD,SAAO,eAAe,KAAK,IAAI,KAAK,SAAS,KAAK,IAAI;AACxD;AAOO,SAAS,oBACd,OACA,SACA,UAAiF,CAAC,GAC1E;AACR,MAAI,gBAAgB,OAAO,SAAS,QAAQ,IAAI,EAAG,QAAO;AAC1D,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,MAAM,SAAS,KAAK,KAAK,gBAAgB,KAAK,EAAE,KAAK;AAC3D,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,KAAK,eAAe,KAAK,IAAI,GAAG,IAAK,QAAO,KAAK,QAAQ,gBAAgB,EAAE,EAAE,KAAK;AACtG,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,YAAY,IAAI,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,KAAK;AAC5D,UAAM,OAAO,cAAc,MAAM,QAAQ;AACzC,WAAO,KAAK,QAAQ,eAAe,IAAI,KAAK,IAAI,IAAI,KAAK,QAAQ;AAAA,EACnE;AACA,SAAO;AACT;;;AC7EA,YAAY,WAAW;AA0DnB,SAmDK,UAnDL,KAkGA,YAlGA;AAhBJ,IAAM,cAAc,uBAAO,IAAI,uCAAuC;AAEtE,IAAM,WAAW;AACjB,IAAM,sBACJ,SAAS,WAAW,MACnB,SAAS,WAAW,IAAU,oBAA2C,IAAI;AAGzE,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,QAAS,UAAS;AAE3D;AAeA,IAAM,YAAY,uBAAO,IAAI,0CAA0C;AAEvE,IAAM,gBAAgB;AACtB,IAAM,wBACJ,cAAc,SAAS,MACtB,cAAc,SAAS,IAAU,oBAAqC,IAAI;AAGtE,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAO,OAAQ,UAAS;AACjE;AAEO,SAAS,oBAA2C;AACzD,SAAa,iBAAW,qBAAqB;AAC/C;AAEO,SAAS,kBAA+C;AAC7D,SAAa,iBAAW,mBAAmB;AAC7C;AAOO,SAAS,aAAa,OAA0B;AACrD,QAAM,SAAS,gBAAgB;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,gCAAG,iBAAO,KAAK,GAAE;AAC1B;AAYA,SAAS,QAAQ,MAAuB,OAAiC;AACvE,MAAI,OAAO,SAAS,SAAU,QAAO,oBAAoB,OAAO,IAAI;AACpE,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,CAAC,MAAO,OAAO,MAAM,YAAY,EAAE,KAAK,IAAI,oBAAoB,OAAO,CAAC,IAAI,CAAE;AACvH,SAAO;AACT;AAEO,IAAM,WAAiB,iBAAuC,SAASA,UAC5E;AAAA,EACE,KAAK;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AAGA,QAAM,MAAM;AACZ,QAAM,QAA2B;AAAA,IAC/B,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACvC,GAAI,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IAC/C,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,IACrD,GAAI,WAAW,SAAY,EAAE,OAAO,IAAI,CAAC;AAAA,EAC3C;AACA,SACE,qBAAC,OAAI,KAAU,MAAK,SAAQ,kBAAe,IAAI,GAAG,MAC/C;AAAA,YAAQ,YAAY,SAAS,KAAK;AAAA,IACnC,oBAAC,gBAAc,GAAG,OAAO;AAAA,KAC3B;AAEJ,CAAC;","names":["ErrorBox"]}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
ErrorActions,
|
|
4
|
+
isTimedOutError,
|
|
5
|
+
peopleErrorSentence,
|
|
4
6
|
useErrorCardMenus
|
|
5
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LJKF267O.js";
|
|
6
8
|
import {
|
|
7
9
|
Button
|
|
8
10
|
} from "./chunk-7IOSA55D.js";
|
|
@@ -17,23 +19,6 @@ import {
|
|
|
17
19
|
|
|
18
20
|
// src/error-notice.tsx
|
|
19
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
-
function errorSentenceOf(error) {
|
|
21
|
-
if (error == null || error === true || error === false) return "";
|
|
22
|
-
if (typeof error === "string") return error;
|
|
23
|
-
if (typeof error !== "object") return String(error);
|
|
24
|
-
const message = error.message;
|
|
25
|
-
return typeof message === "string" ? message : "";
|
|
26
|
-
}
|
|
27
|
-
var TIMED_OUT_SENTENCE = "That took too long. Nothing was lost; try again.";
|
|
28
|
-
function isTimedOutError(error, message, code) {
|
|
29
|
-
if (code === "57014" || code === "timed_out") return true;
|
|
30
|
-
if (error && typeof error === "object") {
|
|
31
|
-
const o = error;
|
|
32
|
-
if (o.code === "57014" || o.code === "timed_out" || o.sqlstate === "57014") return true;
|
|
33
|
-
}
|
|
34
|
-
const text = `${message ?? ""} ${errorSentenceOf(error)}`;
|
|
35
|
-
return /canceling statement due to statement timeout/i.test(text);
|
|
36
|
-
}
|
|
37
22
|
function ErrorNotice({
|
|
38
23
|
title,
|
|
39
24
|
message,
|
|
@@ -53,7 +38,7 @@ function ErrorNotice({
|
|
|
53
38
|
className
|
|
54
39
|
}) {
|
|
55
40
|
const timedOut = isTimedOutError(error, message, code);
|
|
56
|
-
const sentence =
|
|
41
|
+
const sentence = peopleErrorSentence(error, message, { code });
|
|
57
42
|
const actions = actionsProp ?? (timedOut && onRetry ? /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "outline", "data-error-retry": "", onClick: onRetry, children: "Try again" }) : void 0);
|
|
58
43
|
const menus = useErrorCardMenus();
|
|
59
44
|
const input = {
|
|
@@ -337,9 +322,6 @@ function ReadStaleNotice({ read, className }) {
|
|
|
337
322
|
}
|
|
338
323
|
|
|
339
324
|
export {
|
|
340
|
-
errorSentenceOf,
|
|
341
|
-
TIMED_OUT_SENTENCE,
|
|
342
|
-
isTimedOutError,
|
|
343
325
|
ErrorNotice,
|
|
344
326
|
RELOAD_PAGE_LABEL,
|
|
345
327
|
ReloadPageButton,
|
|
@@ -351,4 +333,4 @@ export {
|
|
|
351
333
|
ReadEmpty,
|
|
352
334
|
ReadStaleNotice
|
|
353
335
|
};
|
|
354
|
-
//# sourceMappingURL=chunk-
|
|
336
|
+
//# sourceMappingURL=chunk-RLM4RSED.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/error-notice.tsx","../src/read-state/reload-page-button.tsx","../src/read-state/read-failure.tsx","../src/read-state/stale-data-notice.tsx","../src/read-state/read-gate.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * ErrorNotice — THE inline error card. Every error a surface renders inline\n * (\"Not saved\", \"Failed to load…\", a dialog's refusal, a form's failure) is\n * this component, so every one of them carries the Alchemy Menu and hands an\n * AI the sentence, the code, the operation, the records, the surface's declared\n * values and the person's unsaved input.\n *\n * <ErrorNotice\n * title=\"Not saved\"\n * message={item.error}\n * operation=\"Save comment\"\n * records={[{ type: \"document\", id: docId }]}\n * unsavedInput={{ comment: draft }}\n * actions={<Button onClick={retry}>Retry</Button>}\n * />\n *\n * Moved from matrx-frontend (chat host slots get real package defaults,\n * 2026-10-08). The host's actions (Matrx: the Alchemy Menu) arrive through\n * `ErrorActionsProvider`; the corner ⋯ and right-click menu through\n * `ErrorCardMenusProvider`. Without them the card still says everything.\n */\nimport type { ReactNode } from \"react\";\nimport { AlertTriangleIcon as AlertTriangle } from \"./icons\";\nimport { cn } from \"./cn\";\nimport { Button } from \"./button\";\nimport {\n ErrorActions,\n useErrorCardMenus,\n type ErrorActionsInput,\n type ErrorActionsRecord,\n} from \"./error-box\";\n\nimport {\n errorSentenceOf,\n isTimedOutError,\n peopleErrorSentence,\n TIMED_OUT_SENTENCE,\n} from \"./error-sentence\";\n\nexport { errorSentenceOf, isTimedOutError, TIMED_OUT_SENTENCE };\n\nexport type ErrorNoticeProps = {\n /** Short heading (\"Not saved\"). */\n title?: string;\n /** The sentence shown. Falls back to the error's own message. */\n message?: string | null;\n /** The raw error — code/status/stack go to the AI payload, never the screen. */\n error?: unknown;\n operation?: string;\n records?: readonly ErrorActionsRecord[];\n unsavedInput?: unknown;\n details?: Record<string, unknown>;\n code?: string | number;\n status?: number;\n /** The tables / RPCs the failed call used — pins its captured request as the cause. */\n calls?: readonly string[];\n /** Retry / Discard / other controls, rendered under the sentence. */\n actions?: ReactNode;\n /**\n * Run the failed call again. Shown as \"Try again\" when the error is the\n * store's statement timeout and no `actions` were given.\n */\n onRetry?: () => void;\n /** Extra content under the sentence. */\n children?: ReactNode;\n /**\n * `default` = the card. `compact` = the dense card used inside lists and\n * side panels. `inline` = a one-line field/form error (no card chrome): the\n * sentence in the destructive color with the menu at its end — the shape\n * every `<p role=\"alert\" className=\"text-destructive\">` render moves onto.\n */\n size?: \"inline\" | \"compact\" | \"default\";\n icon?: boolean;\n className?: string;\n};\n\nexport function ErrorNotice({\n title,\n message,\n error,\n operation,\n records,\n unsavedInput,\n details,\n code,\n status,\n calls,\n actions: actionsProp,\n onRetry,\n children,\n size = \"default\",\n icon = true,\n className,\n}: ErrorNoticeProps) {\n const timedOut = isTimedOutError(error, message, code);\n // The one funnel (error-sentence.ts): raw error text and code paths never reach the screen.\n const sentence = peopleErrorSentence(error, message, { code });\n const actions =\n actionsProp ??\n (timedOut && onRetry ? (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" data-error-retry=\"\" onClick={onRetry}>\n Try again\n </Button>\n ) : undefined);\n const menus = useErrorCardMenus();\n const input: ErrorActionsInput = {\n title,\n message: sentence,\n error,\n operation,\n records,\n unsavedInput,\n details,\n code,\n status,\n ...(calls ? { calls } : {}),\n source: \"inline\",\n origin: \"design-system/ErrorNotice\",\n };\n if (size === \"inline\") {\n return (\n <div\n role=\"alert\"\n data-error-notice=\"\"\n className={cn(\"flex items-start gap-1 text-destructive\", className)}\n >\n <span className=\"min-w-0 flex-1 break-words\">\n {title ? (\n <span data-error-title=\"\" className=\"font-medium\">\n {title}:{\" \"}\n </span>\n ) : null}\n {sentence}\n </span>\n {children}\n {actions}\n <ErrorActions {...input} />\n </div>\n );\n }\n const compact = size === \"compact\";\n // Title, actions or extra content make the card taller than the ⋯ + menu column.\n const stacked = Boolean(title || actions || children);\n // The card is an action host like any other content (ALC-15): right-click,\n // ⋯ (the same menu, opened at the button), the phone's sheet and the palette\n // (⌘/Ctrl+Shift+K) all show the ONE registry's actions over this sentence.\n // Read-only: nothing here may change a record.\n const menuTitle = title ?? \"Error\";\n const menuText = title ? `${title}: ${sentence}` : sentence;\n const card = (\n <div\n role=\"alert\"\n data-error-notice=\"\"\n className={cn(\n \"rounded-md border border-destructive/30 bg-destructive/5\",\n compact ? \"px-2 py-1.5 text-xs\" : \"px-3 py-2.5 text-sm\",\n className,\n )}\n >\n <div className=\"flex items-start gap-2\">\n {icon && (\n <AlertTriangle\n aria-hidden\n className={cn(\n \"shrink-0 text-destructive\",\n compact ? \"mt-px h-3.5 w-3.5\" : \"mt-0.5 h-4 w-4\",\n )}\n />\n )}\n <div className=\"min-w-0 flex-1\">\n {title && (\n <p data-error-title=\"\" className=\"font-medium text-destructive\">\n {title}\n </p>\n )}\n <p className={cn(\"break-words text-foreground\", title && \"mt-0.5\")}>\n {sentence}\n {!stacked && <ErrorActions {...input} />}\n </p>\n {children}\n {actions && <div className=\"mt-1 flex flex-wrap gap-1\">{actions}</div>}\n </div>\n {/* The corner column the ⋯ already owns carries the menu under it: no\n new column (beside the ⋯ it narrowed every line — 176→224px at\n 375px) and no new line (inline, a full last line wrapped it,\n +16px). A one-line notice with nothing under its sentence is\n shorter than that column, so there the menu rides the sentence\n (RC-B12 layout rule). */}\n <div className=\"flex shrink-0 flex-col items-center\">\n {menus ? (\n <menus.Button\n title={menuTitle}\n text={menuText}\n {...(compact ? { className: \"h-6 w-6\" } : {})}\n />\n ) : null}\n {stacked && <ErrorActions {...input} />}\n </div>\n </div>\n </div>\n );\n if (!menus) return card;\n return (\n <menus.Boundary title={menuTitle} text={menuText}>\n {card}\n </menus.Boundary>\n );\n}\n","\"use client\";\n\n/**\n * THE SERVER-SAFE RETRY. A server page cannot hand a failure notice a retry\n * function — functions do not cross the server/client boundary — so its\n * \"Couldn't load …\" card had no way forward but the browser's own reload\n * (page-pass 2026-09-27, /chat/message-templates/<id>). This control is the\n * retry any server-rendered failure can carry: it takes no props a server\n * cannot send, and it reloads THIS url (path, query and hash), which re-runs the\n * server read that failed.\n *\n * Its words say what it does — \"Reload page\" — because a reload also drops\n * anything unsaved elsewhere on the page; a client surface that can retry just\n * its own read passes `onRetry` to `ReadFailure` instead.\n */\nimport { RefreshCwIcon as RefreshCw } from \"../icons\";\nimport { Button } from \"../button\";\n\nexport const RELOAD_PAGE_LABEL = \"Reload page\";\n\nexport function ReloadPageButton({ className }: { className?: string }) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className={className ?? \"h-7 gap-1.5 text-xs\"}\n onClick={() => window.location.reload()}\n >\n <RefreshCw className=\"h-3.5 w-3.5\" aria-hidden /> {RELOAD_PAGE_LABEL}\n </Button>\n );\n}\n","\"use client\";\n\n/**\n * The failed-read view every list uses instead of its empty state (RC-B12\n * round 11): the error, with the Alchemy Menu (through ErrorNotice) and a\n * retry when the caller has one. `error` may be the error itself or just the\n * read's failed flag — the sentence never pretends there is nothing.\n *\n * ALWAYS A WAY FORWARD. With `onRetry` it offers \"Try again\" (the surface's own\n * read, nothing else touched). Without one — every SERVER page, which cannot\n * pass a function — it offers `ReloadPageButton`: a same-URL reload that re-runs\n * the server read. A failure card with no retry at all is gone.\n */\nimport { RefreshCwIcon as RefreshCw } from \"../icons\";\nimport { Button } from \"../button\";\nimport { ErrorNotice } from \"../error-notice\";\nimport { ReloadPageButton } from \"./reload-page-button\";\n\nexport type ReadFailureProps = {\n error: unknown;\n /** What could not be read, in the reader's words: \"your tasks\". */\n what?: string | undefined;\n /** Retry just this read. Absent (always, from a server page): a same-URL reload is offered. */\n onRetry?: (() => void) | undefined;\n className?: string | undefined;\n size?: \"compact\" | \"default\" | undefined;\n};\n\nexport function ReadFailure({\n error,\n what = \"this list\",\n onRetry,\n className,\n size = \"compact\",\n}: ReadFailureProps) {\n const flagOnly = error === true || error == null || error === \"\";\n return (\n <ErrorNotice\n size={size}\n className={className ?? \"m-3\"}\n title={`Couldn't load ${what}`}\n {...(flagOnly\n ? { message: `The read for ${what} failed, so nothing here is an answer — not \"empty\".` }\n : { error })}\n operation={`Load ${what}`}\n actions={\n onRetry ? (\n <Button variant=\"outline\" size=\"sm\" className=\"h-7 gap-1.5 text-xs\" onClick={onRetry}>\n <RefreshCw className=\"h-3.5 w-3.5\" /> Try again\n </Button>\n ) : (\n <ReloadPageButton />\n )\n }\n />\n );\n}\n","\"use client\";\n\n/**\n * StaleDataNotice — what a surface shows when a READ FAILED and the rows on\n * screen are therefore not to be trusted.\n *\n * THE DEAD END this kills: a list loads fine, the user acts, the surface\n * reloads — and the reload fails. A toast fires and fades. What remains is the\n * PREVIOUS list, rendered exactly like a fresh one. The user reads a roster\n * that no longer reflects the database and has no way to know. Bugbot found\n * this on the admins roster and the feedback table in the same pass, which is\n * what made it a primitive instead of two patches.\n *\n * This is the campaign's never-report-green rule at its sharpest: **a surface\n * must never present data it could not actually read as though it had.** The\n * sibling `DeepLinkMissNotice` states a definitive negative and so demands\n * proof the list WAS read; this states the opposite — that the read broke —\n * and is what that component's \"show the surface's ordinary error state\n * instead\" was pointing at. Neither is complete without the other.\n *\n * Two cases, and conflating them is the actual bug:\n *\n * 1. **Stale** (`hasData`) — rows are on screen from an earlier successful\n * read. Say they may be out of date. Do NOT blank them: old data plus an\n * honest label beats an empty screen, and the user may still need it.\n * 2. **Unknown** (`!hasData`) — nothing was ever read. An empty list here\n * reads as \"there are none\", which is a claim about the database nobody\n * is entitled to make. Say the read failed instead.\n *\n * `onRetry` is not optional decoration — a problem you can detect ships with\n * its one-click fix. The user should never have to reload the page to recover\n * from a failed fetch.\n */\n\nimport { AlertTriangleIcon as AlertTriangle, RefreshCwIcon as RefreshCw } from \"../icons\";\nimport { Button } from \"../button\";\nimport { cn } from \"../cn\";\nimport { ErrorActions } from \"../error-box\";\n\nexport interface StaleDataNoticeProps {\n /**\n * Whether rows from an earlier SUCCESSFUL read are currently displayed.\n * Decides between \"this may be out of date\" and \"we never read this\".\n */\n hasData: boolean;\n /**\n * What could not be read, as a plain noun phrase the user recognizes —\n * \"the admin roster\", \"feedback\". Never a table or endpoint name: our user\n * is a subject-matter expert, not an engineer.\n */\n what: string;\n /** Re-run the read. Required — the notice always carries its own fix. */\n onRetry: () => void;\n /** Disables the retry control and shows motion while a retry is in flight. */\n retrying?: boolean;\n /** Optional detail (a server message). Shown verbatim, never interpreted. */\n detail?: string | null;\n /**\n * A multi-source list where SOME sources read and some did not: the rows on\n * screen are current, but `what` is missing from them. Says so instead of\n * \"may be out of date\" (RC-B12 round 12 — a partly failed roster).\n */\n partial?: boolean;\n className?: string;\n}\n\nexport function StaleDataNotice({\n hasData,\n what,\n onRetry,\n retrying = false,\n detail,\n partial = false,\n className,\n}: StaleDataNoticeProps) {\n return (\n <div\n role=\"status\"\n className={cn(\n \"flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 type-body\",\n className,\n )}\n >\n <AlertTriangle className=\"h-4 w-4 shrink-0 text-amber-600 dark:text-amber-500\" />\n <span className=\"min-w-0 flex-1 text-foreground\">\n {partial ? (\n <>\n Couldn't load {what}, so <strong>they are missing</strong> from\n the list below — everything else loaded.\n </>\n ) : hasData ? (\n <>\n Couldn't refresh {what}. What you see below is the last version\n we loaded and <strong>may be out of date</strong>.\n </>\n ) : (\n <>\n Couldn't load {what}, so this list is empty because the read\n failed — <strong>not</strong> because there is nothing here.\n </>\n )}\n {detail ? (\n <span className=\"ml-1 text-muted-foreground\">({detail})</span>\n ) : null}\n {/* A failed read is an error on screen: it carries the Alchemy Menu (RC-B12),\n INSIDE the sentence — as its own flex item it narrowed the text and\n grew the notice 118→138px at 375px. */}\n <ErrorActions\n title={partial || !hasData ? `Couldn't load ${what}` : `Couldn't refresh ${what}`}\n message={\n detail\n ? `${hasData ? \"Couldn't refresh\" : \"Couldn't load\"} ${what}: ${detail}`\n : `${hasData ? \"Couldn't refresh\" : \"Couldn't load\"} ${what}.`\n }\n operation={`${hasData ? \"Refresh\" : \"Load\"} ${what}`}\n details={partial ? { rows_on_screen_are_partial: true } : { rows_on_screen_are_stale: hasData }}\n source=\"inline\"\n origin=\"design-system/StaleDataNotice\"\n />\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onRetry}\n disabled={retrying}\n className=\"h-11 shrink-0 gap-1.5 lg:h-7\"\n >\n <RefreshCw className={cn(\"h-3.5 w-3.5\", retrying && \"matrx-spin\")} />\n {retrying ? \"Retrying…\" : \"Try again\"}\n </Button>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * THE rule for every empty view (RC-B12 round 11): \"nothing here\" is an answer\n * only after a read that SUCCEEDED and returned nothing.\n *\n * <ReadGate status={readStatusOf(query)} error={query.error} what=\"your tasks\"\n * isEmpty={tasks.length === 0} empty={<p>No tasks yet.</p>} onRetry={refetch}>\n * <TaskList tasks={tasks} />\n * </ReadGate>\n *\n * loading → a wait · error → the failure (with the menu) · ready + empty → the\n * empty view · ready → the rows. When a refresh fails but earlier rows are on\n * screen, the rows stay and the failure is said above them.\n * Guarded by the census `findUngatedEmptyStates` and the lint rule\n * matrx/error-render-carries-alchemy (an empty view under a loading check\n * with no failure check is refused).\n */\nimport type { ReactNode } from \"react\";\nimport { Loader2Icon as Loader2 } from \"../icons\";\nimport { ReadFailure } from \"./read-failure\";\nimport { StaleDataNotice } from \"./stale-data-notice\";\n\nexport type ReadStatus = \"loading\" | \"error\" | \"ready\";\n\n/** The usual shapes a read reports itself in, folded into one status. */\nexport function readStatusOf(read: {\n status?: string | null;\n isLoading?: boolean;\n loading?: boolean;\n isFetching?: boolean;\n isError?: boolean;\n error?: unknown;\n}): ReadStatus {\n if (read.isError || (read.error != null && read.error !== false && read.error !== \"\")) return \"error\";\n if (read.status === \"error\" || read.status === \"failed\") return \"error\";\n if (read.isLoading || read.loading || read.status === \"loading\" || read.status === \"idle\" || read.status === \"pending\") return \"loading\";\n return \"ready\";\n}\n\n/**\n * A read's outcome handed to a list/table primitive (RC-B12 round 13). Every\n * primitive that takes an `emptyState` also takes `read?: ReadOutcome` and\n * renders the empty state only when `status === \"ready\"`: \"error\" with no rows\n * → <ReadFailure/> with the retry; \"error\" with rows → the rows under a stale\n * notice; \"loading\" with no rows → loading. Structurally identical to the\n * design-system table's `MatrxDataTableRead`, so one value serves both.\n */\nexport interface ReadOutcome {\n status: ReadStatus;\n /** The failure itself, or a truthy flag when the read only says it failed. */\n error?: unknown;\n onRetry?: (() => void) | undefined;\n /** What was read, in the reader's words: \"your tasks\". */\n what?: string | undefined;\n /**\n * A successful read has already produced a value (rows, a count). Then a\n * refetch in flight is not \"loading\" and a failed refresh is not \"no value\":\n * counts keep their last known value (with the stale mark) — the same\n * stale-while-error rule as rows (RC-B12 r13 ruling).\n */\n hasData?: boolean | undefined;\n}\n\ntype ReadLike = Parameters<typeof readStatusOf>[0] & {\n refetch?: () => unknown;\n retry?: () => unknown;\n /** The read's value (react-query `data`, a hook's rows) — present means a read already landed. */\n data?: unknown;\n /** react-query: when the data last arrived (0 = never). */\n dataUpdatedAt?: number;\n /** A hook that tracks it directly. */\n hasData?: boolean;\n};\n\n/** Has a successful read already produced a value? */\nfunction readHasData(read: ReadLike): boolean {\n if (typeof read.hasData === \"boolean\") return read.hasData;\n if (typeof read.dataUpdatedAt === \"number\" && read.dataUpdatedAt > 0) return true;\n // An empty array is often a hook's initial placeholder, not an answer — it\n // proves nothing was read; only a hook's own `hasData`/`dataUpdatedAt` can.\n if (Array.isArray(read.data)) return read.data.length > 0;\n return read.data !== undefined && read.data !== null;\n}\n\n/**\n * Fold a query/hook result into a `ReadOutcome`:\n * <MatrxDataTable data={rows} emptyState={…} read={readOf(query, { what: \"your tasks\" })} />\n * The retry is the read's own `refetch` / `retry` unless `onRetry` is given.\n */\nexport function readOf(read: ReadLike, options: { what?: string; onRetry?: () => void } = {}): ReadOutcome {\n const again = options.onRetry ?? read.refetch ?? read.retry;\n return {\n status: readStatusOf(read),\n error: read.error ?? (read.isError ? true : undefined),\n ...(again ? { onRetry: () => void again() } : {}),\n ...(options.what ? { what: options.what } : {}),\n hasData: readHasData(read),\n };\n}\n\nexport function ReadGate({\n read,\n status: statusProp,\n error: errorProp,\n what: whatProp,\n isEmpty,\n empty,\n loading,\n onRetry: onRetryProp,\n children,\n}: {\n /**\n * The read's outcome in one value — what a list primitive's `read` prop\n * carries. Absent (and no `status`), the gate is \"ready\": a primitive whose\n * caller passed no read renders exactly as before.\n */\n read?: ReadOutcome | undefined;\n status?: ReadStatus;\n error?: unknown;\n what?: string;\n isEmpty: boolean;\n empty: ReactNode;\n loading?: ReactNode;\n onRetry?: (() => void) | undefined;\n children: ReactNode;\n}) {\n const status = statusProp ?? read?.status ?? \"ready\";\n const error = errorProp ?? read?.error;\n const what = whatProp ?? read?.what ?? \"this list\";\n const onRetry = onRetryProp ?? read?.onRetry;\n if (status === \"error\") {\n if (isEmpty) return <ReadFailure error={error ?? true} what={what} onRetry={onRetry} />;\n // Rows from an earlier read stay; the notice says they may be out of date.\n return (\n <>\n {onRetry ? (\n <StaleDataNotice hasData what={what} onRetry={onRetry} className=\"mb-2\" />\n ) : (\n <ReadFailure error={error ?? true} what={what} />\n )}\n {children}\n </>\n );\n }\n if (status === \"loading\" && isEmpty) {\n return (\n loading ?? (\n <div className=\"flex items-center justify-center gap-2 p-4 text-xs text-muted-foreground\" role=\"status\" aria-busy=\"true\">\n <Loader2 className=\"h-4 w-4 matrx-spin\" /> Loading {what}…\n </div>\n )\n );\n }\n if (isEmpty) return <>{empty}</>;\n return <>{children}</>;\n}\n\n/**\n * A list primitive's EMPTY branch (RC-B12 round 13): the failure while its read\n * failed, the wait while it loads, and the primitive's own empty view only\n * after a read that succeeded. No `read` → the empty view, as before.\n *\n * if (items.length === 0) return <ReadEmpty read={read}>{emptyState}</ReadEmpty>;\n */\nexport function ReadEmpty({ read, loading, children }: { read?: ReadOutcome | undefined; loading?: ReactNode; children?: ReactNode }) {\n return (\n <ReadGate read={read} isEmpty empty={children ?? null} {...(loading === undefined ? {} : { loading })}>\n {null}\n </ReadGate>\n );\n}\n\n/**\n * Above a primitive's ROWS: when the latest read failed, the rows on screen are\n * from an earlier read — say so (StaleDataNotice with its retry), never blank them.\n */\nexport function ReadStaleNotice({ read, className }: { read?: ReadOutcome | undefined; className?: string }) {\n if (read?.status !== \"error\") return null;\n const what = read.what ?? \"this list\";\n if (!read.onRetry) return <ReadFailure error={read.error ?? true} what={what} {...(className ? { className } : {})} />;\n return <StaleDataNotice hasData what={what} onRetry={read.onRetry} {...(className ? { className } : {})} />;\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs"]}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-GMNROZQJ.js";
|
|
13
13
|
import {
|
|
14
14
|
ErrorBox
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-LJKF267O.js";
|
|
16
16
|
import {
|
|
17
17
|
Skeleton
|
|
18
18
|
} from "./chunk-DRSBLHOF.js";
|
|
@@ -778,4 +778,4 @@ export {
|
|
|
778
778
|
CanvasLayout,
|
|
779
779
|
ModeSwitch
|
|
780
780
|
};
|
|
781
|
-
//# sourceMappingURL=chunk-
|
|
781
|
+
//# sourceMappingURL=chunk-ZMUPFGQO.js.map
|
package/dist/controls.cjs
CHANGED
|
@@ -3960,6 +3960,7 @@ var Slider = React21.forwardRef(({ className, size = "md", ...props }, ref) => {
|
|
|
3960
3960
|
{
|
|
3961
3961
|
"data-slot": "slider-thumb",
|
|
3962
3962
|
...props["aria-label"] ? { "aria-label": props["aria-label"] } : {},
|
|
3963
|
+
...props["aria-valuetext"] !== void 0 ? { "aria-valuetext": props["aria-valuetext"] } : {},
|
|
3963
3964
|
className: cn(
|
|
3964
3965
|
"relative block cursor-grab rounded-full border border-primary/50 bg-background shadow transition-colors active:cursor-grabbing",
|
|
3965
3966
|
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
@@ -6135,6 +6136,85 @@ function IconDropdownMenu({
|
|
|
6135
6136
|
|
|
6136
6137
|
// src/error-box.tsx
|
|
6137
6138
|
var React29 = __toESM(require("react"), 1);
|
|
6139
|
+
|
|
6140
|
+
// src/desktop/errors.ts
|
|
6141
|
+
var RULES = [
|
|
6142
|
+
{ code: "ENOENT", test: /\b(spawn\s+\S+\s+)?ENOENT\b|command not found|no such file or directory/i, cause: "A program or file it needs isn't on this computer. Install it or update Matrx, then try again." },
|
|
6143
|
+
{ code: "EACCES", test: /\bEACCES\b|\bEPERM\b|permission denied|operation not permitted|not authori[sz]ed by the user/i, cause: "Matrx isn't allowed to do that. Grant access in System Settings \u203A Privacy & Security, then try again." },
|
|
6144
|
+
{ code: "EADDRINUSE", test: /\bEADDRINUSE\b|address already in use|port .* (is )?(already )?in use/i, cause: "Another program is already using that port. Stop it, or choose another port." },
|
|
6145
|
+
{ code: "ECONNREFUSED", test: /\bECONNREFUSED\b|connection refused/i, cause: "The service isn't answering. It may have stopped; start it again." },
|
|
6146
|
+
{ code: "OFFLINE", test: /\bENOTFOUND\b|\bEAI_AGAIN\b|\bENETUNREACH\b|getaddrinfo|network (is )?unreachable|failed to fetch|offline/i, cause: "Can't reach the internet. Check the connection and try again." },
|
|
6147
|
+
{ code: "TIMEOUT", test: /\bETIMEDOUT\b|timed? ?out|\bTIMEOUT\b|deadline exceeded/i, cause: "It took too long to answer. Try again in a moment." },
|
|
6148
|
+
{ code: "ENOSPC", test: /\bENOSPC\b|no space left/i, cause: "The disk is full. Free some space, then try again." },
|
|
6149
|
+
{ code: "ENOMEM", test: /\bENOMEM\b|out of memory|cannot allocate memory/i, cause: "The computer ran out of memory. Close something and try again." },
|
|
6150
|
+
{ code: "UNAUTHORIZED", test: /\b401\b|unauthori[sz]ed|invalid (api )?key|not signed in|jwt expired|session expired/i, cause: "You're signed out, or the key was refused. Sign in again." },
|
|
6151
|
+
{ code: "FORBIDDEN", test: /\b403\b|forbidden/i, cause: "This account isn't allowed to do that." },
|
|
6152
|
+
{ code: "NOT_FOUND", test: /\b404\b|not found/i, cause: "It isn't there any more. Refresh and try again." },
|
|
6153
|
+
{ code: "RATE_LIMIT", test: /\b429\b|rate.?limit|too many requests/i, cause: "Too many requests just now. Wait a moment and try again." },
|
|
6154
|
+
{ code: "SERVER", test: /\b5\d\d\b|internal server error|bad gateway|service unavailable/i, cause: "The server had a problem. Try again in a moment." },
|
|
6155
|
+
{ code: "EXITED", test: /exited with code|process exited|crashed|SIGKILL|SIGSEGV|SIGTERM/i, cause: "The service stopped unexpectedly. Start it again; Copy details if it keeps happening." }
|
|
6156
|
+
];
|
|
6157
|
+
var RAW = /\b[A-Z]{2,}[A-Z_]*\b|[\\/][\w.-]+[\\/]|\bat \S+ \(|\{|\}|errno|stack|undefined|null|\bspawn\b/;
|
|
6158
|
+
function rawText(error) {
|
|
6159
|
+
if (error instanceof Error) return error.message || error.name;
|
|
6160
|
+
if (typeof error === "string") return error;
|
|
6161
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
6162
|
+
return error.message;
|
|
6163
|
+
}
|
|
6164
|
+
return "";
|
|
6165
|
+
}
|
|
6166
|
+
function describeError(error, fallback = "Something went wrong. Try again.") {
|
|
6167
|
+
const detail = rawText(error).trim();
|
|
6168
|
+
if (!detail) return { cause: fallback, detail: "" };
|
|
6169
|
+
for (const rule of RULES) {
|
|
6170
|
+
if (rule.test.test(detail)) return { cause: rule.cause, detail, code: rule.code };
|
|
6171
|
+
}
|
|
6172
|
+
if (!RAW.test(detail) && detail.length <= 160) return { cause: detail, detail: "" };
|
|
6173
|
+
return { cause: fallback, detail };
|
|
6174
|
+
}
|
|
6175
|
+
|
|
6176
|
+
// src/error-sentence.ts
|
|
6177
|
+
function errorSentenceOf(error) {
|
|
6178
|
+
if (error == null || error === true || error === false) return "";
|
|
6179
|
+
if (typeof error === "string") return error;
|
|
6180
|
+
if (typeof error !== "object") return String(error);
|
|
6181
|
+
const message = error.message;
|
|
6182
|
+
return typeof message === "string" ? message : "";
|
|
6183
|
+
}
|
|
6184
|
+
var TIMED_OUT_SENTENCE = "That took too long. Nothing was lost; try again.";
|
|
6185
|
+
var FALLBACK_ERROR_SENTENCE = "Something went wrong. Try again.";
|
|
6186
|
+
function isTimedOutError(error, message, code) {
|
|
6187
|
+
if (code === "57014" || code === "timed_out") return true;
|
|
6188
|
+
if (error && typeof error === "object") {
|
|
6189
|
+
const o = error;
|
|
6190
|
+
if (o.code === "57014" || o.code === "timed_out" || o.sqlstate === "57014") return true;
|
|
6191
|
+
}
|
|
6192
|
+
const text = `${message ?? ""} ${errorSentenceOf(error)}`;
|
|
6193
|
+
return /canceling statement due to statement timeout/i.test(text);
|
|
6194
|
+
}
|
|
6195
|
+
function offlineText(text) {
|
|
6196
|
+
const t = text.trim();
|
|
6197
|
+
return t !== "" && (describeError(t).code === "OFFLINE" || /networkerror|network request failed|load failed/i.test(t));
|
|
6198
|
+
}
|
|
6199
|
+
var ANY_HOST_CODES = /* @__PURE__ */ new Set(["OFFLINE", "TIMEOUT", "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "RATE_LIMIT", "SERVER"]);
|
|
6200
|
+
var MACHINE_PREFIX = /^\s*(?:\[[\w./-]+\]\s*|[a-z][\w]*(?:[._][\w]+)+:\s+)/;
|
|
6201
|
+
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;
|
|
6202
|
+
function peopleErrorSentence(error, message, options = {}) {
|
|
6203
|
+
if (isTimedOutError(error, message, options.code)) return TIMED_OUT_SENTENCE;
|
|
6204
|
+
const fallback = options.fallback ?? FALLBACK_ERROR_SENTENCE;
|
|
6205
|
+
const raw = message?.trim() || errorSentenceOf(error).trim();
|
|
6206
|
+
if (!raw) return fallback;
|
|
6207
|
+
let text = raw;
|
|
6208
|
+
for (let i = 0; i < 3 && MACHINE_PREFIX.test(text); i++) text = text.replace(MACHINE_PREFIX, "").trim();
|
|
6209
|
+
if (!text) return fallback;
|
|
6210
|
+
if (offlineText(text) || RAW_TEXT.test(text) || text !== raw) {
|
|
6211
|
+
const said = describeError(text, fallback);
|
|
6212
|
+
return said.code && ANY_HOST_CODES.has(said.code) ? said.cause : fallback;
|
|
6213
|
+
}
|
|
6214
|
+
return text;
|
|
6215
|
+
}
|
|
6216
|
+
|
|
6217
|
+
// src/error-box.tsx
|
|
6138
6218
|
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
6139
6219
|
var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/design-system/error-actions");
|
|
6140
6220
|
var registry2 = globalThis;
|
|
@@ -6150,6 +6230,11 @@ function ErrorActions(props) {
|
|
|
6150
6230
|
if (!render) return null;
|
|
6151
6231
|
return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_jsx_runtime46.Fragment, { children: render(props) });
|
|
6152
6232
|
}
|
|
6233
|
+
function sayable(body, error) {
|
|
6234
|
+
if (typeof body === "string") return peopleErrorSentence(error, body);
|
|
6235
|
+
if (Array.isArray(body)) return body.map((b) => typeof b === "string" && b.trim() ? peopleErrorSentence(error, b) : b);
|
|
6236
|
+
return body;
|
|
6237
|
+
}
|
|
6153
6238
|
var ErrorBox = React29.forwardRef(function ErrorBox2({
|
|
6154
6239
|
as = "p",
|
|
6155
6240
|
message,
|
|
@@ -6173,7 +6258,7 @@ var ErrorBox = React29.forwardRef(function ErrorBox2({
|
|
|
6173
6258
|
...origin !== void 0 ? { origin } : {}
|
|
6174
6259
|
};
|
|
6175
6260
|
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(Tag, { ref, role: "alert", "data-error-box": "", ...rest, children: [
|
|
6176
|
-
children ?? message,
|
|
6261
|
+
sayable(children ?? message, error),
|
|
6177
6262
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(ErrorActions, { ...facts })
|
|
6178
6263
|
] });
|
|
6179
6264
|
});
|