@ai-matrx/kit 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 — 2026-09-07
4
+
5
+ **THE CONFIRM DIALOG LEAVES THIS PACKAGE.** `@ai-matrx/kit/confirm` is removed.
6
+ The dialog body — `ConfirmDialog` and `ConfirmDialogHost` — now lives in
7
+ **`@ai-matrx/design-system` 0.11.0**. What stays here is
8
+ `@ai-matrx/kit/confirm-opener`: the pure imperative `confirm()`, the host
9
+ registry, and the request queue, unchanged.
10
+
11
+ **Why.** kit's confirm shipped its own inlined AlertDialog wearing
12
+ `data-[state=open]:animate-in … zoom-in-95 … slide-in-from-top-[48%]` — utilities
13
+ the `tailwindcss-animate` / `tw-animate-css` HOST plugin supplies, which a
14
+ node_modules package can neither ship nor require — plus a hardcoded
15
+ `bg-black/80` scrim. kit ships no stylesheet at all, so in three of the four
16
+ consumers the most consequential surface in the product (the one standing between
17
+ a user and a destroyed thing) appeared with no animation, no error, and a scrim
18
+ that ignored the theme. Its unclamped content could also push Cancel and Continue
19
+ below the fold of a short viewport — and an AlertDialog does not dismiss on a
20
+ backdrop click, so a long confirmation was a genuine dead end.
21
+
22
+ kit could not import design-system's 0.10.0 fix: **design-system depends on kit**,
23
+ so importing back is a cycle. Giving kit its own `styles.css` would invent a
24
+ second required-CSS contract no consumer performs — the same silent host contract
25
+ one layer down (C26). So the surface moved to the package that owns the surface,
26
+ exactly as the Tooltip did in tap-target 0.2.0 (census rows 19i / 20a).
27
+
28
+ **No shim.** There is no `./confirm` re-export. One surface, one owner — and the
29
+ packed-tarball canary now asserts the ABSENCE of `./confirm` and of both dropped
30
+ dependencies, so the twin cannot grow back.
31
+
32
+ ### Removed
33
+
34
+ - `@ai-matrx/kit/confirm` (subpath, entry, and every export: `ConfirmDialog`,
35
+ `ConfirmDialogProps`, `ConfirmDialogHost`, and the `confirm` re-export).
36
+ - `confirm` / `ConfirmOptions` from the ROOT barrel (`@ai-matrx/kit`) — the
37
+ opener is not re-exported there, because the root barrel is `"use client"`
38
+ stamped and the opener must stay importable from Server Components, API routes
39
+ and Node scripts.
40
+ - Runtime dependencies `@radix-ui/react-alert-dialog` and `tailwind-merge`. Both
41
+ existed only for the dialog; kit's install is that much smaller.
42
+
43
+ ### Unchanged
44
+
45
+ - `@ai-matrx/kit/confirm-opener` — `confirm`, `ConfirmOptions`, and the
46
+ `Symbol.for("ai-matrx.kit.confirm-opener-state")` globalThis slot the host
47
+ registry lives on. Every behavior is identical: pre-mount calls queue,
48
+ concurrent calls present one at a time, and with no host mounted a `confirm()`
49
+ promise stays pending forever rather than inventing a silent default.
50
+
51
+ ### Consumer action (C28)
52
+
53
+ **Every consumer that mounted the kit confirm host or rendered the kit dialog
54
+ must repoint. Two mechanical edits, no behavior change.**
55
+
56
+ 1. **Take `@ai-matrx/design-system` >= 0.11.0** alongside kit >= 0.9.0 (specs stay
57
+ `"latest"` / `workspace:*` per C15 — this is an install, not a spec change).
58
+
59
+ 2. **The host** — wherever you mounted it:
60
+
61
+ ```diff
62
+ - import { ConfirmDialogHost } from "@ai-matrx/kit/confirm";
63
+ + import { ConfirmDialogHost } from "@ai-matrx/design-system";
64
+ ```
65
+
66
+ Next.js hosts that lazy-loaded it for the fragmentation law change only the
67
+ module specifier:
68
+
69
+ ```diff
70
+ - dynamic(() => import("@ai-matrx/kit/confirm").then((m) => m.ConfirmDialogHost), { ssr: false })
71
+ + dynamic(() => import("@ai-matrx/design-system").then((m) => m.ConfirmDialogHost), { ssr: false })
72
+ ```
73
+
74
+ 3. **The declarative dialog:**
75
+
76
+ ```diff
77
+ - import { ConfirmDialog, type ConfirmDialogProps } from "@ai-matrx/kit/confirm";
78
+ + import { ConfirmDialog, type ConfirmDialogProps } from "@ai-matrx/design-system";
79
+ ```
80
+
81
+ Props are identical. `portalContainer` still wins; with it omitted the dialog
82
+ now also honours design-system's `PortalContainerProvider`, so a host that
83
+ already provides that container can drop its own wrapper.
84
+
85
+ 4. **The imperative `confirm()`** — if you imported it from `@ai-matrx/kit/confirm`
86
+ or from the kit ROOT, repoint to the opener (this is the light-twin entry every
87
+ call site should have been using anyway):
88
+
89
+ ```diff
90
+ - import { confirm } from "@ai-matrx/kit/confirm";
91
+ + import { confirm } from "@ai-matrx/kit/confirm-opener";
92
+ ```
93
+
94
+ 5. **Load design-system's stylesheet** if you do not already — `@ai-matrx/design-system/styles.css`
95
+ plus `tokens.css`. This is what the move buys you: the confirm surface finally
96
+ animates and scrims from your theme in EVERY consumer, not only in the one whose
97
+ CSS entry happened to load an animation plugin.
98
+
3
99
  ## 0.8.0 — 2026-09-07
4
100
 
5
101
  **THE HOME FOR THE FLEET'S SHARED FORMATTERS.** Three new pure subpaths —
package/README.md CHANGED
@@ -2,12 +2,19 @@
2
2
 
3
3
  The always-include AI Matrx kit: the little primitives every Matrx application speaks,
4
4
  one capability per subpath, tree-shaken to what you use. React is the only required
5
- peer; only four subpaths carry runtime dependencies, each because the dependency IS
6
- the capability: `/confirm` (`@radix-ui/react-alert-dialog` + `tailwind-merge`its
7
- product is the dialog), `/json-format` (`json5` — tolerant parsing is the feature),
5
+ peer; only three subpaths carry runtime dependencies, each because the dependency IS
6
+ the capability: `/json-format` (`json5` — tolerant parsing is the feature),
8
7
  `/idb-store` (`idb` — the typed IndexedDB wrapper is the engine), and `/qr` (`jsqr`,
9
8
  loaded lazily only when the browser has no native `BarcodeDetector`).
10
9
 
10
+ **The confirm DIALOG is not here — the opener is.** Since 0.9.0 the body
11
+ (`ConfirmDialog`, `ConfirmDialogHost`) lives in
12
+ [`@ai-matrx/design-system`](../design-system/README.md), which owns the AlertDialog,
13
+ the shipped motion layer and the overlay scrim token. Mount
14
+ `<ConfirmDialogHost />` from there once at your app root, then call `confirm()`
15
+ from `@ai-matrx/kit/confirm-opener` anywhere. One surface, one owner: kit
16
+ re-exports none of the dialog.
17
+
11
18
  ```bash
12
19
  npm install @ai-matrx/kit
13
20
  ```
@@ -22,7 +29,7 @@ npm install @ai-matrx/kit
22
29
  | `@ai-matrx/kit/concurrency` | `runWithConcurrency` | Bounded worker pool over an array with per-item failure isolation and a cooperative `shouldStart()` cancel hook. |
23
30
  | `@ai-matrx/kit/text-case` | `formatText`, `createFormatter` | Acronym-aware normalization of messy DB/API strings into title/sentence case with word-replacement overrides. |
24
31
  | `@ai-matrx/kit/drafts` | `useDurableDraft` + the local-drafts store | User-authored text that survives anything: write-through localStorage drafts, restore on mount, key-change adoption, cleared only after content durably landed. |
25
- | `@ai-matrx/kit/confirm` | `confirm`, `ConfirmDialogHost`, `ConfirmDialog` | The `window.confirm` replacement, both shapes: mount `<ConfirmDialogHost/>` once at app root, then `await confirm({ title, variant: "destructive" })` from anywhere (pre-mount calls queue; concurrent calls present one at a time) or the declarative `<ConfirmDialog/>` when in-dialog busy state matters. Bundles `@radix-ui/react-alert-dialog`; Tailwind semantic-token styling, overridable via `className`/`contentClassName`. |
32
+ | `@ai-matrx/kit/confirm-opener` | `confirm`, `ConfirmOptions` | The `window.confirm` replacement's imperative half — pure TS, zero React, zero radix, no `"use client"`, so a Redux thunk, a util or a sync handler can import it at near-zero bundle cost. `await confirm({ title, description, variant: "destructive" })` resolves `true`/`false`; pre-mount calls queue and concurrent calls present one at a time; with no host mounted it stays pending forever rather than inventing a silent default. Needs `<ConfirmDialogHost />` from `@ai-matrx/design-system` mounted once the two share one registry through a `globalThis` slot. |
26
33
  | `@ai-matrx/kit/toast` | `createMatrxToast` | The captured sonner wrapper as a factory: pass your sonner `toast` in, get a drop-in `toast` back whose `.error`/`.warning` also feed your injected error-capture sink, plus `toastErrorAlreadyCaptured` for notices whose failure was already captured upstream. No sonner import — and sonner is not declared as a dependency or peer at all. |
27
34
  | `@ai-matrx/kit/invalidation` | `registerInvalidationCallback`, `fireInvalidation` | Zero-import, name-keyed callback registry that breaks import edges into heavy chunk clusters: the heavy cluster registers at its own module init, the ubiquitous module fires by name. Unregistered names are a deliberate no-op; a throwing callback screams and never breaks the caller. Keep your key constants in ONE host module. |
28
35
  | `@ai-matrx/kit/delimiter-guard` | `guardMarkdownDelimiters` (+ `guardMathDelimiters`, `guardRunawayLinks`, `reportDelimiterViolations`) | Keeps one stray streamed delimiter from swallowing a whole section: a `$$` that would turn prose into a red KaTeX error span, or an unclosed `[` that would turn a section into one giant link, gets invisibly neutralized while real math and links keep rendering. Pure; report violations loudly via the optional injected capture sink. |
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/confirm-opener.ts","../src/confirm/opener.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/confirm-opener — the imperative `confirm()` API alone.\n *\n * Same module the `./confirm` subpath exposes, WITHOUT the dialog body: zero\n * React runtime, zero radix, no \"use client\" banner — statically importable\n * from anywhere (hooks, utils, Redux thunks, sync code) at near-zero bundle\n * cost. Hosts that mount `ConfirmDialogHost` behind a lazy boundary (the\n * fragmentation-law pattern) import `confirm` from HERE so the hundreds of\n * imperative call sites never drag the dialog chunk into their static graph.\n *\n * State is shared with `./confirm` by construction: the opener's host/queue\n * live on the `Symbol.for(\"ai-matrx.kit.confirm-opener-state\")` globalThis\n * slot, so both entries observe the same registration.\n */\nexport {\n confirm,\n _registerHost,\n _unregisterHost,\n _resetConfirmOpenerState,\n type ConfirmOptions,\n} from \"./confirm/opener\";\n","/**\n * @ai-matrx/kit/confirm — imperative opener.\n *\n * Pure-TS imperative API for the global confirm dialog. Zero React, zero\n * dialog markup — this module is statically importable from anywhere\n * (hooks, utils, Redux thunks, async handlers, sync code, anything).\n *\n * The host (`ConfirmDialogHost`) registers a controller on mount and\n * unregisters on unmount. Calls made before the host has hydrated queue\n * up and resolve as soon as the host is alive — so a destructive action\n * triggered in the first ~50ms after page load still gets a real\n * confirmation, never a silent default-yes/no. With no host ever mounted,\n * a `confirm()` promise stays pending forever (the original's behavior —\n * it never resolves to a silent default).\n *\n * One dialog at a time: concurrent calls queue and present sequentially.\n *\n * Ported verbatim from matrx-frontend\n * `components/dialogs/confirm/confirmDialogOpener.ts`, with ONE structural\n * inversion: the host/queue state lives on `globalThis` under a\n * `Symbol.for` slot instead of module-level variables. With the package\n * built `splitting: false` in dual ESM/CJS format, this module is\n * duplicated into the root bundle and the `./confirm` bundle, and CJS/ESM\n * each instantiate their own module graph — a module-level variable would\n * silently split the host registration from the callers (the same hazard\n * `@ai-matrx/tap-target` documents for its link registry). Behavior is\n * unchanged; never \"clean this up\" into a module local.\n */\n\nimport type { ReactNode } from \"react\";\n\nexport interface ConfirmOptions {\n title: ReactNode;\n description?: ReactNode | undefined;\n confirmLabel?: string | undefined;\n /** `null` hides the cancel button (acknowledge-only dialogs). */\n cancelLabel?: string | null | undefined;\n variant?: \"default\" | \"destructive\" | undefined;\n}\n\ntype Resolver = (confirmed: boolean) => void;\n\ninterface PendingRequest {\n opts: ConfirmOptions;\n resolve: Resolver;\n}\n\ninterface HostController {\n show: (opts: ConfirmOptions, resolve: Resolver) => void;\n}\n\ninterface OpenerState {\n host: HostController | null;\n queue: PendingRequest[];\n}\n\nconst STATE_SLOT = Symbol.for(\"ai-matrx.kit.confirm-opener-state\");\n\nfunction getState(): OpenerState {\n const holder = globalThis as Record<symbol, OpenerState | undefined>;\n let state = holder[STATE_SLOT];\n if (!state) {\n state = { host: null, queue: [] };\n holder[STATE_SLOT] = state;\n }\n return state;\n}\n\n/** @internal Called by `ConfirmDialogHost` on mount. */\nexport function _registerHost(controller: HostController): void {\n const state = getState();\n state.host = controller;\n while (state.queue.length > 0) {\n const next = state.queue.shift()!;\n controller.show(next.opts, next.resolve);\n }\n}\n\n/** @internal Called by `ConfirmDialogHost` on unmount. */\nexport function _unregisterHost(controller: HostController): void {\n const state = getState();\n if (state.host === controller) state.host = null;\n}\n\n/** @internal Test-only: drop any registered host and pending queue. */\nexport function _resetConfirmOpenerState(): void {\n const state = getState();\n state.host = null;\n state.queue.length = 0;\n}\n\n/**\n * Imperative confirm. Returns a Promise that resolves `true` if the user\n * confirms, `false` if they cancel/dismiss. Replaces `window.confirm`.\n *\n * @example\n * const ok = await confirm({\n * title: \"Delete sandbox\",\n * description: \"This cannot be undone.\",\n * variant: \"destructive\",\n * confirmLabel: \"Delete\",\n * });\n * if (!ok) return;\n */\nexport function confirm(opts: ConfirmOptions): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n const state = getState();\n if (state.host) {\n state.host.show(opts, resolve);\n } else {\n state.queue.push({ opts, resolve });\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwDA,IAAM,aAAa,uBAAO,IAAI,mCAAmC;AAEjE,SAAS,WAAwB;AAC/B,QAAM,SAAS;AACf,MAAI,QAAQ,OAAO,UAAU;AAC7B,MAAI,CAAC,OAAO;AACV,YAAQ,EAAE,MAAM,MAAM,OAAO,CAAC,EAAE;AAChC,WAAO,UAAU,IAAI;AAAA,EACvB;AACA,SAAO;AACT;AAGO,SAAS,cAAc,YAAkC;AAC9D,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,SAAO,MAAM,MAAM,SAAS,GAAG;AAC7B,UAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,eAAW,KAAK,KAAK,MAAM,KAAK,OAAO;AAAA,EACzC;AACF;AAGO,SAAS,gBAAgB,YAAkC;AAChE,QAAM,QAAQ,SAAS;AACvB,MAAI,MAAM,SAAS,WAAY,OAAM,OAAO;AAC9C;AAGO,SAAS,2BAAiC;AAC/C,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,QAAM,MAAM,SAAS;AACvB;AAeO,SAAS,QAAQ,MAAwC;AAC9D,SAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,UAAM,QAAQ,SAAS;AACvB,QAAI,MAAM,MAAM;AACd,YAAM,KAAK,KAAK,MAAM,OAAO;AAAA,IAC/B,OAAO;AACL,YAAM,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,IACpC;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/confirm-opener.ts","../src/confirm/opener.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/confirm-opener — the imperative `confirm()` API alone.\n *\n * THE WHOLE CONFIRM SYSTEM THIS PACKAGE SHIPS (since 0.9.0, census row 19i).\n * The dialog BODY — `ConfirmDialog` and `ConfirmDialogHost` — lives in\n * `@ai-matrx/design-system`, which owns the AlertDialog, the shipped motion\n * layer and the `--matrx-overlay-scrim` token this surface needs and kit could\n * neither ship nor import (design-system depends on kit; importing back is a\n * cycle). kit re-exports none of it: one surface, one owner.\n *\n * What is here is the pure half: zero React runtime, zero radix, no\n * \"use client\" banner — statically importable from anywhere (hooks, utils,\n * Redux thunks, sync code) at near-zero bundle cost, which is what lets a host\n * keep hundreds of imperative call sites out of the dialog's chunk.\n *\n * Wiring, end to end:\n * 1. mount `<ConfirmDialogHost />` from `@ai-matrx/design-system` once, near\n * the root of every provider tree;\n * 2. `await confirm({ ... })` from anywhere, importing from HERE.\n * The two find each other through the\n * `Symbol.for(\"ai-matrx.kit.confirm-opener-state\")` globalThis slot, so they\n * observe one registration across every module graph and both packages.\n */\nexport {\n confirm,\n _registerHost,\n _unregisterHost,\n _resetConfirmOpenerState,\n type ConfirmOptions,\n} from \"./confirm/opener\";\n","/**\n * @ai-matrx/kit/confirm-openerthe imperative opener, and since 0.9.0 the\n * ONLY half of the confirm system this package ships.\n *\n * Pure-TS imperative API for the global confirm dialog. Zero React, zero\n * dialog markup — this module is statically importable from anywhere\n * (hooks, utils, Redux thunks, async handlers, sync code, anything).\n *\n * The host (`ConfirmDialogHost`, in `@ai-matrx/design-system`) registers a\n * controller on mount and\n * unregisters on unmount. Calls made before the host has hydrated queue\n * up and resolve as soon as the host is alive — so a destructive action\n * triggered in the first ~50ms after page load still gets a real\n * confirmation, never a silent default-yes/no. With no host ever mounted,\n * a `confirm()` promise stays pending forever (the original's behavior —\n * it never resolves to a silent default).\n *\n * One dialog at a time: concurrent calls queue and present sequentially.\n *\n * Ported verbatim from matrx-frontend\n * `components/dialogs/confirm/confirmDialogOpener.ts`, with ONE structural\n * inversion: the host/queue state lives on `globalThis` under a\n * `Symbol.for` slot instead of module-level variables. With the package\n * built `splitting: false` in dual ESM/CJS format, and the host now living in\n * a DIFFERENT PACKAGE, CJS/ESM each instantiate their own module graph — a module-level variable would\n * silently split the host registration from the callers (the same hazard\n * `@ai-matrx/tap-target` documents for its link registry). Behavior is\n * unchanged; never \"clean this up\" into a module local.\n */\n\nimport type { ReactNode } from \"react\";\n\nexport interface ConfirmOptions {\n title: ReactNode;\n description?: ReactNode | undefined;\n confirmLabel?: string | undefined;\n /** `null` hides the cancel button (acknowledge-only dialogs). */\n cancelLabel?: string | null | undefined;\n variant?: \"default\" | \"destructive\" | undefined;\n}\n\ntype Resolver = (confirmed: boolean) => void;\n\ninterface PendingRequest {\n opts: ConfirmOptions;\n resolve: Resolver;\n}\n\ninterface HostController {\n show: (opts: ConfirmOptions, resolve: Resolver) => void;\n}\n\ninterface OpenerState {\n host: HostController | null;\n queue: PendingRequest[];\n}\n\nconst STATE_SLOT = Symbol.for(\"ai-matrx.kit.confirm-opener-state\");\n\nfunction getState(): OpenerState {\n const holder = globalThis as Record<symbol, OpenerState | undefined>;\n let state = holder[STATE_SLOT];\n if (!state) {\n state = { host: null, queue: [] };\n holder[STATE_SLOT] = state;\n }\n return state;\n}\n\n/** @internal Called by `ConfirmDialogHost` on mount. */\nexport function _registerHost(controller: HostController): void {\n const state = getState();\n state.host = controller;\n while (state.queue.length > 0) {\n const next = state.queue.shift()!;\n controller.show(next.opts, next.resolve);\n }\n}\n\n/** @internal Called by `ConfirmDialogHost` on unmount. */\nexport function _unregisterHost(controller: HostController): void {\n const state = getState();\n if (state.host === controller) state.host = null;\n}\n\n/** @internal Test-only: drop any registered host and pending queue. */\nexport function _resetConfirmOpenerState(): void {\n const state = getState();\n state.host = null;\n state.queue.length = 0;\n}\n\n/**\n * Imperative confirm. Returns a Promise that resolves `true` if the user\n * confirms, `false` if they cancel/dismiss. Replaces `window.confirm`.\n *\n * @example\n * const ok = await confirm({\n * title: \"Delete sandbox\",\n * description: \"This cannot be undone.\",\n * variant: \"destructive\",\n * confirmLabel: \"Delete\",\n * });\n * if (!ok) return;\n */\nexport function confirm(opts: ConfirmOptions): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n const state = getState();\n if (state.host) {\n state.host.show(opts, resolve);\n } else {\n state.queue.push({ opts, resolve });\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyDA,IAAM,aAAa,uBAAO,IAAI,mCAAmC;AAEjE,SAAS,WAAwB;AAC/B,QAAM,SAAS;AACf,MAAI,QAAQ,OAAO,UAAU;AAC7B,MAAI,CAAC,OAAO;AACV,YAAQ,EAAE,MAAM,MAAM,OAAO,CAAC,EAAE;AAChC,WAAO,UAAU,IAAI;AAAA,EACvB;AACA,SAAO;AACT;AAGO,SAAS,cAAc,YAAkC;AAC9D,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,SAAO,MAAM,MAAM,SAAS,GAAG;AAC7B,UAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,eAAW,KAAK,KAAK,MAAM,KAAK,OAAO;AAAA,EACzC;AACF;AAGO,SAAS,gBAAgB,YAAkC;AAChE,QAAM,QAAQ,SAAS;AACvB,MAAI,MAAM,SAAS,WAAY,OAAM,OAAO;AAC9C;AAGO,SAAS,2BAAiC;AAC/C,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,QAAM,MAAM,SAAS;AACvB;AAeO,SAAS,QAAQ,MAAwC;AAC9D,SAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,UAAM,QAAQ,SAAS;AACvB,QAAI,MAAM,MAAM;AACd,YAAM,KAAK,KAAK,MAAM,OAAO;AAAA,IAC/B,OAAO;AACL,YAAM,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,IACpC;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -1,13 +1,15 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
3
  /**
4
- * @ai-matrx/kit/confirm — imperative opener.
4
+ * @ai-matrx/kit/confirm-openerthe imperative opener, and since 0.9.0 the
5
+ * ONLY half of the confirm system this package ships.
5
6
  *
6
7
  * Pure-TS imperative API for the global confirm dialog. Zero React, zero
7
8
  * dialog markup — this module is statically importable from anywhere
8
9
  * (hooks, utils, Redux thunks, async handlers, sync code, anything).
9
10
  *
10
- * The host (`ConfirmDialogHost`) registers a controller on mount and
11
+ * The host (`ConfirmDialogHost`, in `@ai-matrx/design-system`) registers a
12
+ * controller on mount and
11
13
  * unregisters on unmount. Calls made before the host has hydrated queue
12
14
  * up and resolve as soon as the host is alive — so a destructive action
13
15
  * triggered in the first ~50ms after page load still gets a real
@@ -21,9 +23,8 @@ import { ReactNode } from 'react';
21
23
  * `components/dialogs/confirm/confirmDialogOpener.ts`, with ONE structural
22
24
  * inversion: the host/queue state lives on `globalThis` under a
23
25
  * `Symbol.for` slot instead of module-level variables. With the package
24
- * built `splitting: false` in dual ESM/CJS format, this module is
25
- * duplicated into the root bundle and the `./confirm` bundle, and CJS/ESM
26
- * each instantiate their own module graph — a module-level variable would
26
+ * built `splitting: false` in dual ESM/CJS format, and the host now living in
27
+ * a DIFFERENT PACKAGE, CJS/ESM each instantiate their own module graph — a module-level variable would
27
28
  * silently split the host registration from the callers (the same hazard
28
29
  * `@ai-matrx/tap-target` documents for its link registry). Behavior is
29
30
  * unchanged; never "clean this up" into a module local.
@@ -1,13 +1,15 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
3
  /**
4
- * @ai-matrx/kit/confirm — imperative opener.
4
+ * @ai-matrx/kit/confirm-openerthe imperative opener, and since 0.9.0 the
5
+ * ONLY half of the confirm system this package ships.
5
6
  *
6
7
  * Pure-TS imperative API for the global confirm dialog. Zero React, zero
7
8
  * dialog markup — this module is statically importable from anywhere
8
9
  * (hooks, utils, Redux thunks, async handlers, sync code, anything).
9
10
  *
10
- * The host (`ConfirmDialogHost`) registers a controller on mount and
11
+ * The host (`ConfirmDialogHost`, in `@ai-matrx/design-system`) registers a
12
+ * controller on mount and
11
13
  * unregisters on unmount. Calls made before the host has hydrated queue
12
14
  * up and resolve as soon as the host is alive — so a destructive action
13
15
  * triggered in the first ~50ms after page load still gets a real
@@ -21,9 +23,8 @@ import { ReactNode } from 'react';
21
23
  * `components/dialogs/confirm/confirmDialogOpener.ts`, with ONE structural
22
24
  * inversion: the host/queue state lives on `globalThis` under a
23
25
  * `Symbol.for` slot instead of module-level variables. With the package
24
- * built `splitting: false` in dual ESM/CJS format, this module is
25
- * duplicated into the root bundle and the `./confirm` bundle, and CJS/ESM
26
- * each instantiate their own module graph — a module-level variable would
26
+ * built `splitting: false` in dual ESM/CJS format, and the host now living in
27
+ * a DIFFERENT PACKAGE, CJS/ESM each instantiate their own module graph — a module-level variable would
27
28
  * silently split the host registration from the callers (the same hazard
28
29
  * `@ai-matrx/tap-target` documents for its link registry). Behavior is
29
30
  * unchanged; never "clean this up" into a module local.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/confirm/opener.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/confirm — imperative opener.\n *\n * Pure-TS imperative API for the global confirm dialog. Zero React, zero\n * dialog markup — this module is statically importable from anywhere\n * (hooks, utils, Redux thunks, async handlers, sync code, anything).\n *\n * The host (`ConfirmDialogHost`) registers a controller on mount and\n * unregisters on unmount. Calls made before the host has hydrated queue\n * up and resolve as soon as the host is alive — so a destructive action\n * triggered in the first ~50ms after page load still gets a real\n * confirmation, never a silent default-yes/no. With no host ever mounted,\n * a `confirm()` promise stays pending forever (the original's behavior —\n * it never resolves to a silent default).\n *\n * One dialog at a time: concurrent calls queue and present sequentially.\n *\n * Ported verbatim from matrx-frontend\n * `components/dialogs/confirm/confirmDialogOpener.ts`, with ONE structural\n * inversion: the host/queue state lives on `globalThis` under a\n * `Symbol.for` slot instead of module-level variables. With the package\n * built `splitting: false` in dual ESM/CJS format, this module is\n * duplicated into the root bundle and the `./confirm` bundle, and CJS/ESM\n * each instantiate their own module graph — a module-level variable would\n * silently split the host registration from the callers (the same hazard\n * `@ai-matrx/tap-target` documents for its link registry). Behavior is\n * unchanged; never \"clean this up\" into a module local.\n */\n\nimport type { ReactNode } from \"react\";\n\nexport interface ConfirmOptions {\n title: ReactNode;\n description?: ReactNode | undefined;\n confirmLabel?: string | undefined;\n /** `null` hides the cancel button (acknowledge-only dialogs). */\n cancelLabel?: string | null | undefined;\n variant?: \"default\" | \"destructive\" | undefined;\n}\n\ntype Resolver = (confirmed: boolean) => void;\n\ninterface PendingRequest {\n opts: ConfirmOptions;\n resolve: Resolver;\n}\n\ninterface HostController {\n show: (opts: ConfirmOptions, resolve: Resolver) => void;\n}\n\ninterface OpenerState {\n host: HostController | null;\n queue: PendingRequest[];\n}\n\nconst STATE_SLOT = Symbol.for(\"ai-matrx.kit.confirm-opener-state\");\n\nfunction getState(): OpenerState {\n const holder = globalThis as Record<symbol, OpenerState | undefined>;\n let state = holder[STATE_SLOT];\n if (!state) {\n state = { host: null, queue: [] };\n holder[STATE_SLOT] = state;\n }\n return state;\n}\n\n/** @internal Called by `ConfirmDialogHost` on mount. */\nexport function _registerHost(controller: HostController): void {\n const state = getState();\n state.host = controller;\n while (state.queue.length > 0) {\n const next = state.queue.shift()!;\n controller.show(next.opts, next.resolve);\n }\n}\n\n/** @internal Called by `ConfirmDialogHost` on unmount. */\nexport function _unregisterHost(controller: HostController): void {\n const state = getState();\n if (state.host === controller) state.host = null;\n}\n\n/** @internal Test-only: drop any registered host and pending queue. */\nexport function _resetConfirmOpenerState(): void {\n const state = getState();\n state.host = null;\n state.queue.length = 0;\n}\n\n/**\n * Imperative confirm. Returns a Promise that resolves `true` if the user\n * confirms, `false` if they cancel/dismiss. Replaces `window.confirm`.\n *\n * @example\n * const ok = await confirm({\n * title: \"Delete sandbox\",\n * description: \"This cannot be undone.\",\n * variant: \"destructive\",\n * confirmLabel: \"Delete\",\n * });\n * if (!ok) return;\n */\nexport function confirm(opts: ConfirmOptions): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n const state = getState();\n if (state.host) {\n state.host.show(opts, resolve);\n } else {\n state.queue.push({ opts, resolve });\n }\n });\n}\n"],"mappings":";AAwDA,IAAM,aAAa,uBAAO,IAAI,mCAAmC;AAEjE,SAAS,WAAwB;AAC/B,QAAM,SAAS;AACf,MAAI,QAAQ,OAAO,UAAU;AAC7B,MAAI,CAAC,OAAO;AACV,YAAQ,EAAE,MAAM,MAAM,OAAO,CAAC,EAAE;AAChC,WAAO,UAAU,IAAI;AAAA,EACvB;AACA,SAAO;AACT;AAGO,SAAS,cAAc,YAAkC;AAC9D,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,SAAO,MAAM,MAAM,SAAS,GAAG;AAC7B,UAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,eAAW,KAAK,KAAK,MAAM,KAAK,OAAO;AAAA,EACzC;AACF;AAGO,SAAS,gBAAgB,YAAkC;AAChE,QAAM,QAAQ,SAAS;AACvB,MAAI,MAAM,SAAS,WAAY,OAAM,OAAO;AAC9C;AAGO,SAAS,2BAAiC;AAC/C,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,QAAM,MAAM,SAAS;AACvB;AAeO,SAAS,QAAQ,MAAwC;AAC9D,SAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,UAAM,QAAQ,SAAS;AACvB,QAAI,MAAM,MAAM;AACd,YAAM,KAAK,KAAK,MAAM,OAAO;AAAA,IAC/B,OAAO;AACL,YAAM,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,IACpC;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/confirm/opener.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/confirm-openerthe imperative opener, and since 0.9.0 the\n * ONLY half of the confirm system this package ships.\n *\n * Pure-TS imperative API for the global confirm dialog. Zero React, zero\n * dialog markup — this module is statically importable from anywhere\n * (hooks, utils, Redux thunks, async handlers, sync code, anything).\n *\n * The host (`ConfirmDialogHost`, in `@ai-matrx/design-system`) registers a\n * controller on mount and\n * unregisters on unmount. Calls made before the host has hydrated queue\n * up and resolve as soon as the host is alive — so a destructive action\n * triggered in the first ~50ms after page load still gets a real\n * confirmation, never a silent default-yes/no. With no host ever mounted,\n * a `confirm()` promise stays pending forever (the original's behavior —\n * it never resolves to a silent default).\n *\n * One dialog at a time: concurrent calls queue and present sequentially.\n *\n * Ported verbatim from matrx-frontend\n * `components/dialogs/confirm/confirmDialogOpener.ts`, with ONE structural\n * inversion: the host/queue state lives on `globalThis` under a\n * `Symbol.for` slot instead of module-level variables. With the package\n * built `splitting: false` in dual ESM/CJS format, and the host now living in\n * a DIFFERENT PACKAGE, CJS/ESM each instantiate their own module graph — a module-level variable would\n * silently split the host registration from the callers (the same hazard\n * `@ai-matrx/tap-target` documents for its link registry). Behavior is\n * unchanged; never \"clean this up\" into a module local.\n */\n\nimport type { ReactNode } from \"react\";\n\nexport interface ConfirmOptions {\n title: ReactNode;\n description?: ReactNode | undefined;\n confirmLabel?: string | undefined;\n /** `null` hides the cancel button (acknowledge-only dialogs). */\n cancelLabel?: string | null | undefined;\n variant?: \"default\" | \"destructive\" | undefined;\n}\n\ntype Resolver = (confirmed: boolean) => void;\n\ninterface PendingRequest {\n opts: ConfirmOptions;\n resolve: Resolver;\n}\n\ninterface HostController {\n show: (opts: ConfirmOptions, resolve: Resolver) => void;\n}\n\ninterface OpenerState {\n host: HostController | null;\n queue: PendingRequest[];\n}\n\nconst STATE_SLOT = Symbol.for(\"ai-matrx.kit.confirm-opener-state\");\n\nfunction getState(): OpenerState {\n const holder = globalThis as Record<symbol, OpenerState | undefined>;\n let state = holder[STATE_SLOT];\n if (!state) {\n state = { host: null, queue: [] };\n holder[STATE_SLOT] = state;\n }\n return state;\n}\n\n/** @internal Called by `ConfirmDialogHost` on mount. */\nexport function _registerHost(controller: HostController): void {\n const state = getState();\n state.host = controller;\n while (state.queue.length > 0) {\n const next = state.queue.shift()!;\n controller.show(next.opts, next.resolve);\n }\n}\n\n/** @internal Called by `ConfirmDialogHost` on unmount. */\nexport function _unregisterHost(controller: HostController): void {\n const state = getState();\n if (state.host === controller) state.host = null;\n}\n\n/** @internal Test-only: drop any registered host and pending queue. */\nexport function _resetConfirmOpenerState(): void {\n const state = getState();\n state.host = null;\n state.queue.length = 0;\n}\n\n/**\n * Imperative confirm. Returns a Promise that resolves `true` if the user\n * confirms, `false` if they cancel/dismiss. Replaces `window.confirm`.\n *\n * @example\n * const ok = await confirm({\n * title: \"Delete sandbox\",\n * description: \"This cannot be undone.\",\n * variant: \"destructive\",\n * confirmLabel: \"Delete\",\n * });\n * if (!ok) return;\n */\nexport function confirm(opts: ConfirmOptions): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n const state = getState();\n if (state.host) {\n state.host.show(opts, resolve);\n } else {\n state.queue.push({ opts, resolve });\n }\n });\n}\n"],"mappings":";AAyDA,IAAM,aAAa,uBAAO,IAAI,mCAAmC;AAEjE,SAAS,WAAwB;AAC/B,QAAM,SAAS;AACf,MAAI,QAAQ,OAAO,UAAU;AAC7B,MAAI,CAAC,OAAO;AACV,YAAQ,EAAE,MAAM,MAAM,OAAO,CAAC,EAAE;AAChC,WAAO,UAAU,IAAI;AAAA,EACvB;AACA,SAAO;AACT;AAGO,SAAS,cAAc,YAAkC;AAC9D,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,SAAO,MAAM,MAAM,SAAS,GAAG;AAC7B,UAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,eAAW,KAAK,KAAK,MAAM,KAAK,OAAO;AAAA,EACzC;AACF;AAGO,SAAS,gBAAgB,YAAkC;AAChE,QAAM,QAAQ,SAAS;AACvB,MAAI,MAAM,SAAS,WAAY,OAAM,OAAO;AAC9C;AAGO,SAAS,2BAAiC;AAC/C,QAAM,QAAQ,SAAS;AACvB,QAAM,OAAO;AACb,QAAM,MAAM,SAAS;AACvB;AAeO,SAAS,QAAQ,MAAwC;AAC9D,SAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,UAAM,QAAQ,SAAS;AACvB,QAAI,MAAM,MAAM;AACd,YAAM,KAAK,KAAK,MAAM,OAAO;AAAA,IAC/B,OAAO;AACL,YAAM,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,IACpC;AAAA,EACF,CAAC;AACH;","names":[]}