@ai-matrx/content-ir-react 0.11.11 → 0.12.1
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 +33 -0
- package/dist/directive-host-Bl-ZgJWp.d.cts +116 -0
- package/dist/directive-host-Bl-ZgJWp.d.ts +116 -0
- package/dist/directives.cjs +164 -23
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.d.cts +43 -2
- package/dist/directives.d.ts +43 -2
- package/dist/directives.js +149 -7
- package/dist/directives.js.map +1 -1
- package/dist/{host-types-C8jR31Wk.d.cts → host-types-BgROpyjE.d.cts} +1 -1
- package/dist/{host-types-IUEDmso7.d.ts → host-types-Cz-1pGHz.d.ts} +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 +179 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -3
- package/dist/index.d.ts +44 -3
- package/dist/index.js +149 -7
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +174 -39
- 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 +143 -7
- package/dist/react.js.map +1 -1
- package/dist/{route-B9U-9QCw.d.cts → route-DxK1SoPh.d.cts} +54 -1
- package/dist/{route-gNpJ8zzW.d.ts → route-JGZfl2nL.d.ts} +54 -1
- package/dist/route.d.cts +1 -1
- package/dist/route.d.ts +1 -1
- package/package.json +3 -2
- package/dist/directive-host-DAb7Keg8.d.cts +0 -63
- package/dist/directive-host-DAb7Keg8.d.ts +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.1
|
|
4
|
+
|
|
5
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
+
`git diff npm/content-ir-react/v0.12.0..npm/content-ir-react/v0.12.1 -- apps/shared/content-ir-react`).
|
|
7
|
+
No source changes intended and no consumer action required.
|
|
8
|
+
|
|
9
|
+
## 0.12.0 — 2026-09-30
|
|
10
|
+
|
|
11
|
+
- **An update/delete card names the record it changes.** Rows for
|
|
12
|
+
`directive_v1_update_*` / `directive_v1_delete_*` items carrying an `id` render
|
|
13
|
+
the EXISTING record through the new host seam `renderRecord` (its live name, a
|
|
14
|
+
way to open it). With no seam the row reads "Task 1a2b3c4d" — never "Item",
|
|
15
|
+
and never an update's NEW title passed off as the record's name.
|
|
16
|
+
- **An update card lists what it changes** — every field the item sets,
|
|
17
|
+
"Due date → 2026-10-15", "Assignee id → cleared" (`itemChanges`, exported with
|
|
18
|
+
`itemRecordId`, `fieldLabel`, `recordFallbackName`, `TARGETING_CLASSES`).
|
|
19
|
+
- **Apply tells the truth about where it is.** New host seam `ask(request)` runs
|
|
20
|
+
BEFORE anything is applied; the control stays idle (disabled) while it is open
|
|
21
|
+
and only a yes moves it to "Applying…". A no leaves the card exactly as it was.
|
|
22
|
+
- **The tally is a door.** `DirectiveApplyResult` gains optional `message` (the
|
|
23
|
+
server's receipt sentence, shown instead of "Applied 1") and `records`
|
|
24
|
+
(`{noun, id}[]`, drawn through `renderRecord` with `context: "applied"` and
|
|
25
|
+
`trashed` for a delete).
|
|
26
|
+
|
|
27
|
+
### Consumer action
|
|
28
|
+
|
|
29
|
+
- Wire `ask` (your consequence dialog) and stop asking inside `confirm`, or your
|
|
30
|
+
card will still read "Applying…" while your dialog is up.
|
|
31
|
+
- Wire `renderRecord` to your live-name + open primitive, and return `message`
|
|
32
|
+
and `records` from `confirm` (the server's receipt `message` and
|
|
33
|
+
`resource_kind`/`resource_ids`).
|
|
34
|
+
|
|
3
35
|
## 0.11.11 — 2026-09-25
|
|
4
36
|
|
|
5
37
|
- **THE PROVISIONAL TIER in `DirectiveRender`.** A directive whose JSON has not
|
|
@@ -429,3 +461,4 @@ customers building on the platform render kinds through ONE implementation
|
|
|
429
461
|
kind in an app and look like a data problem for weeks.
|
|
430
462
|
|
|
431
463
|
Peer dependencies: `react >= 19` and `@ai-matrx/content-ir` 0.2.0 (exact).
|
|
464
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { DecodedDirective, DirectiveNounCatalog, DirectiveItemKindLookup } from '@ai-matrx/content-ir';
|
|
4
|
+
|
|
5
|
+
interface DirectiveApplyResult {
|
|
6
|
+
applied: number;
|
|
7
|
+
failed: number;
|
|
8
|
+
/**
|
|
9
|
+
* The server's own receipt sentence ("Deleted task “Ship it”."), shown in
|
|
10
|
+
* place of a bare count. Never composed client-side.
|
|
11
|
+
*/
|
|
12
|
+
message?: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* The records the apply created or changed, so the tally is a way IN to them
|
|
15
|
+
* (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.
|
|
16
|
+
*/
|
|
17
|
+
records?: DirectiveRecordRef[];
|
|
18
|
+
}
|
|
19
|
+
/** One record a directive names: the noun it is, and its id. */
|
|
20
|
+
interface DirectiveRecordRef {
|
|
21
|
+
noun: string;
|
|
22
|
+
id: string;
|
|
23
|
+
}
|
|
24
|
+
interface DirectiveRecordProps extends DirectiveRecordRef {
|
|
25
|
+
/**
|
|
26
|
+
* What to show until (or unless) the host resolves the live name —
|
|
27
|
+
* "Task 1a2b3c4d". Never blank, never "Item".
|
|
28
|
+
*/
|
|
29
|
+
fallback: string;
|
|
30
|
+
/** Where it is drawn: an item row, the confirm dialog, or the applied tally. */
|
|
31
|
+
context: "row" | "dialog" | "applied";
|
|
32
|
+
/**
|
|
33
|
+
* True when the record is (about to be) in the trash — the host may still
|
|
34
|
+
* name it but should not promise it opens as a live record.
|
|
35
|
+
*/
|
|
36
|
+
trashed?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** What the person is being asked to approve — everything the card knows. */
|
|
39
|
+
interface DirectiveAskRequest {
|
|
40
|
+
directive: DecodedDirective;
|
|
41
|
+
/** The items, exactly as they will be sent. */
|
|
42
|
+
items: Record<string, unknown>[];
|
|
43
|
+
/** The noun's human label ("Task"), from the host's catalog when it has one. */
|
|
44
|
+
nounLabel: string;
|
|
45
|
+
}
|
|
46
|
+
interface DirectiveOpenItemOptions {
|
|
47
|
+
/** The item, `__kind`-stamped when the shape has an item kind. */
|
|
48
|
+
item: Record<string, unknown>;
|
|
49
|
+
/** The content-IR kind this item IS, or null when the shape has none. */
|
|
50
|
+
itemKind: string | null;
|
|
51
|
+
/** What the item is called — the same title the card shows. */
|
|
52
|
+
title: string;
|
|
53
|
+
/** "Run Create agent definition · item 1 of 1". */
|
|
54
|
+
subtitle: string | null;
|
|
55
|
+
directive: DecodedDirective;
|
|
56
|
+
index: number;
|
|
57
|
+
total: number;
|
|
58
|
+
}
|
|
59
|
+
interface DirectiveCopyProps {
|
|
60
|
+
label: string;
|
|
61
|
+
/** The value to copy — the whole shell or one item. */
|
|
62
|
+
value: unknown;
|
|
63
|
+
/** The item's kind, when copying an item. */
|
|
64
|
+
kind?: string | null;
|
|
65
|
+
size: "icon" | "xs";
|
|
66
|
+
}
|
|
67
|
+
interface DirectiveHost {
|
|
68
|
+
/**
|
|
69
|
+
* Apply a side-effect directive as the user (the server re-validates every
|
|
70
|
+
* item against the registered model and runs the ONE handler; idempotent).
|
|
71
|
+
* Reject with an Error whose message is safe to show.
|
|
72
|
+
*/
|
|
73
|
+
confirm?: (shell: {
|
|
74
|
+
__kind: string;
|
|
75
|
+
items: Record<string, unknown>[];
|
|
76
|
+
}) => Promise<DirectiveApplyResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Ask the person BEFORE anything runs, naming the consequence (the records,
|
|
79
|
+
* what changes). Resolve `true` to go ahead, `false` to leave the card as it
|
|
80
|
+
* was. The Apply control stays idle while this is open — nothing reads as
|
|
81
|
+
* "applying" until the person has said yes. Absent → `confirm` is called
|
|
82
|
+
* directly (a host that asks inside `confirm` keeps working, but its card
|
|
83
|
+
* will read "Applying…" while its dialog is up).
|
|
84
|
+
*/
|
|
85
|
+
ask?: (request: DirectiveAskRequest) => Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Draw ONE existing record by its live name, as a way to open it. Used on
|
|
88
|
+
* update/delete rows (the record that will change), in the host's own ask
|
|
89
|
+
* dialog if it likes, and on the applied tally (what was written). Absent →
|
|
90
|
+
* the fallback text, unlinked.
|
|
91
|
+
*/
|
|
92
|
+
renderRecord?: (props: DirectiveRecordProps) => ReactNode;
|
|
93
|
+
/** Open ONE item to read it properly before approving the write. */
|
|
94
|
+
openItem?: (options: DirectiveOpenItemOptions) => void;
|
|
95
|
+
/** Host copy controls. */
|
|
96
|
+
renderCopy?: (props: DirectiveCopyProps) => ReactNode;
|
|
97
|
+
/** The mirrored noun catalog (label / family / title column). */
|
|
98
|
+
nouns?: DirectiveNounCatalog;
|
|
99
|
+
/** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */
|
|
100
|
+
itemKind?: DirectiveItemKindLookup;
|
|
101
|
+
/** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
|
|
102
|
+
reportError?: (message: string) => void;
|
|
103
|
+
}
|
|
104
|
+
interface DirectiveHostProviderProps {
|
|
105
|
+
host: DirectiveHost;
|
|
106
|
+
children: ReactNode;
|
|
107
|
+
}
|
|
108
|
+
declare function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps): react.JSX.Element;
|
|
109
|
+
/**
|
|
110
|
+
* The wired directive host. Unlike the render host this does NOT throw when
|
|
111
|
+
* unwired: a directive still renders (named, itemised, copy-less, apply-less)
|
|
112
|
+
* — degrading the chrome is honest, hiding the content is not.
|
|
113
|
+
*/
|
|
114
|
+
declare function useDirectiveHost(): DirectiveHost;
|
|
115
|
+
|
|
116
|
+
export { type DirectiveApplyResult as D, type DirectiveAskRequest as a, type DirectiveCopyProps as b, type DirectiveHost as c, DirectiveHostProvider as d, type DirectiveHostProviderProps as e, type DirectiveOpenItemOptions as f, type DirectiveRecordProps as g, type DirectiveRecordRef as h, useDirectiveHost as u };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { DecodedDirective, DirectiveNounCatalog, DirectiveItemKindLookup } from '@ai-matrx/content-ir';
|
|
4
|
+
|
|
5
|
+
interface DirectiveApplyResult {
|
|
6
|
+
applied: number;
|
|
7
|
+
failed: number;
|
|
8
|
+
/**
|
|
9
|
+
* The server's own receipt sentence ("Deleted task “Ship it”."), shown in
|
|
10
|
+
* place of a bare count. Never composed client-side.
|
|
11
|
+
*/
|
|
12
|
+
message?: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* The records the apply created or changed, so the tally is a way IN to them
|
|
15
|
+
* (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.
|
|
16
|
+
*/
|
|
17
|
+
records?: DirectiveRecordRef[];
|
|
18
|
+
}
|
|
19
|
+
/** One record a directive names: the noun it is, and its id. */
|
|
20
|
+
interface DirectiveRecordRef {
|
|
21
|
+
noun: string;
|
|
22
|
+
id: string;
|
|
23
|
+
}
|
|
24
|
+
interface DirectiveRecordProps extends DirectiveRecordRef {
|
|
25
|
+
/**
|
|
26
|
+
* What to show until (or unless) the host resolves the live name —
|
|
27
|
+
* "Task 1a2b3c4d". Never blank, never "Item".
|
|
28
|
+
*/
|
|
29
|
+
fallback: string;
|
|
30
|
+
/** Where it is drawn: an item row, the confirm dialog, or the applied tally. */
|
|
31
|
+
context: "row" | "dialog" | "applied";
|
|
32
|
+
/**
|
|
33
|
+
* True when the record is (about to be) in the trash — the host may still
|
|
34
|
+
* name it but should not promise it opens as a live record.
|
|
35
|
+
*/
|
|
36
|
+
trashed?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** What the person is being asked to approve — everything the card knows. */
|
|
39
|
+
interface DirectiveAskRequest {
|
|
40
|
+
directive: DecodedDirective;
|
|
41
|
+
/** The items, exactly as they will be sent. */
|
|
42
|
+
items: Record<string, unknown>[];
|
|
43
|
+
/** The noun's human label ("Task"), from the host's catalog when it has one. */
|
|
44
|
+
nounLabel: string;
|
|
45
|
+
}
|
|
46
|
+
interface DirectiveOpenItemOptions {
|
|
47
|
+
/** The item, `__kind`-stamped when the shape has an item kind. */
|
|
48
|
+
item: Record<string, unknown>;
|
|
49
|
+
/** The content-IR kind this item IS, or null when the shape has none. */
|
|
50
|
+
itemKind: string | null;
|
|
51
|
+
/** What the item is called — the same title the card shows. */
|
|
52
|
+
title: string;
|
|
53
|
+
/** "Run Create agent definition · item 1 of 1". */
|
|
54
|
+
subtitle: string | null;
|
|
55
|
+
directive: DecodedDirective;
|
|
56
|
+
index: number;
|
|
57
|
+
total: number;
|
|
58
|
+
}
|
|
59
|
+
interface DirectiveCopyProps {
|
|
60
|
+
label: string;
|
|
61
|
+
/** The value to copy — the whole shell or one item. */
|
|
62
|
+
value: unknown;
|
|
63
|
+
/** The item's kind, when copying an item. */
|
|
64
|
+
kind?: string | null;
|
|
65
|
+
size: "icon" | "xs";
|
|
66
|
+
}
|
|
67
|
+
interface DirectiveHost {
|
|
68
|
+
/**
|
|
69
|
+
* Apply a side-effect directive as the user (the server re-validates every
|
|
70
|
+
* item against the registered model and runs the ONE handler; idempotent).
|
|
71
|
+
* Reject with an Error whose message is safe to show.
|
|
72
|
+
*/
|
|
73
|
+
confirm?: (shell: {
|
|
74
|
+
__kind: string;
|
|
75
|
+
items: Record<string, unknown>[];
|
|
76
|
+
}) => Promise<DirectiveApplyResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Ask the person BEFORE anything runs, naming the consequence (the records,
|
|
79
|
+
* what changes). Resolve `true` to go ahead, `false` to leave the card as it
|
|
80
|
+
* was. The Apply control stays idle while this is open — nothing reads as
|
|
81
|
+
* "applying" until the person has said yes. Absent → `confirm` is called
|
|
82
|
+
* directly (a host that asks inside `confirm` keeps working, but its card
|
|
83
|
+
* will read "Applying…" while its dialog is up).
|
|
84
|
+
*/
|
|
85
|
+
ask?: (request: DirectiveAskRequest) => Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Draw ONE existing record by its live name, as a way to open it. Used on
|
|
88
|
+
* update/delete rows (the record that will change), in the host's own ask
|
|
89
|
+
* dialog if it likes, and on the applied tally (what was written). Absent →
|
|
90
|
+
* the fallback text, unlinked.
|
|
91
|
+
*/
|
|
92
|
+
renderRecord?: (props: DirectiveRecordProps) => ReactNode;
|
|
93
|
+
/** Open ONE item to read it properly before approving the write. */
|
|
94
|
+
openItem?: (options: DirectiveOpenItemOptions) => void;
|
|
95
|
+
/** Host copy controls. */
|
|
96
|
+
renderCopy?: (props: DirectiveCopyProps) => ReactNode;
|
|
97
|
+
/** The mirrored noun catalog (label / family / title column). */
|
|
98
|
+
nouns?: DirectiveNounCatalog;
|
|
99
|
+
/** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */
|
|
100
|
+
itemKind?: DirectiveItemKindLookup;
|
|
101
|
+
/** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
|
|
102
|
+
reportError?: (message: string) => void;
|
|
103
|
+
}
|
|
104
|
+
interface DirectiveHostProviderProps {
|
|
105
|
+
host: DirectiveHost;
|
|
106
|
+
children: ReactNode;
|
|
107
|
+
}
|
|
108
|
+
declare function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps): react.JSX.Element;
|
|
109
|
+
/**
|
|
110
|
+
* The wired directive host. Unlike the render host this does NOT throw when
|
|
111
|
+
* unwired: a directive still renders (named, itemised, copy-less, apply-less)
|
|
112
|
+
* — degrading the chrome is honest, hiding the content is not.
|
|
113
|
+
*/
|
|
114
|
+
declare function useDirectiveHost(): DirectiveHost;
|
|
115
|
+
|
|
116
|
+
export { type DirectiveApplyResult as D, type DirectiveAskRequest as a, type DirectiveCopyProps as b, type DirectiveHost as c, DirectiveHostProvider as d, type DirectiveHostProviderProps as e, type DirectiveOpenItemOptions as f, type DirectiveRecordProps as g, type DirectiveRecordRef as h, useDirectiveHost as u };
|
package/dist/directives.cjs
CHANGED
|
@@ -22,13 +22,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var directives_exports = {};
|
|
23
23
|
__export(directives_exports, {
|
|
24
24
|
ApplyDirectiveButton: () => ApplyDirectiveButton,
|
|
25
|
+
CHANGE_VALUE_MAX: () => CHANGE_VALUE_MAX,
|
|
25
26
|
DirectiveFallbackCard: () => DirectiveFallbackCard,
|
|
26
27
|
DirectiveHostProvider: () => DirectiveHostProvider,
|
|
27
28
|
DirectiveProvisionalCard: () => DirectiveProvisionalCard,
|
|
28
29
|
DirectiveRender: () => DirectiveRender,
|
|
29
30
|
SideEffectDirectiveCard: () => SideEffectDirectiveCard,
|
|
31
|
+
TARGETING_CLASSES: () => TARGETING_CLASSES,
|
|
32
|
+
fieldLabel: () => fieldLabel,
|
|
30
33
|
getDirectiveRenderer: () => getDirectiveRenderer,
|
|
34
|
+
itemChanges: () => itemChanges,
|
|
35
|
+
itemRecordId: () => itemRecordId,
|
|
31
36
|
provisionalDirectiveSlug: () => provisionalDirectiveSlug,
|
|
37
|
+
recordFallbackName: () => recordFallbackName,
|
|
32
38
|
registerDirectiveRenderer: () => registerDirectiveRenderer,
|
|
33
39
|
resetDirectiveRenderers: () => resetDirectiveRenderers,
|
|
34
40
|
useDirectiveHost: () => useDirectiveHost
|
|
@@ -68,6 +74,63 @@ function resetDirectiveRenderers() {
|
|
|
68
74
|
|
|
69
75
|
// directives/ApplyDirectiveButton.tsx
|
|
70
76
|
var import_react2 = require("react");
|
|
77
|
+
var import_content_ir2 = require("@ai-matrx/content-ir");
|
|
78
|
+
|
|
79
|
+
// directives/item-changes.ts
|
|
80
|
+
var import_uuid = require("@ai-matrx/kit/uuid");
|
|
81
|
+
var TARGETING_CLASSES = /* @__PURE__ */ new Set(["update", "delete"]);
|
|
82
|
+
var NOT_A_CHANGE = /* @__PURE__ */ new Set(["__kind", "id"]);
|
|
83
|
+
var CHANGE_VALUE_MAX = 60;
|
|
84
|
+
function itemRecordId(item) {
|
|
85
|
+
const id = item.id;
|
|
86
|
+
if (typeof id === "string" && id.trim()) return id.trim();
|
|
87
|
+
if (typeof id === "number") return String(id);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function fieldLabel(key) {
|
|
91
|
+
const spaced = key.replace(/_/g, " ").trim();
|
|
92
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
93
|
+
}
|
|
94
|
+
function recordFallbackName(nounLabel3, id) {
|
|
95
|
+
const short = (0, import_uuid.isUuidShape)(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
|
|
96
|
+
return `${nounLabel3} ${short}`;
|
|
97
|
+
}
|
|
98
|
+
function describeValue(value) {
|
|
99
|
+
if (value === null || value === void 0) return { value: "cleared", full: "cleared", clears: true };
|
|
100
|
+
if (typeof value === "string") {
|
|
101
|
+
const trimmed = value.trim();
|
|
102
|
+
if (!trimmed) return { value: "cleared", full: "cleared", clears: true };
|
|
103
|
+
const oneLine = trimmed.replace(/\s+/g, " ");
|
|
104
|
+
const shown = oneLine.length > CHANGE_VALUE_MAX ? `${oneLine.slice(0, CHANGE_VALUE_MAX - 1)}\u2026` : oneLine;
|
|
105
|
+
return { value: shown, full: trimmed, clears: false };
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
108
|
+
return { value: String(value), full: String(value), clears: false };
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
const n2 = value.length;
|
|
112
|
+
const text2 = n2 === 0 ? "cleared" : `${n2} ${n2 === 1 ? "entry" : "entries"}`;
|
|
113
|
+
return { value: text2, full: safeJson(value), clears: n2 === 0 };
|
|
114
|
+
}
|
|
115
|
+
const n = Object.keys(value).length;
|
|
116
|
+
const text = n === 0 ? "cleared" : `${n} ${n === 1 ? "field" : "fields"}`;
|
|
117
|
+
return { value: text, full: safeJson(value), clears: n === 0 };
|
|
118
|
+
}
|
|
119
|
+
function safeJson(value) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.stringify(value, null, 2);
|
|
122
|
+
} catch {
|
|
123
|
+
return String(value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function itemChanges(item) {
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const [key, raw] of Object.entries(item)) {
|
|
129
|
+
if (NOT_A_CHANGE.has(key)) continue;
|
|
130
|
+
out.push({ key, label: fieldLabel(key), ...describeValue(raw) });
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
71
134
|
|
|
72
135
|
// directives/icons.tsx
|
|
73
136
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
@@ -185,10 +248,38 @@ function ApplyDirectiveButton({
|
|
|
185
248
|
const items = directive.items;
|
|
186
249
|
async function handleApply() {
|
|
187
250
|
if (!confirm) return;
|
|
251
|
+
if (host.ask) {
|
|
252
|
+
setState({ status: "asking" });
|
|
253
|
+
let ok = false;
|
|
254
|
+
try {
|
|
255
|
+
ok = await host.ask({
|
|
256
|
+
directive,
|
|
257
|
+
items,
|
|
258
|
+
nounLabel: (0, import_content_ir2.nounLabel)(directive.noun, host.nouns)
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
setState({
|
|
262
|
+
status: "error",
|
|
263
|
+
message: error instanceof Error && error.message ? error.message : "We could not ask you to confirm this, so nothing was done."
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!ok) {
|
|
268
|
+
setState({ status: "idle" });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
setElapsed(0);
|
|
188
273
|
setState({ status: "applying" });
|
|
189
274
|
try {
|
|
190
275
|
const result = await confirm({ __kind: directive.slug, items });
|
|
191
|
-
setState({
|
|
276
|
+
setState({
|
|
277
|
+
status: "applied",
|
|
278
|
+
applied: result.applied,
|
|
279
|
+
failed: result.failed,
|
|
280
|
+
message: result.message?.trim() ? result.message.trim() : null,
|
|
281
|
+
records: result.records ?? []
|
|
282
|
+
});
|
|
192
283
|
onApplied?.();
|
|
193
284
|
} catch (error) {
|
|
194
285
|
setState({
|
|
@@ -199,15 +290,26 @@ function ApplyDirectiveButton({
|
|
|
199
290
|
}
|
|
200
291
|
if (state.status === "applied") {
|
|
201
292
|
const bad = state.failed > 0;
|
|
293
|
+
const trashed = directive.directiveClass === "delete";
|
|
294
|
+
const label2 = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
|
|
295
|
+
const records = host.renderRecord ? state.records : [];
|
|
202
296
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
203
297
|
"span",
|
|
204
298
|
{
|
|
205
|
-
className: `inline-flex items-center gap-1 text-xs ${bad ? "text-destructive" : "text-primary"}`,
|
|
299
|
+
className: `inline-flex min-w-0 flex-wrap items-center gap-1 text-xs ${bad ? "text-destructive" : "text-primary"}`,
|
|
300
|
+
"data-apply-state": "applied",
|
|
206
301
|
children: [
|
|
207
|
-
bad ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckCircleIcon, { className: "h-3.5 w-3.5" }),
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
302
|
+
bad ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckCircleIcon, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
303
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
|
|
304
|
+
state.message ?? `Applied ${state.applied}`,
|
|
305
|
+
bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
|
|
306
|
+
] }),
|
|
307
|
+
records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
|
|
308
|
+
...record,
|
|
309
|
+
fallback: recordFallbackName(label2, record.id),
|
|
310
|
+
context: "applied",
|
|
311
|
+
trashed
|
|
312
|
+
}) }, `${record.noun}:${record.id}`))
|
|
211
313
|
]
|
|
212
314
|
}
|
|
213
315
|
);
|
|
@@ -218,7 +320,7 @@ function ApplyDirectiveButton({
|
|
|
218
320
|
{
|
|
219
321
|
type: "button",
|
|
220
322
|
onClick: () => void handleApply(),
|
|
221
|
-
disabled: state.status === "applying" || items.length === 0,
|
|
323
|
+
disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
|
|
222
324
|
className: "inline-flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1 text-xs font-medium hover:bg-accent disabled:opacity-50",
|
|
223
325
|
children: [
|
|
224
326
|
state.status === "applying" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PlayIcon, { className: "h-3.5 w-3.5" }),
|
|
@@ -235,7 +337,7 @@ function ApplyDirectiveButton({
|
|
|
235
337
|
|
|
236
338
|
// directives/SideEffectDirectiveCard.tsx
|
|
237
339
|
var import_react3 = require("react");
|
|
238
|
-
var
|
|
340
|
+
var import_content_ir3 = require("@ai-matrx/content-ir");
|
|
239
341
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
240
342
|
var ROWS_BEFORE_FOLD = 3;
|
|
241
343
|
function ItemRow({
|
|
@@ -245,12 +347,16 @@ function ItemRow({
|
|
|
245
347
|
total
|
|
246
348
|
}) {
|
|
247
349
|
const host = useDirectiveHost();
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
350
|
+
const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
|
|
351
|
+
if (recordId) {
|
|
352
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TargetRow, { directive, item, recordId });
|
|
353
|
+
}
|
|
354
|
+
const titleColumn = (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns);
|
|
355
|
+
const title = (0, import_content_ir3.itemTitle)(item, titleColumn, index, total);
|
|
356
|
+
const subtitle = (0, import_content_ir3.itemSubtitle)(item);
|
|
357
|
+
const facts = (0, import_content_ir3.itemFacts)(item);
|
|
252
358
|
const kind = host.itemKind?.(directive.slug) ?? null;
|
|
253
|
-
const display = (0,
|
|
359
|
+
const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
|
|
254
360
|
const openItem = host.openItem;
|
|
255
361
|
const body = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
256
362
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 shrink-0 truncate font-medium text-foreground", children: title }),
|
|
@@ -269,7 +375,7 @@ function ItemRow({
|
|
|
269
375
|
type: "button",
|
|
270
376
|
onClick: () => openItem({
|
|
271
377
|
// Stamped so the panel's kind pipeline types it from its own first key.
|
|
272
|
-
item: (0,
|
|
378
|
+
item: (0, import_content_ir3.asKindInstance)(item, kind) ?? item,
|
|
273
379
|
itemKind: kind,
|
|
274
380
|
title,
|
|
275
381
|
subtitle: `${display.title} \xB7 item ${index + 1} of ${total}`,
|
|
@@ -285,12 +391,47 @@ function ItemRow({
|
|
|
285
391
|
host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
|
|
286
392
|
] });
|
|
287
393
|
}
|
|
394
|
+
function TargetRow({
|
|
395
|
+
directive,
|
|
396
|
+
item,
|
|
397
|
+
recordId
|
|
398
|
+
}) {
|
|
399
|
+
const host = useDirectiveHost();
|
|
400
|
+
const fallback = recordFallbackName((0, import_content_ir3.nounLabel)(directive.noun, host.nouns), recordId);
|
|
401
|
+
const changes = directive.directiveClass === "update" ? itemChanges(item) : [];
|
|
402
|
+
const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
|
|
403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
|
|
404
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
405
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
|
|
406
|
+
host.renderCopy?.({ label: fallback, value: item, size: "xs" })
|
|
407
|
+
] }),
|
|
408
|
+
directive.directiveClass === "update" ? changes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "px-1 text-[11px] text-muted-foreground", children: "Sets no fields \u2014 nothing would change." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mt-0.5 space-y-0.5 px-1", children: changes.map((change) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
409
|
+
"li",
|
|
410
|
+
{
|
|
411
|
+
className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
|
|
412
|
+
title: `${change.label} \u2192 ${change.full}`,
|
|
413
|
+
children: [
|
|
414
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
|
|
415
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
|
|
416
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
417
|
+
"span",
|
|
418
|
+
{
|
|
419
|
+
className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
|
|
420
|
+
children: change.value
|
|
421
|
+
}
|
|
422
|
+
)
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
change.key
|
|
426
|
+
)) }) : null
|
|
427
|
+
] });
|
|
428
|
+
}
|
|
288
429
|
function SideEffectDirectiveCard({ directive }) {
|
|
289
430
|
const host = useDirectiveHost();
|
|
290
431
|
const [expanded, setExpanded] = (0, import_react3.useState)(false);
|
|
291
432
|
const items = directive.items;
|
|
292
433
|
const total = items.length;
|
|
293
|
-
const display = (0,
|
|
434
|
+
const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
|
|
294
435
|
const Icon = classIcon(directive.directiveClass, directive.noun);
|
|
295
436
|
const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
|
|
296
437
|
const hidden = total - shown.length;
|
|
@@ -329,12 +470,12 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
329
470
|
}
|
|
330
471
|
|
|
331
472
|
// directives/DirectiveFallbackCard.tsx
|
|
332
|
-
var
|
|
473
|
+
var import_content_ir4 = require("@ai-matrx/content-ir");
|
|
333
474
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
334
475
|
function DirectiveFallbackCard({ directive, reason }) {
|
|
335
476
|
const host = useDirectiveHost();
|
|
336
477
|
const count = directive.items.length;
|
|
337
|
-
const display = (0,
|
|
478
|
+
const display = (0, import_content_ir4.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
|
|
338
479
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "my-3 inline-flex flex-wrap items-center gap-2 rounded-md border border-border bg-muted px-2.5 py-1 text-sm", children: [
|
|
339
480
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BoxesIcon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
340
481
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-foreground", children: display.title }),
|
|
@@ -357,7 +498,7 @@ function DirectiveFallbackCard({ directive, reason }) {
|
|
|
357
498
|
}
|
|
358
499
|
|
|
359
500
|
// directives/DirectiveProvisionalCard.tsx
|
|
360
|
-
var
|
|
501
|
+
var import_content_ir5 = require("@ai-matrx/content-ir");
|
|
361
502
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
362
503
|
var COMPLETE_SLUG_RE = /^\{\s*"__kind"\s*:\s*"([^"]+)"/;
|
|
363
504
|
function provisionalDirectiveSlug(content) {
|
|
@@ -367,8 +508,8 @@ function provisionalDirectiveSlug(content) {
|
|
|
367
508
|
function DirectiveProvisionalCard({ content, streaming, className }) {
|
|
368
509
|
const host = useDirectiveHost();
|
|
369
510
|
const slug = provisionalDirectiveSlug(content);
|
|
370
|
-
const known = slug ? (0,
|
|
371
|
-
const display = known ? (0,
|
|
511
|
+
const known = slug ? (0, import_content_ir5.parseDirectiveSlug)(slug) : null;
|
|
512
|
+
const display = known ? (0, import_content_ir5.directiveDisplay)(known.directiveClass, known.noun, host.nouns) : null;
|
|
372
513
|
const Icon = known ? classIcon(known.directiveClass, known.noun) : BoxesIcon;
|
|
373
514
|
const title = display?.title ?? "Structured action";
|
|
374
515
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -397,7 +538,7 @@ function DirectiveProvisionalCard({ content, streaming, className }) {
|
|
|
397
538
|
}
|
|
398
539
|
|
|
399
540
|
// directives/DirectiveRender.tsx
|
|
400
|
-
var
|
|
541
|
+
var import_content_ir6 = require("@ai-matrx/content-ir");
|
|
401
542
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
402
543
|
function isWholeJson(content) {
|
|
403
544
|
try {
|
|
@@ -409,13 +550,13 @@ function isWholeJson(content) {
|
|
|
409
550
|
}
|
|
410
551
|
function DirectiveRender({ content, className, streaming }) {
|
|
411
552
|
const host = useDirectiveHost();
|
|
412
|
-
const directive = (0,
|
|
553
|
+
const directive = (0, import_content_ir6.tryDecodeDirectiveContent)(content, (message) => {
|
|
413
554
|
const line = `[kind-directives] ${message}`;
|
|
414
555
|
if (host.reportError) host.reportError(line);
|
|
415
556
|
else console.error(line);
|
|
416
557
|
});
|
|
417
558
|
if (!directive) {
|
|
418
|
-
if (typeof content === "string" && (0,
|
|
559
|
+
if (typeof content === "string" && (0, import_content_ir6.looksLikeDirectiveHead)(content) && !isWholeJson(content)) {
|
|
419
560
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveProvisionalCard, { content, streaming: streaming !== false, className });
|
|
420
561
|
}
|
|
421
562
|
const raw = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|