@ai-matrx/kit
v0.16.2
Published
The always-include AI Matrx kit: the little primitives every Matrx app speaks — autosave that never loses a keystroke, stale-response guards, clipboard with graceful fallbacks — one per subpath, tree-shaken to what you use.
Maintainers
Readme
@ai-matrx/kit
The always-include AI Matrx kit: the little primitives every Matrx application speaks,
one capability per subpath, tree-shaken to what you use. React is the only required
peer; only three subpaths carry runtime dependencies, each because the dependency IS
the capability: /json-format (json5 — tolerant parsing is the feature),
/idb-store (idb — the typed IndexedDB wrapper is the engine), and /qr (jsqr,
loaded lazily only when the browser has no native BarcodeDetector).
The confirm DIALOG is not here — the opener is. Since 0.9.0 the body
(ConfirmDialog, ConfirmDialogHost) lives in
@ai-matrx/design-system, which owns the AlertDialog,
the shipped motion layer and the overlay scrim token. Mount
<ConfirmDialogHost /> from there once at your app root, then call confirm()
from @ai-matrx/kit/confirm-opener anywhere. Since 0.10.0 that opener is a
thin specialisation of @ai-matrx/kit/opener — the same engine any app can use
for its own imperative dialogs. One surface, one owner: kit
re-exports none of the dialog.
npm install @ai-matrx/kit| Subpath | Export | What it does |
|---|---|---|
| @ai-matrx/kit/autosave | useAutosave | Debounced autosave with in-flight coalescing, a UI status (idle/unsaved/saving/saved/error), loud non-throwing failures that re-queue the payload, and flush-on-unmount so no keystroke is ever lost. |
| @ai-matrx/kit/latest-request | useLatestRequest | Makes a superseded async response impossible to apply: claim an attempt, then check the predicate before every state write after an await. Kills the out-of-order-response bug (the wrong record under a confident label) at the only moment that matters — apply time. Render-stable by construction. |
| @ai-matrx/kit/clipboard | useClipboard | Copy/paste text, links, and images with the browser quirks solved: canvas→PNG re-encode for image copy, Safari clipboard.read feature detection, param-stripping link copy. Notifier injected — wrap your own toast. |
| @ai-matrx/kit/content-transfer | snapshot, preparation, serializers, createBrowserTransport | Immutable projected content snapshots, explicit omission evidence, JSON/table formats, and rich clipboard/download outcomes. Browser APIs are used only when delivery is requested. |
Surface handles omit own object properties whose captured value is undefined, recursively, so host optional fields retain absent-value semantics. normalizeTransferJson(value) stays strict by default; a caller must explicitly pass { omitUndefinedObjectProperties: true } to request the same object-only omission. Array undefined entries or sparse slots still fail at their JSON pointer.
| @ai-matrx/kit/search-scoring | filterAndSortBySearch, computeSearchScore, matchesSearch, idMatchesQuery | Relevance-weighted search ranking — title above description, exact above prefix above includes, pasted-UUID matching — instead of the naive .includes() filter. |
| @ai-matrx/kit/concurrency | runWithConcurrency | Bounded worker pool over an array with per-item failure isolation and a cooperative shouldStart() cancel hook. |
| @ai-matrx/kit/text-case | formatText, createFormatter | Acronym-aware normalization of messy DB/API strings into title/sentence case with word-replacement overrides. |
| @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. |
| @ai-matrx/kit/opener | createOpener, Opener, OpenerHostController, OpenerAbortError | THE opener pattern, once. Build an imperative await open(request) for any global dialog: pure TS, zero React, so hundreds of call sites (thunks, utils, sync handlers) import it at near-zero cost while the heavy body stays behind a lazy host. You get the whole contract for free — one host at a time, requests queued in call order before the host hydrates, never a silent default answer, a console scream naming the component to mount when a request waits with no host, opt-in dedupe so a double-clicked action cannot stack two dialogs, and opt-in AbortSignal cancellation. State lives on a globalThis Symbol.for(slot), so host and caller find each other across packages AND across ESM/CJS module graphs. |
| @ai-matrx/kit/credential-generator | generateCredentialSecret, CREDENTIAL_GENERATION_TECHNICAL_LIMITS | A pure local Web Crypto password/passphrase generator. Hosts supply validated feature limits within the published technical work ceilings; the generator never stores, logs, fetches, or falls back from secure entropy. |
| @ai-matrx/kit/opener-react | useOpenerHost | The mount side of an opener: registers the component as THE host, drains queued requests one at a time, and hands back { request, open, settle }. React only — no markup, no CSS — so the dialog body stays entirely yours. |
| @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. |
| @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. |
| @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. |
| @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. |
| @ai-matrx/kit/json-format | detectJson, formatJsonText, stringifyJson | "Is this text JSON, and what shape should it be in?" Detection survives code fences, surrounding prose, and tolerant (JSON5) input — looksLikeJson gates offering, ok gates running. The writer does minify / width-aware compact (inline + pack) / pretty, key sorting, fence add/strip/preserve. Never throws, never mangles text it did not parse. |
| @ai-matrx/kit/idle-scheduler | useIdleTask, useIdleReady, useIdleGate, useIdleRegister + registerIdleTask, onFlushComplete, whenPageIdle | Priority-aware deferred execution: work waits for document load → paint → true browser idle (scheduler.postTask / requestIdleCallback / MessageChannel fallback, hidden tabs still flush), then runs 1-first…5-last. Zero re-renders to register; one re-render for the ready signal. |
| @ai-matrx/kit/url-state | useUrlState + codecs, useMirroredUrlState, useUrlSearchParams, commitUrlParams | The URL as the source of truth for view state: refresh, copied links, and Back/Forward reproduce the same view. Codecs (string/enum/boolean/positive-integer/JSON) omit defaults so pristine URLs stay clean; discrete decisions push, high-frequency text replaces; every write notifies every other URL-backed control on the page. |
| @ai-matrx/kit/idb-store | DBStoreManager, PublicStoreManager, FeatureStore, getIdbStoreSingleton | Typed, versioned IndexedDB stores over idb: subclass, declare your object stores/indexes in setupStores, and every operation resolves { data, error } — never throws, before or after init. Hold each store's one instance with getIdbStoreSingleton(key, create) (a globalThis slot, so the ESM and CJS graphs share it). |
| @ai-matrx/kit/color-util | findNearestTailwindColor, getColorFromTailwind, formatTailwindColor, format* normalizers, createColorNormalizer, tailwindColors | The bidirectional Tailwind-token color mapping with zero dependencies: token → hex lookups (including fuzzy input like "skyblue598"), and hex/rgb() → nearest token by perceptual CIEDE2000 distance (bit-identical to the colord lab plugin; a colord instance also plugs straight into the structural { delta(hex) } seam). Plus the pure paste-anything input normalizers and the injected-validity normalizeColorInput waterfall. |
| @ai-matrx/kit/react-tree | treeContainsComponent | Safe React children-tree scan for optional a11y children (is a DialogDescription anywhere under these children?): walks elements, arrays, fragments, iterables, and portals by hand, and SKIPS a non-renderable child with a loud dev scream instead of crashing at the probe like React.Children.toArray would. |
| @ai-matrx/kit/qr | decodeQrFromImageFile, decodeQrFromElement, decodeQrFromImageData, hasNativeQrDetector | THE QR decoder, in memory and never uploaded: a pasted screenshot, a dropped file, or a live <video>/<canvas> frame in; the encoded text (or null) out. Native BarcodeDetector first; the pure-JS jsqr fallback is lazily imported only in browsers that lack it. |
| @ai-matrx/kit/format | formatDurationMs / Seconds / Minutes, formatRelativeTime, formatAbsoluteDate, parseTimestamp, durationMsBetween, formatFileSize, getInitials, avatarPaletteIndex | THE one home for the tiny display formatters nothing else owns, with the display decision made ONCE per voice. Durations speak clock (9:04), compact (5.2s, 1m 30s), coarse (45 min, 3d 4h) or long (45 minutes, 3 days — the voice that goes inside a sentence), and round: "down" makes any of them safe for a countdown. Relative stamps speak short, long or locale-aware intl, all three in BOTH directions — "2d ago" and "in 2d" — with a sub-minute future magnitude reading "just now" because that much is clock skew, and suffix: false for a dense column whose header already says "Age" or "Due" (it drops " ago" and "in " alike). The unit is always in the name — there is deliberately no formatDuration. It also owns the HONEST NUMBER contract: formatUsd, formatCount, formatPercentFromFraction, safeRatio and sumKnown refuse to invent a value — unknown reads as UNKNOWN_DISPLAY ("—"), a measured 0 still reads as 0, and sumKnown hands back how many values it could NOT measure beside the total, so ?? 0 is never the easy path into an aggregate. |
credential-generator bundles the EFF Large Wordlist (7,776 entries; SHA-256 addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e). EFF's copyright policy makes its original material available under CC-BY 4.0.
More subpaths are on the roadmap: copy-for-ai.
Host wiring: /url-state
No wiring is required. The module writes through the History API
(history.pushState/replaceState) and dispatches its own sync event — this
is exactly how it runs inside the Next.js App Router host it was extracted
from; do NOT pass the Next.js router. Inject a router only when your host's
router must observe or perform the URL writes (react-router, a memory router
in tests, a native shell):
import { setUrlStateRouter } from "@ai-matrx/kit/url-state";
setUrlStateRouter({
push: (url) => myRouter.navigate(url), // full relative URL: pathname?query#hash
replace: (url) => myRouter.navigate(url, { replace: true }),
// Optional: where the current URL is read from (default: window.location)
getLocation: () => ({ pathname, search, hash }),
});Call it once at app boot (setUrlStateRouter(null) removes it). With no
router injected, behavior is byte-identical to the Matrx frontend original.
Design
- One package, many subpaths — you always include the kit; you only bundle what you import.
- Injected hosts (the clipboard notifier), opinionated behavior (the autosave status contract, the apply-time guard). The opinions are the product.
- ESM + CommonJS conditions with matching declarations, proven from the packed tarball.
The
"use client"boundary is stamped per chunk: React-touching subpaths carry it, pure ones (search-scoring, concurrency, text-case, invalidation, delimiter-guard, json-format, idb-store, color-util, react-tree, qr, credential-generator) ship unstamped and RSC-safe.
MIT © AI Matrx
