@ai-matrx/content-ir-react 0.15.1 → 0.16.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 +30 -0
- package/dist/{directive-host-DJfF35FD.d.cts → directive-host-C4TcPpxX.d.cts} +19 -2
- package/dist/{directive-host-DJfF35FD.d.ts → directive-host-C4TcPpxX.d.ts} +19 -2
- package/dist/directives.cjs +150 -30
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.d.cts +76 -41
- package/dist/directives.d.ts +76 -41
- package/dist/directives.js +150 -30
- package/dist/directives.js.map +1 -1
- package/dist/{host-types-9NfBeqrG.d.ts → host-types-CEc86R-v.d.ts} +1 -1
- package/dist/{host-types-DVIaaGR_.d.cts → host-types-a0XEr5b3.d.cts} +1 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.d.cts +3 -3
- package/dist/host.d.ts +3 -3
- package/dist/host.js.map +1 -1
- package/dist/index.cjs +156 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -42
- package/dist/index.d.ts +76 -42
- package/dist/index.js +153 -33
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +149 -35
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +146 -32
- package/dist/react.js.map +1 -1
- package/dist/{route-DDbgc-ID.d.ts → route-De9c5YgW.d.ts} +19 -2
- package/dist/{route-BCtPhF7W.d.cts → route-YcoyR3Fj.d.cts} +19 -2
- package/dist/route.d.cts +1 -1
- package/dist/route.d.ts +1 -1
- package/package.json +3 -3
package/dist/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../host/error-report.ts","../host/host-types.ts","../host/ContentIrProvider.tsx","../directives/directive-host.tsx"],"sourcesContent":["/**\n * THE SCREAM SEAM.\n *\n * Every recovery path in this package is loud: a defective component row, a\n * failed resolver load, a kind whose component throws on a provisional value.\n * The package refuses to own where those screams land — matrx-frontend routes\n * them to the Error Inspector, the dashboard to its own log, the extension to\n * its background page. Each host binds one function.\n *\n * A host that binds a no-op has chosen silence, which is a defect in itself;\n * {@link consoleErrorReporter} is the honest default.\n */\n\nexport interface ContentIrErrorReport {\n /** Always `\"content-ir\"` — hosts key their capture stores on it. */\n source: \"content-ir\";\n message: string;\n name?: string;\n stack?: string;\n /** Free-form grouping key (the host's `relation`), e.g. `\"partial-kind\"`. */\n relation?: string;\n raw?: unknown;\n}\n\nexport type ContentIrErrorReporter = (report: ContentIrErrorReport) => void;\n\n/** The default when a host binds nothing: never silent. */\nexport const consoleErrorReporter: ContentIrErrorReporter = (report) => {\n console.error(`[content-ir] ${report.message}`, report.raw ?? \"\");\n};\n","/**\n * THE HOST CONTRACT — everything this package refuses to decide.\n *\n * The kernel (`@ai-matrx/content-ir`) proved the shape: extract the portable\n * part, let host capability enter through explicit named seams. The render\n * layer needs four things it must never own:\n *\n * 1. WHERE KIND DEFINITIONS COME FROM (a bundled table, `content_ir`, a REST\n * call). The route only asks \"is this kind registered, and does it carry a\n * legacy bridge / a partial-ready opt-in\".\n * 2. WHERE COMPONENT ROWS COME FROM — {@link ComponentResolutionSource}. The\n * package ships `ComponentResolver`, which any host can construct with its\n * own loaders; a host with its own resolver only has to satisfy the type.\n * 3. HOW A ROUTED BLOCK ACTUALLY DRAWS. `applyIrKindRoute` decides a block's\n * TYPE; turning that type into pixels is the host's dispatch table\n * (matrx-frontend's `BlockRenderer`, the dashboard's small map). The\n * package never imports a component library.\n * 4. WHERE SCREAMS LAND — {@link ContentIrErrorReporter}.\n *\n * No Next.js, no Redux, no Supabase, no router, no host error capture. If a\n * seam is missing, ADD A SEAM — never an import.\n */\n\nimport type { ReactNode } from \"react\";\nimport type { KindDefinition } from \"@ai-matrx/content-ir\";\nimport type { ContentIrErrorReporter } from \"./error-report\";\nimport type { ComponentResolutionSource } from \"../resolver/component-resolution.types\";\nimport type { DirectiveHost } from \"../directives/directive-host\";\n\n/**\n * The minimum a render block must expose for the kind route to work. Hosts\n * pass their own richer block type through; the route is generic over `T` and\n * preserves every extra field.\n */\nexport interface IrRoutableBlock {\n type: string;\n serverData?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\n/** A routable block that also carries its source text (the raw region). */\nexport interface IrRenderBlock extends IrRoutableBlock {\n content: string;\n language?: string;\n}\n\n/**\n * Where registered kinds come from. `getDefinition` is the only method the\n * pure route needs; the rest drive the granular repaint and the warm-up tick\n * that keeps a cold registry from being mistaken for \"this kind has no\n * component\".\n */\nexport interface KindDefinitionSource {\n getDefinition(kind: string): KindDefinition | undefined;\n /** Monotonic per-kind version — the repaint snapshot key. */\n getKindVersion(kind: string): number;\n subscribeKind(kind: string, listener: () => void): () => void;\n /** One definition load per app session. */\n ensureWarm(): Promise<void>;\n}\n\n/**\n * The rendering seams. Everything here returns host UI; the package supplies\n * the DECISIONS and the chrome-free structure around them.\n */\nexport interface ContentIrRenderSeams {\n /**\n * Draw a block the kind route has already typed. This is the host's dispatch\n * table — the single place a component key becomes a component.\n */\n renderBlock(block: IrRenderBlock): ReactNode;\n /**\n * THE FLOOR: render ANY JSON value as a human document. Reached when a kind\n * has no render-trusted component, when the value is not an object at all,\n * and by the generic structured view.\n *\n * This is deliberately a seam and not a bundled component. Rendering a value\n * WELL means prose through the host's markdown renderer, media through the\n * host's file handler, and uniform arrays through the host's data table —\n * all of which are host property. A host that has none of those can pass a\n * `<pre>`; it will be honest, just plain.\n */\n renderValue(props: StructuredValueRenderProps): ReactNode;\n /**\n * The \"still arriving\" indicator used by the provisional frame. Optional —\n * hosts without a shimmer get plain text.\n */\n renderShimmer?(text: string): ReactNode;\n /**\n * The honest \"this shape has no custom component yet\" notice. Optional: the\n * package draws a plain amber line when a host supplies nothing, and a host\n * with an icon set or its own callout component supplies that instead. It is\n * a seam and not a bundled component because this package depends on no icon\n * library.\n */\n renderNotice?(text: string): ReactNode;\n}\n\nexport interface StructuredValueRenderProps {\n value: unknown;\n /** The kind slug this value claims, when known. Honesty line only — never a renderer choice. */\n kind?: string;\n /** Why this shape has no custom view, in human words. */\n note?: string;\n /** Show the \"what this is / raw data\" footer. Default true. */\n footer?: boolean;\n}\n\n/**\n * One host, wired once, read by every component in this package through\n * {@link ContentIrRenderProvider}.\n */\nexport interface ContentIrHost extends ContentIrRenderSeams {\n kinds: KindDefinitionSource;\n components: ComponentResolutionSource;\n reportError: ContentIrErrorReporter;\n /**\n * The `kind_component.platform` this host resolves as. \"web\" for every\n * browser UI; the column already models \"react-native\" and friends, and a\n * host that lies here renders the wrong component everywhere.\n */\n platform: string;\n /**\n * The Kind Directives seams (apply / open item / copy / noun catalog / item\n * kind). Optional: a host without them still renders every directive card,\n * apply-less and copy-less — honest chrome, never hidden content. Provided\n * to the directive tier by `ContentIrRenderProvider`; a host that has not\n * adopted the render provider mounts `DirectiveHostProvider` directly.\n */\n directives?: DirectiveHost;\n}\n\n/**\n * The pure route's dependencies — a strict subset of {@link ContentIrHost},\n * because `applyIrKindRoute` is called OUTSIDE React (reducers, stream\n * accumulators, tests) where there is no provider to read.\n */\nexport interface KindRouteEnv {\n kinds: Pick<KindDefinitionSource, \"getDefinition\">;\n /**\n * `hasSettled` is optional and read ONLY to suppress a negative verdict from\n * a registry that has not loaded yet — see THE COLD-VERDICT RULE on\n * `ComponentResolutionSource`.\n */\n components: Pick<ComponentResolutionSource, \"resolve\"> &\n Partial<Pick<ComponentResolutionSource, \"hasSettled\">>;\n reportError: ContentIrErrorReporter;\n platform: string;\n}\n\n/** Narrow a host (or anything host-shaped) to what the pure route needs. */\nexport function routeEnvOf(host: ContentIrHost): KindRouteEnv {\n return {\n kinds: host.kinds,\n components: host.components,\n reportError: host.reportError,\n platform: host.platform,\n };\n}\n","\"use client\";\n\n/**\n * The one place a host is wired in. Everything React in this package reads its\n * capability from here — no module singleton, no import into an app.\n *\n * A host mounts this ONCE, high enough that every kind-rendering surface is\n * inside it:\n *\n * ```tsx\n * <ContentIrRenderProvider host={matrxContentIrHost}>\n * <App />\n * </ContentIrRenderProvider>\n * ```\n *\n * Reading it outside a provider THROWS rather than falling back to a default.\n * A silent default would render the wrong component for every kind in the app\n * and look like a data problem for weeks.\n */\n\nimport { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { ContentIrHost, KindRouteEnv } from \"./host-types\";\nimport { routeEnvOf } from \"./host-types\";\nimport { DirectiveHostProvider } from \"../directives/directive-host\";\n\nconst ContentIrHostContext = createContext<ContentIrHost | null>(null);\n\nexport interface ContentIrRenderProviderProps {\n host: ContentIrHost;\n children: ReactNode;\n}\n\nexport function ContentIrRenderProvider({\n host,\n children,\n}: ContentIrRenderProviderProps) {\n return (\n <ContentIrHostContext.Provider value={host}>\n <DirectiveHostProvider host={host.directives ?? {}}>{children}</DirectiveHostProvider>\n </ContentIrHostContext.Provider>\n );\n}\n\n/**\n * The wired host, or null. For the rare consumer that can legitimately run\n * without a provider because it was handed its sources explicitly (see\n * `useContentIrKindVersion`). Everything else uses {@link useContentIrHost}.\n */\nexport function useContentIrHostOrNull(): ContentIrHost | null {\n return useContext(ContentIrHostContext);\n}\n\n/** The wired host. Throws outside a provider — see the module doc. */\nexport function useContentIrHost(): ContentIrHost {\n const host = useContentIrHostOrNull();\n if (!host) {\n throw new Error(\n \"[content-ir-react] No ContentIrRenderProvider above this component. \" +\n \"Mount one at the app root with your host adapter (kind definitions, \" +\n \"component resolver, renderBlock, renderValue, reportError).\",\n );\n }\n return host;\n}\n\n/** The wired host narrowed to what the pure route functions take. */\nexport function useKindRouteEnv(): KindRouteEnv {\n const host = useContentIrHost();\n return useMemo(() => routeEnvOf(host), [host]);\n}\n","\"use client\";\n\n/**\n * THE DIRECTIVE HOST CONTRACT — what the directive tier refuses to decide.\n *\n * The package owns the DECISIONS (decode → renderer → card → floor, the\n * position law, never-null rendering) and the card itself. Four things stay\n * host property and enter through named seams:\n *\n * 1. APPLYING a side-effect directive — `POST /directives/confirm` as the\n * user, with the host's auth and base URL. Absent → the Apply control is\n * ABSENT (honest), never a dead button.\n * 2. OPENING an item to read it before approving — a window panel, a dialog,\n * a route. Absent → items are not clickable.\n * 3. COPY controls — host chrome (matrx-frontend's CopyButtons). Absent → none.\n * 4. The NOUN catalog + item-kind lookup — server-derived tables the host\n * mirrors (or reads from its kind catalog's `items` edges).\n * 5. ASKING (`ask`), NAMING a record (`renderRecord`), and READING the ledger\n * (`applyState`) — the consequence before the click, the doors, and the\n * state that survives a reload.\n *\n * A host that has adopted `ContentIrRenderProvider` passes `directives` on its\n * ContentIrHost; a host that has NOT (Workflow Studio, 2026-09-08) mounts\n * `DirectiveHostProvider` alone — the directive tier never requires the full\n * render host, because a chat bubble must be able to draw a directive card\n * identically everywhere, today.\n */\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type {\n DecodedDirective,\n DirectiveItemKindLookup,\n DirectiveNounCatalog,\n} from \"@ai-matrx/content-ir\";\n\nexport interface DirectiveApplyResult {\n applied: number;\n failed: number;\n /**\n * The server's own receipt sentence (\"Deleted task “Ship it”.\"), shown in\n * place of a bare count. Never composed client-side.\n */\n message?: string | null;\n /**\n * The records the apply created or changed, so the tally is a way IN to them\n * (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.\n */\n records?: DirectiveRecordRef[];\n}\n\n/**\n * What the server's action ledger says about ONE block, read on mount so a\n * reloaded page never offers \"Apply\" for work that already ran (reviewer\n * defect 5, 2026-10-02).\n *\n * - `not_applied` — still to do (or partly done: the batch re-applies, and the\n * server replays the members it already holds). The card offers Apply.\n * - `in_flight` — another request is applying it right now. The card says it\n * is finishing and asks again shortly; it offers neither Apply nor a tally.\n * - `applied` — the ledger holds every item. The card shows the tally exactly\n * as a fresh apply would: the ledger's own sentence and the records, as doors.\n */\nexport interface DirectiveApplyState {\n state: \"not_applied\" | \"in_flight\" | \"applied\";\n /** The ledger row's sentence (\"Created task.\"), never composed client-side. */\n message?: string | null;\n /** The records the original apply wrote — the tally's doors. */\n records?: DirectiveRecordRef[];\n}\n\n/** One record a directive names: the noun it is, and its id. */\nexport interface DirectiveRecordRef {\n noun: string;\n id: string;\n}\n\nexport interface DirectiveRecordProps extends DirectiveRecordRef {\n /**\n * What to show until (or unless) the host resolves the live name —\n * \"Task 1a2b3c4d\". Never blank, never \"Item\".\n */\n fallback: string;\n /** Where it is drawn: an item row, the confirm dialog, or the applied tally. */\n context: \"row\" | \"dialog\" | \"applied\";\n /**\n * True when the record is (about to be) in the trash — the host may still\n * name it but should not promise it opens as a live record.\n */\n trashed?: boolean;\n}\n\n/** What the person is being asked to approve — everything the card knows. */\nexport interface DirectiveAskRequest {\n directive: DecodedDirective;\n /** The items, exactly as they will be sent. */\n items: Record<string, unknown>[];\n /** The noun's human label (\"Task\"), from the host's catalog when it has one. */\n nounLabel: string;\n /**\n * True for \"Run again\": the ledger already holds this block, and a yes runs\n * it a SECOND time (`force`) — another copy for a create, the same fields\n * written again for an update, another delete for a restored record. The\n * question must say so.\n */\n again?: boolean;\n}\n\n/**\n * The shell a card hands its host — the two keys, plus WHERE it sits.\n *\n * `conversationId` is the conversation the card renders in (a chat message),\n * absent in a person's own content (a note). It is the idempotency NAMESPACE:\n * a card in an agent's message must apply under the SAME key as that\n * proposal's Approve button, or the one action could run twice.\n */\nexport interface DirectiveShell {\n __kind: string;\n items: Record<string, unknown>[];\n conversationId?: string;\n /** \"Run again\": skip the ledger's dedup and apply a second time. */\n force?: boolean;\n}\n\nexport interface DirectiveOpenItemOptions {\n /** The item, `__kind`-stamped when the shape has an item kind. */\n item: Record<string, unknown>;\n /** The content-IR kind this item IS, or null when the shape has none. */\n itemKind: string | null;\n /** What the item is called — the same title the card shows. */\n title: string;\n /** \"Run Create agent definition · item 1 of 1\". */\n subtitle: string | null;\n directive: DecodedDirective;\n index: number;\n total: number;\n}\n\nexport interface DirectiveCopyProps {\n label: string;\n /** The value to copy — the whole shell or one item. */\n value: unknown;\n /** The item's kind, when copying an item. */\n kind?: string | null;\n size: \"icon\" | \"xs\";\n}\n\nexport interface DirectiveHost {\n /**\n * Apply a side-effect directive as the user (the server re-validates every\n * item against the registered model and runs the ONE handler; idempotent).\n * Reject with an Error whose message is safe to show.\n */\n confirm?: (shell: DirectiveShell) => Promise<DirectiveApplyResult>;\n /**\n * Ask the person BEFORE anything runs, naming the consequence (the records,\n * what changes). Resolve `true` to go ahead, `false` to leave the card as it\n * was. The Apply control stays idle while this is open — nothing reads as\n * \"applying\" until the person has said yes. Absent → `confirm` is called\n * directly (a host that asks inside `confirm` keeps working, but its card\n * will read \"Applying…\" while its dialog is up).\n */\n ask?: (request: DirectiveAskRequest) => Promise<boolean>;\n /**\n * Has this exact block already been applied? Read ONCE when the card mounts\n * (and again while the answer is `in_flight`), so the card survives a reload:\n * the server ledger, not component state, is what knows. The identity is the\n * server's (it hashes the validated items in the same namespace `confirm`\n * applies in), so a host asks — it never computes. Batch on the host side if\n * a page carries many cards. Reject with an Error when the ledger cannot be\n * read: the card reports it and offers Apply, which is safe because `confirm`\n * is idempotent. Absent → the card starts at Apply, as before.\n */\n applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;\n /**\n * Draw ONE existing record by its live name, as a way to open it. Used on\n * update/delete rows (the record that will change), in the host's own ask\n * dialog if it likes, and on the applied tally (what was written). Absent →\n * the fallback text, unlinked.\n */\n renderRecord?: (props: DirectiveRecordProps) => ReactNode;\n /** Open ONE item to read it properly before approving the write. */\n openItem?: (options: DirectiveOpenItemOptions) => void;\n /** Host copy controls. */\n renderCopy?: (props: DirectiveCopyProps) => ReactNode;\n /** The mirrored noun catalog (label / family / title column). */\n nouns?: DirectiveNounCatalog;\n /** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */\n itemKind?: DirectiveItemKindLookup;\n /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */\n reportError?: (message: string) => void;\n}\n\nconst DirectiveHostContext = createContext<DirectiveHost | null>(null);\n\n/** Where a directive renders — today, the conversation it sits in (if any). */\nexport interface DirectiveScope {\n conversationId?: string | null;\n}\n\nconst DirectiveScopeContext = createContext<DirectiveScope>({});\n\n/**\n * Tell every card below WHERE it renders. A host draws a chat message's\n * directives inside one of these (or passes `conversationId` to\n * `DirectiveRender`), so the card applies in that conversation's namespace —\n * the same key the agent's proposal approves under. A note mounts none.\n */\nexport function DirectiveScopeProvider({\n conversationId,\n children,\n}: DirectiveScope & { children: ReactNode }) {\n return (\n <DirectiveScopeContext.Provider value={{ conversationId: conversationId ?? null }}>\n {children}\n </DirectiveScopeContext.Provider>\n );\n}\n\nexport function useDirectiveScope(): DirectiveScope {\n return useContext(DirectiveScopeContext);\n}\n\nexport interface DirectiveHostProviderProps {\n host: DirectiveHost;\n children: ReactNode;\n}\n\nexport function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps) {\n return <DirectiveHostContext.Provider value={host}>{children}</DirectiveHostContext.Provider>;\n}\n\nconst EMPTY_HOST: DirectiveHost = {};\n\n/**\n * The wired directive host. Unlike the render host this does NOT throw when\n * unwired: a directive still renders (named, itemised, copy-less, apply-less)\n * — degrading the chrome is honest, hiding the content is not.\n */\nexport function useDirectiveHost(): DirectiveHost {\n return useContext(DirectiveHostContext) ?? EMPTY_HOST;\n}\n"],"mappings":";;;AA2BO,IAAM,uBAA+C,CAAC,WAAW;AACtE,UAAQ,MAAM,gBAAgB,OAAO,OAAO,IAAI,OAAO,OAAO,EAAE;AAClE;;;AC0HO,SAAS,WAAW,MAAmC;AAC5D,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,YAAY,KAAK;AAAA,IACjB,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,EACjB;AACF;;;AC1IA,SAAS,iBAAAA,gBAAe,cAAAC,aAAY,eAA+B;;;ACQnE,SAAS,eAAe,kBAAkC;AAwLtD;AApBJ,IAAM,uBAAuB,cAAoC,IAAI;AAOrE,IAAM,wBAAwB,cAA8B,CAAC,CAAC;AA4BvD,SAAS,sBAAsB,EAAE,MAAM,SAAS,GAA+B;AACpF,SAAO,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,MAAO,UAAS;AAC/D;;;AD/LM,gBAAAC,YAAA;AAbN,IAAM,uBAAuBC,eAAoC,IAAI;AAO9D,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAAiC;AAC/B,SACE,gBAAAD,KAAC,qBAAqB,UAArB,EAA8B,OAAO,MACpC,0BAAAA,KAAC,yBAAsB,MAAM,KAAK,cAAc,CAAC,GAAI,UAAS,GAChE;AAEJ;AAOO,SAAS,yBAA+C;AAC7D,SAAOE,YAAW,oBAAoB;AACxC;AAGO,SAAS,mBAAkC;AAChD,QAAM,OAAO,uBAAuB;AACpC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,kBAAgC;AAC9C,QAAM,OAAO,iBAAiB;AAC9B,SAAO,QAAQ,MAAM,WAAW,IAAI,GAAG,CAAC,IAAI,CAAC;AAC/C;","names":["createContext","useContext","jsx","createContext","useContext"]}
|
|
1
|
+
{"version":3,"sources":["../host/error-report.ts","../host/host-types.ts","../host/ContentIrProvider.tsx","../directives/directive-host.tsx"],"sourcesContent":["/**\n * THE SCREAM SEAM.\n *\n * Every recovery path in this package is loud: a defective component row, a\n * failed resolver load, a kind whose component throws on a provisional value.\n * The package refuses to own where those screams land — matrx-frontend routes\n * them to the Error Inspector, the dashboard to its own log, the extension to\n * its background page. Each host binds one function.\n *\n * A host that binds a no-op has chosen silence, which is a defect in itself;\n * {@link consoleErrorReporter} is the honest default.\n */\n\nexport interface ContentIrErrorReport {\n /** Always `\"content-ir\"` — hosts key their capture stores on it. */\n source: \"content-ir\";\n message: string;\n name?: string;\n stack?: string;\n /** Free-form grouping key (the host's `relation`), e.g. `\"partial-kind\"`. */\n relation?: string;\n raw?: unknown;\n}\n\nexport type ContentIrErrorReporter = (report: ContentIrErrorReport) => void;\n\n/** The default when a host binds nothing: never silent. */\nexport const consoleErrorReporter: ContentIrErrorReporter = (report) => {\n console.error(`[content-ir] ${report.message}`, report.raw ?? \"\");\n};\n","/**\n * THE HOST CONTRACT — everything this package refuses to decide.\n *\n * The kernel (`@ai-matrx/content-ir`) proved the shape: extract the portable\n * part, let host capability enter through explicit named seams. The render\n * layer needs four things it must never own:\n *\n * 1. WHERE KIND DEFINITIONS COME FROM (a bundled table, `content_ir`, a REST\n * call). The route only asks \"is this kind registered, and does it carry a\n * legacy bridge / a partial-ready opt-in\".\n * 2. WHERE COMPONENT ROWS COME FROM — {@link ComponentResolutionSource}. The\n * package ships `ComponentResolver`, which any host can construct with its\n * own loaders; a host with its own resolver only has to satisfy the type.\n * 3. HOW A ROUTED BLOCK ACTUALLY DRAWS. `applyIrKindRoute` decides a block's\n * TYPE; turning that type into pixels is the host's dispatch table\n * (matrx-frontend's `BlockRenderer`, the dashboard's small map). The\n * package never imports a component library.\n * 4. WHERE SCREAMS LAND — {@link ContentIrErrorReporter}.\n *\n * No Next.js, no Redux, no Supabase, no router, no host error capture. If a\n * seam is missing, ADD A SEAM — never an import.\n */\n\nimport type { ReactNode } from \"react\";\nimport type { KindDefinition } from \"@ai-matrx/content-ir\";\nimport type { ContentIrErrorReporter } from \"./error-report\";\nimport type { ComponentResolutionSource } from \"../resolver/component-resolution.types\";\nimport type { DirectiveHost } from \"../directives/directive-host\";\n\n/**\n * The minimum a render block must expose for the kind route to work. Hosts\n * pass their own richer block type through; the route is generic over `T` and\n * preserves every extra field.\n */\nexport interface IrRoutableBlock {\n type: string;\n serverData?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\n/** A routable block that also carries its source text (the raw region). */\nexport interface IrRenderBlock extends IrRoutableBlock {\n content: string;\n language?: string;\n}\n\n/**\n * Where registered kinds come from. `getDefinition` is the only method the\n * pure route needs; the rest drive the granular repaint and the warm-up tick\n * that keeps a cold registry from being mistaken for \"this kind has no\n * component\".\n */\nexport interface KindDefinitionSource {\n getDefinition(kind: string): KindDefinition | undefined;\n /** Monotonic per-kind version — the repaint snapshot key. */\n getKindVersion(kind: string): number;\n subscribeKind(kind: string, listener: () => void): () => void;\n /** One definition load per app session. */\n ensureWarm(): Promise<void>;\n}\n\n/**\n * The rendering seams. Everything here returns host UI; the package supplies\n * the DECISIONS and the chrome-free structure around them.\n */\nexport interface ContentIrRenderSeams {\n /**\n * Draw a block the kind route has already typed. This is the host's dispatch\n * table — the single place a component key becomes a component.\n */\n renderBlock(block: IrRenderBlock): ReactNode;\n /**\n * THE FLOOR: render ANY JSON value as a human document. Reached when a kind\n * has no render-trusted component, when the value is not an object at all,\n * and by the generic structured view.\n *\n * This is deliberately a seam and not a bundled component. Rendering a value\n * WELL means prose through the host's markdown renderer, media through the\n * host's file handler, and uniform arrays through the host's data table —\n * all of which are host property. A host that has none of those can pass a\n * `<pre>`; it will be honest, just plain.\n */\n renderValue(props: StructuredValueRenderProps): ReactNode;\n /**\n * The \"still arriving\" indicator used by the provisional frame. Optional —\n * hosts without a shimmer get plain text.\n */\n renderShimmer?(text: string): ReactNode;\n /**\n * The honest \"this shape has no custom component yet\" notice. Optional: the\n * package draws a plain amber line when a host supplies nothing, and a host\n * with an icon set or its own callout component supplies that instead. It is\n * a seam and not a bundled component because this package depends on no icon\n * library.\n */\n renderNotice?(text: string): ReactNode;\n}\n\nexport interface StructuredValueRenderProps {\n value: unknown;\n /** The kind slug this value claims, when known. Honesty line only — never a renderer choice. */\n kind?: string;\n /** Why this shape has no custom view, in human words. */\n note?: string;\n /** Show the \"what this is / raw data\" footer. Default true. */\n footer?: boolean;\n}\n\n/**\n * One host, wired once, read by every component in this package through\n * {@link ContentIrRenderProvider}.\n */\nexport interface ContentIrHost extends ContentIrRenderSeams {\n kinds: KindDefinitionSource;\n components: ComponentResolutionSource;\n reportError: ContentIrErrorReporter;\n /**\n * The `kind_component.platform` this host resolves as. \"web\" for every\n * browser UI; the column already models \"react-native\" and friends, and a\n * host that lies here renders the wrong component everywhere.\n */\n platform: string;\n /**\n * The Kind Directives seams (apply / open item / copy / noun catalog / item\n * kind). Optional: a host without them still renders every directive card,\n * apply-less and copy-less — honest chrome, never hidden content. Provided\n * to the directive tier by `ContentIrRenderProvider`; a host that has not\n * adopted the render provider mounts `DirectiveHostProvider` directly.\n */\n directives?: DirectiveHost;\n}\n\n/**\n * The pure route's dependencies — a strict subset of {@link ContentIrHost},\n * because `applyIrKindRoute` is called OUTSIDE React (reducers, stream\n * accumulators, tests) where there is no provider to read.\n */\nexport interface KindRouteEnv {\n kinds: Pick<KindDefinitionSource, \"getDefinition\">;\n /**\n * `hasSettled` is optional and read ONLY to suppress a negative verdict from\n * a registry that has not loaded yet — see THE COLD-VERDICT RULE on\n * `ComponentResolutionSource`.\n */\n components: Pick<ComponentResolutionSource, \"resolve\"> &\n Partial<Pick<ComponentResolutionSource, \"hasSettled\">>;\n reportError: ContentIrErrorReporter;\n platform: string;\n}\n\n/** Narrow a host (or anything host-shaped) to what the pure route needs. */\nexport function routeEnvOf(host: ContentIrHost): KindRouteEnv {\n return {\n kinds: host.kinds,\n components: host.components,\n reportError: host.reportError,\n platform: host.platform,\n };\n}\n","\"use client\";\n\n/**\n * The one place a host is wired in. Everything React in this package reads its\n * capability from here — no module singleton, no import into an app.\n *\n * A host mounts this ONCE, high enough that every kind-rendering surface is\n * inside it:\n *\n * ```tsx\n * <ContentIrRenderProvider host={matrxContentIrHost}>\n * <App />\n * </ContentIrRenderProvider>\n * ```\n *\n * Reading it outside a provider THROWS rather than falling back to a default.\n * A silent default would render the wrong component for every kind in the app\n * and look like a data problem for weeks.\n */\n\nimport { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { ContentIrHost, KindRouteEnv } from \"./host-types\";\nimport { routeEnvOf } from \"./host-types\";\nimport { DirectiveHostProvider } from \"../directives/directive-host\";\n\nconst ContentIrHostContext = createContext<ContentIrHost | null>(null);\n\nexport interface ContentIrRenderProviderProps {\n host: ContentIrHost;\n children: ReactNode;\n}\n\nexport function ContentIrRenderProvider({\n host,\n children,\n}: ContentIrRenderProviderProps) {\n return (\n <ContentIrHostContext.Provider value={host}>\n <DirectiveHostProvider host={host.directives ?? {}}>{children}</DirectiveHostProvider>\n </ContentIrHostContext.Provider>\n );\n}\n\n/**\n * The wired host, or null. For the rare consumer that can legitimately run\n * without a provider because it was handed its sources explicitly (see\n * `useContentIrKindVersion`). Everything else uses {@link useContentIrHost}.\n */\nexport function useContentIrHostOrNull(): ContentIrHost | null {\n return useContext(ContentIrHostContext);\n}\n\n/** The wired host. Throws outside a provider — see the module doc. */\nexport function useContentIrHost(): ContentIrHost {\n const host = useContentIrHostOrNull();\n if (!host) {\n throw new Error(\n \"[content-ir-react] No ContentIrRenderProvider above this component. \" +\n \"Mount one at the app root with your host adapter (kind definitions, \" +\n \"component resolver, renderBlock, renderValue, reportError).\",\n );\n }\n return host;\n}\n\n/** The wired host narrowed to what the pure route functions take. */\nexport function useKindRouteEnv(): KindRouteEnv {\n const host = useContentIrHost();\n return useMemo(() => routeEnvOf(host), [host]);\n}\n","\"use client\";\n\n/**\n * THE DIRECTIVE HOST CONTRACT — what the directive tier refuses to decide.\n *\n * The package owns the DECISIONS (decode → renderer → card → floor, the\n * position law, never-null rendering) and the card itself. Four things stay\n * host property and enter through named seams:\n *\n * 1. APPLYING a side-effect directive — `POST /directives/confirm` as the\n * user, with the host's auth and base URL. Absent → the Apply control is\n * ABSENT (honest), never a dead button.\n * 2. OPENING an item to read it before approving — a window panel, a dialog,\n * a route. Absent → items are not clickable.\n * 3. COPY controls — host chrome (matrx-frontend's CopyButtons). Absent → none.\n * 4. The NOUN catalog + item-kind lookup — server-derived tables the host\n * mirrors (or reads from its kind catalog's `items` edges).\n * 5. ASKING (`ask`), NAMING a record (`renderRecord`), and READING the ledger\n * (`applyState`) — the consequence before the click, the doors, and the\n * state that survives a reload.\n *\n * A host that has adopted `ContentIrRenderProvider` passes `directives` on its\n * ContentIrHost; a host that has NOT (Workflow Studio, 2026-09-08) mounts\n * `DirectiveHostProvider` alone — the directive tier never requires the full\n * render host, because a chat bubble must be able to draw a directive card\n * identically everywhere, today.\n */\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type {\n DecodedDirective,\n DirectiveItemKindLookup,\n DirectiveNounCatalog,\n} from \"@ai-matrx/content-ir\";\n\nexport interface DirectiveApplyResult {\n applied: number;\n failed: number;\n /**\n * The server's own receipt sentence (\"Deleted task “Ship it”.\"), shown in\n * place of a bare count. Never composed client-side.\n */\n message?: string | null;\n /**\n * The records the apply created or changed, so the tally is a way IN to them\n * (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.\n */\n records?: DirectiveRecordRef[];\n}\n\n/**\n * What the server's action ledger says about ONE block, read on mount so a\n * reloaded page never offers \"Apply\" for work that already ran (reviewer\n * defect 5, 2026-10-02).\n *\n * - `not_applied` — still to do (or partly done: the batch re-applies, and the\n * server replays the members it already holds). The card offers Apply.\n * - `in_flight` — another request is applying it right now. The card says it\n * is finishing and asks again shortly; it offers neither Apply nor a tally.\n * - `applied` — the ledger holds every item. The card shows the tally exactly\n * as a fresh apply would: the ledger's own sentence and the records, as doors.\n */\nexport interface DirectiveApplyState {\n state: \"not_applied\" | \"in_flight\" | \"applied\";\n /** The ledger row's sentence (\"Created task.\"), never composed client-side. */\n message?: string | null;\n /**\n * The records the LATEST apply wrote — the tally's doors. After \"Run again\"\n * that is the newest copy, never the first.\n */\n records?: DirectiveRecordRef[];\n /**\n * How many times the ledger holds this block as applied: 1 for the original,\n * one more for every \"Run again\". Absent → 1. A create that ran twice made\n * two records, and the card says so (reviewer, 2026-10-02).\n */\n copies?: number;\n}\n\n/** One record a directive names: the noun it is, and its id. */\nexport interface DirectiveRecordRef {\n noun: string;\n id: string;\n}\n\nexport interface DirectiveRecordProps extends DirectiveRecordRef {\n /**\n * What to show until (or unless) the host resolves the live name —\n * \"Task 1a2b3c4d\". Never blank, never \"Item\".\n */\n fallback: string;\n /** Where it is drawn: an item row, the confirm dialog, or the applied tally. */\n context: \"row\" | \"dialog\" | \"applied\";\n /**\n * True when the record is (about to be) in the trash — the host may still\n * name it but should not promise it opens as a live record.\n */\n trashed?: boolean;\n}\n\n/** What the person is being asked to approve — everything the card knows. */\nexport interface DirectiveAskRequest {\n directive: DecodedDirective;\n /** The items, exactly as they will be sent. */\n items: Record<string, unknown>[];\n /** The noun's human label (\"Task\"), from the host's catalog when it has one. */\n nounLabel: string;\n /**\n * True for \"Run again\": the ledger already holds this block, and a yes runs\n * it a SECOND time (`force`) — another copy for a create, the same fields\n * written again for an update, another delete for a restored record. The\n * question must say so.\n */\n again?: boolean;\n}\n\n/**\n * The shell a card hands its host — the two keys, plus WHERE it sits.\n *\n * `conversationId` is the conversation the card renders in (a chat message),\n * absent in a person's own content (a note). It is the idempotency NAMESPACE:\n * a card in an agent's message must apply under the SAME key as that\n * proposal's Approve button, or the one action could run twice.\n */\nexport interface DirectiveShell {\n __kind: string;\n items: Record<string, unknown>[];\n conversationId?: string;\n /** \"Run again\": skip the ledger's dedup and apply a second time. */\n force?: boolean;\n}\n\nexport interface DirectiveOpenItemOptions {\n /** The item, `__kind`-stamped when the shape has an item kind. */\n item: Record<string, unknown>;\n /** The content-IR kind this item IS, or null when the shape has none. */\n itemKind: string | null;\n /** What the item is called — the same title the card shows. */\n title: string;\n /** \"Run Create agent definition · item 1 of 1\". */\n subtitle: string | null;\n directive: DecodedDirective;\n index: number;\n total: number;\n}\n\nexport interface DirectiveCopyProps {\n label: string;\n /** The value to copy — the whole shell or one item. */\n value: unknown;\n /** The item's kind, when copying an item. */\n kind?: string | null;\n size: \"icon\" | \"xs\";\n}\n\nexport interface DirectiveHost {\n /**\n * Apply a side-effect directive as the user (the server re-validates every\n * item against the registered model and runs the ONE handler; idempotent).\n * Reject with an Error whose message is safe to show.\n */\n confirm?: (shell: DirectiveShell) => Promise<DirectiveApplyResult>;\n /**\n * Ask the person BEFORE anything runs, naming the consequence (the records,\n * what changes). Resolve `true` to go ahead, `false` to leave the card as it\n * was. The Apply control stays idle while this is open — nothing reads as\n * \"applying\" until the person has said yes. Absent → `confirm` is called\n * directly (a host that asks inside `confirm` keeps working, but its card\n * will read \"Applying…\" while its dialog is up).\n */\n ask?: (request: DirectiveAskRequest) => Promise<boolean>;\n /**\n * Has this exact block already been applied? Read ONCE when the card mounts\n * (and again while the answer is `in_flight`), so the card survives a reload:\n * the server ledger, not component state, is what knows. The identity is the\n * server's (it hashes the validated items in the same namespace `confirm`\n * applies in), so a host asks — it never computes. Batch on the host side if\n * a page carries many cards. Reject with an Error when the ledger cannot be\n * read: the card reports it and offers Apply, which is safe because `confirm`\n * is idempotent. Absent → the card starts at Apply, as before.\n */\n applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;\n /**\n * Read the CURRENT values of ONE existing record, so an update says what it\n * replaces — \"Status open → done\", never just \"→ done\" (reviewer,\n * 2026-10-02). Resolve `null` when the record cannot be read (no access, no\n * such record, a noun the host cannot read); reject only for a real failure.\n * Absent → an update row shows the new values alone, as before.\n */\n readRecord?: (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;\n /**\n * Draw ONE existing record by its live name, as a way to open it. Used on\n * update/delete rows (the record that will change), in the host's own ask\n * dialog if it likes, and on the applied tally (what was written). Absent →\n * the fallback text, unlinked.\n */\n renderRecord?: (props: DirectiveRecordProps) => ReactNode;\n /** Open ONE item to read it properly before approving the write. */\n openItem?: (options: DirectiveOpenItemOptions) => void;\n /** Host copy controls. */\n renderCopy?: (props: DirectiveCopyProps) => ReactNode;\n /** The mirrored noun catalog (label / family / title column). */\n nouns?: DirectiveNounCatalog;\n /** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */\n itemKind?: DirectiveItemKindLookup;\n /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */\n reportError?: (message: string) => void;\n}\n\nconst DirectiveHostContext = createContext<DirectiveHost | null>(null);\n\n/** Where a directive renders — today, the conversation it sits in (if any). */\nexport interface DirectiveScope {\n conversationId?: string | null;\n}\n\nconst DirectiveScopeContext = createContext<DirectiveScope>({});\n\n/**\n * Tell every card below WHERE it renders. A host draws a chat message's\n * directives inside one of these (or passes `conversationId` to\n * `DirectiveRender`), so the card applies in that conversation's namespace —\n * the same key the agent's proposal approves under. A note mounts none.\n */\nexport function DirectiveScopeProvider({\n conversationId,\n children,\n}: DirectiveScope & { children: ReactNode }) {\n return (\n <DirectiveScopeContext.Provider value={{ conversationId: conversationId ?? null }}>\n {children}\n </DirectiveScopeContext.Provider>\n );\n}\n\nexport function useDirectiveScope(): DirectiveScope {\n return useContext(DirectiveScopeContext);\n}\n\nexport interface DirectiveHostProviderProps {\n host: DirectiveHost;\n children: ReactNode;\n}\n\nexport function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps) {\n return <DirectiveHostContext.Provider value={host}>{children}</DirectiveHostContext.Provider>;\n}\n\nconst EMPTY_HOST: DirectiveHost = {};\n\n/**\n * The wired directive host. Unlike the render host this does NOT throw when\n * unwired: a directive still renders (named, itemised, copy-less, apply-less)\n * — degrading the chrome is honest, hiding the content is not.\n */\nexport function useDirectiveHost(): DirectiveHost {\n return useContext(DirectiveHostContext) ?? EMPTY_HOST;\n}\n"],"mappings":";;;AA2BO,IAAM,uBAA+C,CAAC,WAAW;AACtE,UAAQ,MAAM,gBAAgB,OAAO,OAAO,IAAI,OAAO,OAAO,EAAE;AAClE;;;AC0HO,SAAS,WAAW,MAAmC;AAC5D,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,YAAY,KAAK;AAAA,IACjB,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,EACjB;AACF;;;AC1IA,SAAS,iBAAAA,gBAAe,cAAAC,aAAY,eAA+B;;;ACQnE,SAAS,eAAe,kBAAkC;AAyMtD;AApBJ,IAAM,uBAAuB,cAAoC,IAAI;AAOrE,IAAM,wBAAwB,cAA8B,CAAC,CAAC;AA4BvD,SAAS,sBAAsB,EAAE,MAAM,SAAS,GAA+B;AACpF,SAAO,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,MAAO,UAAS;AAC/D;;;ADhNM,gBAAAC,YAAA;AAbN,IAAM,uBAAuBC,eAAoC,IAAI;AAO9D,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAAiC;AAC/B,SACE,gBAAAD,KAAC,qBAAqB,UAArB,EAA8B,OAAO,MACpC,0BAAAA,KAAC,yBAAsB,MAAM,KAAK,cAAc,CAAC,GAAI,UAAS,GAChE;AAEJ;AAOO,SAAS,yBAA+C;AAC7D,SAAOE,YAAW,oBAAoB;AACxC;AAGO,SAAS,mBAAkC;AAChD,QAAM,OAAO,uBAAuB;AACpC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,kBAAgC;AAC9C,QAAM,OAAO,iBAAiB;AAC9B,SAAO,QAAQ,MAAM,WAAW,IAAI,GAAG,CAAC,IAAI,CAAC;AAC/C;","names":["createContext","useContext","jsx","createContext","useContext"]}
|
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
DIRECTIVE_BLOCK_TYPE: () => DIRECTIVE_BLOCK_TYPE,
|
|
39
39
|
DbKindComponentErrorBoundary: () => DbKindComponentErrorBoundary,
|
|
40
40
|
DelegatedOutput: () => DelegatedOutput,
|
|
41
|
+
DirectiveChangeList: () => DirectiveChangeList,
|
|
41
42
|
DirectiveFallbackCard: () => DirectiveFallbackCard,
|
|
42
43
|
DirectiveHostProvider: () => DirectiveHostProvider,
|
|
43
44
|
DirectiveProvisionalCard: () => DirectiveProvisionalCard,
|
|
@@ -58,6 +59,7 @@ __export(index_exports, {
|
|
|
58
59
|
consoleErrorReporter: () => consoleErrorReporter,
|
|
59
60
|
createDbKindComponentCache: () => createDbKindComponentCache,
|
|
60
61
|
createKindRouteBinding: () => createKindRouteBinding,
|
|
62
|
+
describeChangeValue: () => describeChangeValue,
|
|
61
63
|
envelopeFromPartialKind: () => envelopeFromPartialKind,
|
|
62
64
|
fieldLabel: () => fieldLabel,
|
|
63
65
|
getDirectiveRenderer: () => getDirectiveRenderer,
|
|
@@ -86,7 +88,8 @@ __export(index_exports, {
|
|
|
86
88
|
useContentIrKindVersion: () => useContentIrKindVersion,
|
|
87
89
|
useDirectiveHost: () => useDirectiveHost,
|
|
88
90
|
useDirectiveScope: () => useDirectiveScope,
|
|
89
|
-
useKindRouteEnv: () => useKindRouteEnv
|
|
91
|
+
useKindRouteEnv: () => useKindRouteEnv,
|
|
92
|
+
useRecordValues: () => useRecordValues
|
|
90
93
|
});
|
|
91
94
|
module.exports = __toCommonJS(index_exports);
|
|
92
95
|
|
|
@@ -817,7 +820,7 @@ function useContentIrKindVersion(kind, sources) {
|
|
|
817
820
|
}
|
|
818
821
|
|
|
819
822
|
// react/KindInstanceRender.tsx
|
|
820
|
-
var
|
|
823
|
+
var import_react7 = require("react");
|
|
821
824
|
var import_content_ir9 = require("@ai-matrx/content-ir");
|
|
822
825
|
|
|
823
826
|
// directives/DirectiveRender.tsx
|
|
@@ -849,6 +852,9 @@ function recordFallbackName(nounLabel3, id) {
|
|
|
849
852
|
const short = (0, import_uuid.isUuidShape)(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
|
|
850
853
|
return `${nounLabel3} ${short}`;
|
|
851
854
|
}
|
|
855
|
+
function describeChangeValue(value) {
|
|
856
|
+
return describeValue(value);
|
|
857
|
+
}
|
|
852
858
|
function describeValue(value) {
|
|
853
859
|
if (value === null || value === void 0) return { value: "cleared", full: "cleared", clears: true };
|
|
854
860
|
if (typeof value === "string") {
|
|
@@ -877,11 +883,26 @@ function safeJson(value) {
|
|
|
877
883
|
return String(value);
|
|
878
884
|
}
|
|
879
885
|
}
|
|
880
|
-
function
|
|
886
|
+
function sameValue(a, b) {
|
|
887
|
+
if (a === b) return true;
|
|
888
|
+
if (a === null || a === void 0 || b === null || b === void 0) {
|
|
889
|
+
const emptyA = a === null || a === void 0 || a === "";
|
|
890
|
+
const emptyB = b === null || b === void 0 || b === "";
|
|
891
|
+
return emptyA && emptyB;
|
|
892
|
+
}
|
|
893
|
+
if (typeof a === "object" || typeof b === "object") return safeJson(a) === safeJson(b);
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
function itemChanges(item, current) {
|
|
881
897
|
const out = [];
|
|
882
898
|
for (const [key, raw] of Object.entries(item)) {
|
|
883
899
|
if (NOT_A_CHANGE.has(key)) continue;
|
|
884
|
-
|
|
900
|
+
const change = { key, label: fieldLabel(key), ...describeValue(raw) };
|
|
901
|
+
if (current && Object.prototype.hasOwnProperty.call(current, key)) {
|
|
902
|
+
change.before = describeValue(current[key]);
|
|
903
|
+
change.same = sameValue(current[key], raw);
|
|
904
|
+
}
|
|
905
|
+
out.push(change);
|
|
885
906
|
}
|
|
886
907
|
return out;
|
|
887
908
|
}
|
|
@@ -994,11 +1015,23 @@ function ApplyDirectiveButton({
|
|
|
994
1015
|
const conversationId = scope.conversationId || void 0;
|
|
995
1016
|
const items = directive.items;
|
|
996
1017
|
const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
);
|
|
1018
|
+
const initial = readsLedger ? { status: "checking" } : { status: "idle" };
|
|
1019
|
+
const [state, setState] = (0, import_react4.useState)(initial);
|
|
1000
1020
|
const [elapsed, setElapsed] = (0, import_react4.useState)(0);
|
|
1021
|
+
const [askingAgain, setAskingAgain] = (0, import_react4.useState)(false);
|
|
1001
1022
|
const startedAt = (0, import_react4.useRef)(null);
|
|
1023
|
+
const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
|
|
1024
|
+
const [stateFor, setStateFor] = (0, import_react4.useState)(identity);
|
|
1025
|
+
if (stateFor !== identity) {
|
|
1026
|
+
setStateFor(identity);
|
|
1027
|
+
setState(initial);
|
|
1028
|
+
setAskingAgain(false);
|
|
1029
|
+
setElapsed(0);
|
|
1030
|
+
}
|
|
1031
|
+
const currentIdentity = (0, import_react4.useRef)(identity);
|
|
1032
|
+
(0, import_react4.useEffect)(() => {
|
|
1033
|
+
currentIdentity.current = identity;
|
|
1034
|
+
}, [identity]);
|
|
1002
1035
|
const nounText = (0, import_content_ir3.nounLabel)(directive.noun, host.nouns);
|
|
1003
1036
|
const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
|
|
1004
1037
|
const read = host.applyState;
|
|
@@ -1021,7 +1054,8 @@ function ApplyDirectiveButton({
|
|
|
1021
1054
|
applied: shellItems.length,
|
|
1022
1055
|
failed: 0,
|
|
1023
1056
|
message: answer.message?.trim() ? answer.message.trim() : null,
|
|
1024
|
-
records: answer.records ?? []
|
|
1057
|
+
records: answer.records ?? [],
|
|
1058
|
+
copies: Math.max(1, answer.copies ?? 1)
|
|
1025
1059
|
} : prev
|
|
1026
1060
|
);
|
|
1027
1061
|
} else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
|
|
@@ -1061,8 +1095,12 @@ function ApplyDirectiveButton({
|
|
|
1061
1095
|
async function handleApply(again = false) {
|
|
1062
1096
|
if (!confirm) return;
|
|
1063
1097
|
const before = state;
|
|
1098
|
+
const askedFor = identity;
|
|
1099
|
+
const stillHere = () => currentIdentity.current === askedFor;
|
|
1100
|
+
const priorCopies = state.status === "applied" ? state.copies : 0;
|
|
1064
1101
|
if (host.ask) {
|
|
1065
|
-
|
|
1102
|
+
if (again) setAskingAgain(true);
|
|
1103
|
+
else setState({ status: "asking" });
|
|
1066
1104
|
let ok = false;
|
|
1067
1105
|
try {
|
|
1068
1106
|
ok = await host.ask({
|
|
@@ -1072,12 +1110,16 @@ function ApplyDirectiveButton({
|
|
|
1072
1110
|
...again ? { again: true } : {}
|
|
1073
1111
|
});
|
|
1074
1112
|
} catch (error) {
|
|
1113
|
+
if (!stillHere()) return;
|
|
1114
|
+
setAskingAgain(false);
|
|
1075
1115
|
setState({
|
|
1076
1116
|
status: "error",
|
|
1077
1117
|
message: error instanceof Error && error.message ? error.message : "We could not ask you to confirm this, so nothing was done."
|
|
1078
1118
|
});
|
|
1079
1119
|
return;
|
|
1080
1120
|
}
|
|
1121
|
+
if (!stillHere()) return;
|
|
1122
|
+
setAskingAgain(false);
|
|
1081
1123
|
if (!ok) {
|
|
1082
1124
|
setState(again ? before : { status: "idle" });
|
|
1083
1125
|
return;
|
|
@@ -1090,15 +1132,19 @@ function ApplyDirectiveButton({
|
|
|
1090
1132
|
if (conversationId) shell.conversationId = conversationId;
|
|
1091
1133
|
if (again) shell.force = true;
|
|
1092
1134
|
const result = await confirm(shell);
|
|
1135
|
+
if (!stillHere()) return;
|
|
1093
1136
|
setState({
|
|
1094
1137
|
status: "applied",
|
|
1095
1138
|
applied: result.applied,
|
|
1096
1139
|
failed: result.failed,
|
|
1097
1140
|
message: result.message?.trim() ? result.message.trim() : null,
|
|
1098
|
-
records: result.records ?? []
|
|
1141
|
+
records: result.records ?? [],
|
|
1142
|
+
// "Run again" made one more; a first apply (or a replay of one) is one.
|
|
1143
|
+
copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
|
|
1099
1144
|
});
|
|
1100
1145
|
onApplied?.();
|
|
1101
1146
|
} catch (error) {
|
|
1147
|
+
if (!stillHere()) return;
|
|
1102
1148
|
setState({
|
|
1103
1149
|
status: "error",
|
|
1104
1150
|
message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
|
|
@@ -1123,6 +1169,15 @@ function ApplyDirectiveButton({
|
|
|
1123
1169
|
bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
|
|
1124
1170
|
] })
|
|
1125
1171
|
] }),
|
|
1172
|
+
state.copies > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1173
|
+
"span",
|
|
1174
|
+
{
|
|
1175
|
+
className: "shrink-0 text-muted-foreground",
|
|
1176
|
+
"data-apply-copies": state.copies,
|
|
1177
|
+
title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
|
|
1178
|
+
children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
|
|
1179
|
+
}
|
|
1180
|
+
) : null,
|
|
1126
1181
|
records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
|
|
1127
1182
|
...record,
|
|
1128
1183
|
fallback: recordFallbackName(label2, record.id),
|
|
@@ -1133,8 +1188,9 @@ function ApplyDirectiveButton({
|
|
|
1133
1188
|
"button",
|
|
1134
1189
|
{
|
|
1135
1190
|
type: "button",
|
|
1191
|
+
disabled: askingAgain,
|
|
1136
1192
|
onClick: () => void handleApply(!bad),
|
|
1137
|
-
className: "inline-flex items-center gap-1 rounded px-1 py-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
1193
|
+
className: "inline-flex items-center gap-1 rounded px-1 py-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50",
|
|
1138
1194
|
"data-apply-action": bad ? "retry" : "run-again",
|
|
1139
1195
|
children: [
|
|
1140
1196
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RotateCcwIcon, { className: "h-3 w-3" }),
|
|
@@ -1172,6 +1228,7 @@ function ApplyDirectiveButton({
|
|
|
1172
1228
|
}
|
|
1173
1229
|
);
|
|
1174
1230
|
}
|
|
1231
|
+
const destructive = directive.directiveClass === "delete";
|
|
1175
1232
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
|
|
1176
1233
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1177
1234
|
"button",
|
|
@@ -1179,7 +1236,8 @@ function ApplyDirectiveButton({
|
|
|
1179
1236
|
type: "button",
|
|
1180
1237
|
onClick: () => void handleApply(),
|
|
1181
1238
|
disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
|
|
1182
|
-
|
|
1239
|
+
"data-apply-tone": destructive ? "destructive" : "default",
|
|
1240
|
+
className: `inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium disabled:opacity-50 ${destructive ? "border-destructive/50 bg-background text-destructive hover:bg-destructive/10" : "border-border bg-background hover:bg-accent"}`,
|
|
1183
1241
|
children: [
|
|
1184
1242
|
state.status === "applying" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlayIcon, { className: "h-3.5 w-3.5" }),
|
|
1185
1243
|
state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
|
|
@@ -1280,8 +1338,41 @@ function resetDirectiveRenderers() {
|
|
|
1280
1338
|
}
|
|
1281
1339
|
|
|
1282
1340
|
// directives/SideEffectDirectiveCard.tsx
|
|
1283
|
-
var
|
|
1341
|
+
var import_react6 = require("react");
|
|
1284
1342
|
var import_content_ir7 = require("@ai-matrx/content-ir");
|
|
1343
|
+
|
|
1344
|
+
// directives/record-values.ts
|
|
1345
|
+
var import_react5 = require("react");
|
|
1346
|
+
function useRecordValues(read, ref) {
|
|
1347
|
+
const key = ref && read ? `${ref.noun}:${ref.id}` : "";
|
|
1348
|
+
const [result, setResult] = (0, import_react5.useState)({
|
|
1349
|
+
key: "",
|
|
1350
|
+
values: null,
|
|
1351
|
+
status: "idle"
|
|
1352
|
+
});
|
|
1353
|
+
(0, import_react5.useEffect)(() => {
|
|
1354
|
+
if (!key || !read) return;
|
|
1355
|
+
const split = key.indexOf(":");
|
|
1356
|
+
const target = { noun: key.slice(0, split), id: key.slice(split + 1) };
|
|
1357
|
+
let live = true;
|
|
1358
|
+
setResult({ key, values: null, status: "loading" });
|
|
1359
|
+
read(target).then((values) => {
|
|
1360
|
+
if (!live) return;
|
|
1361
|
+
setResult({ key, values, status: values ? "ready" : "unavailable" });
|
|
1362
|
+
}).catch(() => {
|
|
1363
|
+
if (!live) return;
|
|
1364
|
+
setResult({ key, values: null, status: "unavailable" });
|
|
1365
|
+
});
|
|
1366
|
+
return () => {
|
|
1367
|
+
live = false;
|
|
1368
|
+
};
|
|
1369
|
+
}, [key, read]);
|
|
1370
|
+
if (!key) return { values: null, status: "idle" };
|
|
1371
|
+
if (result.key !== key) return { values: null, status: "loading" };
|
|
1372
|
+
return { values: result.values, status: result.status };
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// directives/SideEffectDirectiveCard.tsx
|
|
1285
1376
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1286
1377
|
var ROWS_BEFORE_FOLD = 3;
|
|
1287
1378
|
function ItemRow({
|
|
@@ -1335,44 +1426,73 @@ function ItemRow({
|
|
|
1335
1426
|
host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
|
|
1336
1427
|
] });
|
|
1337
1428
|
}
|
|
1338
|
-
function
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
recordId
|
|
1429
|
+
function DirectiveChangeList({
|
|
1430
|
+
changes,
|
|
1431
|
+
className
|
|
1342
1432
|
}) {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
host.renderCopy?.({ label: fallback, value: item, size: "xs" })
|
|
1351
|
-
] }),
|
|
1352
|
-
directive.directiveClass === "update" ? changes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "px-1 text-[11px] text-muted-foreground", children: "Sets no fields \u2014 nothing would change." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "mt-0.5 space-y-0.5 px-1", children: changes.map((change) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1433
|
+
if (changes.length === 0) {
|
|
1434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
|
|
1435
|
+
}
|
|
1436
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
|
|
1437
|
+
const before = change.before && !change.same ? change.before : null;
|
|
1438
|
+
const was = before ? before.clears ? "empty" : before.value : null;
|
|
1439
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1353
1440
|
"li",
|
|
1354
1441
|
{
|
|
1355
1442
|
className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
|
|
1356
|
-
title: `${change.label} \u2192 ${change.full}`,
|
|
1443
|
+
title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
|
|
1444
|
+
"data-change-key": change.key,
|
|
1357
1445
|
children: [
|
|
1358
1446
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
|
|
1447
|
+
was !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1448
|
+
"span",
|
|
1449
|
+
{
|
|
1450
|
+
className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
|
|
1451
|
+
"data-change-before": "",
|
|
1452
|
+
children: was
|
|
1453
|
+
}
|
|
1454
|
+
) : null,
|
|
1359
1455
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
|
|
1360
1456
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1361
1457
|
"span",
|
|
1362
1458
|
{
|
|
1363
1459
|
className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
|
|
1460
|
+
"data-change-after": "",
|
|
1364
1461
|
children: change.value
|
|
1365
1462
|
}
|
|
1366
|
-
)
|
|
1463
|
+
),
|
|
1464
|
+
change.same ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
|
|
1367
1465
|
]
|
|
1368
1466
|
},
|
|
1369
1467
|
change.key
|
|
1370
|
-
)
|
|
1468
|
+
);
|
|
1469
|
+
}) });
|
|
1470
|
+
}
|
|
1471
|
+
function TargetRow({
|
|
1472
|
+
directive,
|
|
1473
|
+
item,
|
|
1474
|
+
recordId
|
|
1475
|
+
}) {
|
|
1476
|
+
const host = useDirectiveHost();
|
|
1477
|
+
const fallback = recordFallbackName((0, import_content_ir7.nounLabel)(directive.noun, host.nouns), recordId);
|
|
1478
|
+
const isUpdate = directive.directiveClass === "update";
|
|
1479
|
+
const current = useRecordValues(
|
|
1480
|
+
isUpdate ? host.readRecord : void 0,
|
|
1481
|
+
isUpdate ? { noun: directive.noun, id: recordId } : null
|
|
1482
|
+
);
|
|
1483
|
+
const changes = isUpdate ? itemChanges(item, current.values) : [];
|
|
1484
|
+
const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
|
|
1485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
|
|
1486
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
|
|
1488
|
+
host.renderCopy?.({ label: fallback, value: item, size: "xs" })
|
|
1489
|
+
] }),
|
|
1490
|
+
isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
|
|
1371
1491
|
] });
|
|
1372
1492
|
}
|
|
1373
1493
|
function SideEffectDirectiveCard({ directive }) {
|
|
1374
1494
|
const host = useDirectiveHost();
|
|
1375
|
-
const [expanded, setExpanded] = (0,
|
|
1495
|
+
const [expanded, setExpanded] = (0, import_react6.useState)(false);
|
|
1376
1496
|
const items = directive.items;
|
|
1377
1497
|
const total = items.length;
|
|
1378
1498
|
const display = (0, import_content_ir7.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
|
|
@@ -1486,10 +1606,10 @@ function KindInstanceRenderInner({
|
|
|
1486
1606
|
className
|
|
1487
1607
|
}) {
|
|
1488
1608
|
const host = useContentIrHost();
|
|
1489
|
-
const [routingStatus, setRoutingStatus] = (0,
|
|
1609
|
+
const [routingStatus, setRoutingStatus] = (0, import_react7.useState)(
|
|
1490
1610
|
() => kindIsRoutable(kind, host) ? "routable" : "checking"
|
|
1491
1611
|
);
|
|
1492
|
-
(0,
|
|
1612
|
+
(0, import_react7.useEffect)(() => {
|
|
1493
1613
|
let cancelled = false;
|
|
1494
1614
|
let warmed = false;
|
|
1495
1615
|
const syncRoutingStatus = () => {
|
|
@@ -1537,9 +1657,9 @@ function KindInstanceRenderInner({
|
|
|
1537
1657
|
}
|
|
1538
1658
|
|
|
1539
1659
|
// react/ProvisionalKindBoundary.tsx
|
|
1540
|
-
var
|
|
1660
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
1541
1661
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1542
|
-
var ProvisionalKindBoundaryInner = class extends
|
|
1662
|
+
var ProvisionalKindBoundaryInner = class extends import_react8.default.Component {
|
|
1543
1663
|
state = { failed: false };
|
|
1544
1664
|
static getDerivedStateFromError() {
|
|
1545
1665
|
return { failed: true };
|
|
@@ -1575,7 +1695,7 @@ function ProvisionalKindFrame({ children }) {
|
|
|
1575
1695
|
}
|
|
1576
1696
|
|
|
1577
1697
|
// react/GenericStructuredView.tsx
|
|
1578
|
-
var
|
|
1698
|
+
var import_react9 = require("react");
|
|
1579
1699
|
var import_content_ir10 = require("@ai-matrx/content-ir");
|
|
1580
1700
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1581
1701
|
function readStructuredValue(content, metadata) {
|