@assistant-ui/react-generative-ui 0.0.15 → 0.0.17

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.
Files changed (61) hide show
  1. package/dist/convert/boundSpec.d.ts +51 -0
  2. package/dist/convert/boundSpec.d.ts.map +1 -0
  3. package/dist/convert/boundSpec.js +108 -0
  4. package/dist/convert/boundSpec.js.map +1 -0
  5. package/dist/convert/copyBounded.d.ts +28 -0
  6. package/dist/convert/copyBounded.d.ts.map +1 -0
  7. package/dist/convert/copyBounded.js +38 -0
  8. package/dist/convert/copyBounded.js.map +1 -0
  9. package/dist/convert/isElement.d.ts +6 -0
  10. package/dist/convert/isElement.d.ts.map +1 -0
  11. package/dist/convert/isElement.js +6 -0
  12. package/dist/convert/isElement.js.map +1 -0
  13. package/dist/convert/takeRun.d.ts +9 -0
  14. package/dist/convert/takeRun.d.ts.map +1 -0
  15. package/dist/convert/takeRun.js +20 -0
  16. package/dist/convert/takeRun.js.map +1 -0
  17. package/dist/slack/constants.d.ts +1 -26
  18. package/dist/slack/constants.d.ts.map +1 -1
  19. package/dist/slack/constants.js +1 -26
  20. package/dist/slack/constants.js.map +1 -1
  21. package/dist/slack/fromSlackBlocks.js +6 -10
  22. package/dist/slack/fromSlackBlocks.js.map +1 -1
  23. package/dist/slack/toSlackBlocks.d.ts.map +1 -1
  24. package/dist/slack/toSlackBlocks.js +28 -95
  25. package/dist/slack/toSlackBlocks.js.map +1 -1
  26. package/dist/teams/constants.d.ts +1 -28
  27. package/dist/teams/constants.d.ts.map +1 -1
  28. package/dist/teams/constants.js +1 -32
  29. package/dist/teams/constants.js.map +1 -1
  30. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  31. package/dist/teams/toAdaptiveCard.js +12 -23
  32. package/dist/teams/toAdaptiveCard.js.map +1 -1
  33. package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
  34. package/dist/teams/toTeamsAttachments.js +5 -9
  35. package/dist/teams/toTeamsAttachments.js.map +1 -1
  36. package/dist/vocabulary/list.js +4 -2
  37. package/dist/vocabulary/list.js.map +1 -1
  38. package/package.json +6 -6
  39. package/src/convert/boundSpec.test.ts +192 -0
  40. package/src/convert/boundSpec.ts +134 -0
  41. package/src/convert/copyBounded.test.ts +131 -0
  42. package/src/convert/copyBounded.ts +38 -0
  43. package/src/convert/isElement.ts +6 -0
  44. package/src/convert/takeRun.test.ts +64 -0
  45. package/src/convert/takeRun.ts +19 -0
  46. package/src/slack/constants.ts +0 -29
  47. package/src/slack/fromSlackBlocks.test.ts +105 -7
  48. package/src/slack/fromSlackBlocks.ts +9 -9
  49. package/src/slack/toSlackBlocks.test.ts +82 -2
  50. package/src/slack/toSlackBlocks.ts +68 -164
  51. package/src/teams/constants.ts +0 -42
  52. package/src/teams/toAdaptiveCard.test.ts +74 -2
  53. package/src/teams/toAdaptiveCard.ts +29 -36
  54. package/src/teams/toTeamsAttachments.ts +4 -17
  55. package/src/vocabulary/list.test.tsx +35 -9
  56. package/src/vocabulary/list.tsx +4 -2
  57. package/dist/teams/boundSpec.d.ts +0 -20
  58. package/dist/teams/boundSpec.d.ts.map +0 -1
  59. package/dist/teams/boundSpec.js +0 -67
  60. package/dist/teams/boundSpec.js.map +0 -1
  61. package/src/teams/boundSpec.ts +0 -102
@@ -0,0 +1,51 @@
1
+ //#region src/convert/boundSpec.d.ts
2
+ /** The maximum traversal depth, counted in visited values, not tree levels. */
3
+ declare const MAX_TRAVERSAL_DEPTH = 64;
4
+ /**
5
+ * The element-nesting ceiling a caller observes. `boundNode` visits an element
6
+ * and its `children` array separately, so one level of nesting spends two of
7
+ * {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never
8
+ * the looser of the two. A root array costs one more level than a root object.
9
+ */
10
+ declare const MAX_ELEMENT_DEPTH: number;
11
+ /**
12
+ * The maximum number of entries kept at any single level (root, or a
13
+ * `children` array at any depth) of the raw spec, before it ever reaches
14
+ * `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass
15
+ * can never be made to walk further than this regardless of what the array
16
+ * claims about itself.
17
+ */
18
+ declare const CHILDREN_CAP = 200;
19
+ /**
20
+ * The total number of nodes {@link boundSpec} will visit across one walk,
21
+ * regardless of how many times a shared reference recurs. Bounds the
22
+ * combinatorial work a DAG of shared or self-referential nodes would
23
+ * otherwise force even though each individual array stays within
24
+ * {@link CHILDREN_CAP}.
25
+ */
26
+ declare const NODE_BUDGET = 5000;
27
+ type ClampReason = "children" | "budget" | "cycle" | "depth";
28
+ /** Renders the warning detail a protocol reports for one {@link ClampReason}. */
29
+ declare function clampReasonDetail(reason: ClampReason): string;
30
+ /**
31
+ * Produces a bounded plain copy of a raw generative-ui spec before it
32
+ * reaches `normalizeSpec`, whose own traversal of the root array or any
33
+ * `children` array walks the full reported length of a hostile proxied
34
+ * array before any per-field cap downstream ever applies. Every array
35
+ * (root, or `children` at any depth) is capped to {@link CHILDREN_CAP}
36
+ * entries by index, which bounds a proxy however it fabricates `length` or
37
+ * replaces its array methods; recursion itself is capped at
38
+ * {@link MAX_TRAVERSAL_DEPTH}. Every object is returned as a plain copy whose
39
+ * `children` came from a single read, because a record that answers
40
+ * `[[HasProperty]]` and `[[Get]]`, or two `[[Get]]`s, differently would
41
+ * otherwise hand the bound one value and `normalizeSpec` another, skipping
42
+ * every bound here for that subtree. `onClamp` fires once per level that was
43
+ * truncated, receiving the reason for that truncation: `"children"`,
44
+ * `"depth"`, `"budget"`, or `"cycle"`. The walk also spends a total budget of
45
+ * {@link NODE_BUDGET} nodes, so shared references cannot multiply work
46
+ * exponentially, and a node that is its own ancestor is cut to `null`.
47
+ */
48
+ declare function boundSpec(spec: unknown, onClamp: (reason: ClampReason) => void): unknown;
49
+ //#endregion
50
+ export { CHILDREN_CAP, ClampReason, MAX_ELEMENT_DEPTH, MAX_TRAVERSAL_DEPTH, NODE_BUDGET, boundSpec, clampReasonDetail };
51
+ //# sourceMappingURL=boundSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundSpec.d.ts","names":[],"sources":["../../src/convert/boundSpec.ts"],"mappings":";;cAGa;;;;;;;cAQA;;;;;;;;cASA;;;;;;;;cASA;KAED;;iBAGI,kBAAkB,QAAQ;;;;;;;;;;;;;;;;;;;iBAwF1B,UACd,eACA,UAAU,QAAQ"}
@@ -0,0 +1,108 @@
1
+ import { copyBounded } from "./copyBounded.js";
2
+ //#region src/convert/boundSpec.ts
3
+ /** The maximum traversal depth, counted in visited values, not tree levels. */
4
+ const MAX_TRAVERSAL_DEPTH = 64;
5
+ /**
6
+ * The element-nesting ceiling a caller observes. `boundNode` visits an element
7
+ * and its `children` array separately, so one level of nesting spends two of
8
+ * {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never
9
+ * the looser of the two. A root array costs one more level than a root object.
10
+ */
11
+ const MAX_ELEMENT_DEPTH = 32;
12
+ /**
13
+ * The maximum number of entries kept at any single level (root, or a
14
+ * `children` array at any depth) of the raw spec, before it ever reaches
15
+ * `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass
16
+ * can never be made to walk further than this regardless of what the array
17
+ * claims about itself.
18
+ */
19
+ const CHILDREN_CAP = 200;
20
+ /**
21
+ * The total number of nodes {@link boundSpec} will visit across one walk,
22
+ * regardless of how many times a shared reference recurs. Bounds the
23
+ * combinatorial work a DAG of shared or self-referential nodes would
24
+ * otherwise force even though each individual array stays within
25
+ * {@link CHILDREN_CAP}.
26
+ */
27
+ const NODE_BUDGET = 5e3;
28
+ /** Renders the warning detail a protocol reports for one {@link ClampReason}. */
29
+ function clampReasonDetail(reason) {
30
+ if (reason === "budget") return `the tree was truncated after ${NODE_BUDGET} nodes.`;
31
+ if (reason === "cycle") return "a self-referencing node was dropped.";
32
+ if (reason === "depth") return `nodes deeper than 32 levels were dropped.`;
33
+ return `children were clamped to 200 entries.`;
34
+ }
35
+ function boundNode(value, depth, onClamp, state, ancestors) {
36
+ if (state.remaining <= 0) {
37
+ if (!state.exhausted) {
38
+ state.exhausted = true;
39
+ onClamp("budget");
40
+ }
41
+ return null;
42
+ }
43
+ state.remaining -= 1;
44
+ if (depth > 64) {
45
+ onClamp("depth");
46
+ return null;
47
+ }
48
+ if (Array.isArray(value)) {
49
+ if (ancestors.has(value)) {
50
+ onClamp("cycle");
51
+ return null;
52
+ }
53
+ ancestors.add(value);
54
+ const { items: bounded, truncated } = copyBounded(value, 200);
55
+ if (truncated) onClamp("children");
56
+ const result = bounded.map((item) => boundNode(item, depth + 1, onClamp, state, ancestors));
57
+ ancestors.delete(value);
58
+ return result;
59
+ }
60
+ if (value !== null && typeof value === "object") {
61
+ const record = value;
62
+ const children = record["children"];
63
+ if (children === void 0) return {
64
+ ...record,
65
+ children: void 0
66
+ };
67
+ if (ancestors.has(value)) {
68
+ onClamp("cycle");
69
+ return null;
70
+ }
71
+ ancestors.add(value);
72
+ const result = {
73
+ ...record,
74
+ children: boundNode(children, depth + 1, onClamp, state, ancestors)
75
+ };
76
+ ancestors.delete(value);
77
+ return result;
78
+ }
79
+ return value;
80
+ }
81
+ /**
82
+ * Produces a bounded plain copy of a raw generative-ui spec before it
83
+ * reaches `normalizeSpec`, whose own traversal of the root array or any
84
+ * `children` array walks the full reported length of a hostile proxied
85
+ * array before any per-field cap downstream ever applies. Every array
86
+ * (root, or `children` at any depth) is capped to {@link CHILDREN_CAP}
87
+ * entries by index, which bounds a proxy however it fabricates `length` or
88
+ * replaces its array methods; recursion itself is capped at
89
+ * {@link MAX_TRAVERSAL_DEPTH}. Every object is returned as a plain copy whose
90
+ * `children` came from a single read, because a record that answers
91
+ * `[[HasProperty]]` and `[[Get]]`, or two `[[Get]]`s, differently would
92
+ * otherwise hand the bound one value and `normalizeSpec` another, skipping
93
+ * every bound here for that subtree. `onClamp` fires once per level that was
94
+ * truncated, receiving the reason for that truncation: `"children"`,
95
+ * `"depth"`, `"budget"`, or `"cycle"`. The walk also spends a total budget of
96
+ * {@link NODE_BUDGET} nodes, so shared references cannot multiply work
97
+ * exponentially, and a node that is its own ancestor is cut to `null`.
98
+ */
99
+ function boundSpec(spec, onClamp) {
100
+ return boundNode(spec, 0, onClamp, {
101
+ remaining: NODE_BUDGET,
102
+ exhausted: false
103
+ }, /* @__PURE__ */ new WeakSet());
104
+ }
105
+ //#endregion
106
+ export { CHILDREN_CAP, MAX_ELEMENT_DEPTH, MAX_TRAVERSAL_DEPTH, NODE_BUDGET, boundSpec, clampReasonDetail };
107
+
108
+ //# sourceMappingURL=boundSpec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundSpec.js","names":[],"sources":["../../src/convert/boundSpec.ts"],"sourcesContent":["import { copyBounded } from \"./copyBounded\";\n\n/** The maximum traversal depth, counted in visited values, not tree levels. */\nexport const MAX_TRAVERSAL_DEPTH = 64;\n\n/**\n * The element-nesting ceiling a caller observes. `boundNode` visits an element\n * and its `children` array separately, so one level of nesting spends two of\n * {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never\n * the looser of the two. A root array costs one more level than a root object.\n */\nexport const MAX_ELEMENT_DEPTH = MAX_TRAVERSAL_DEPTH / 2;\n\n/**\n * The maximum number of entries kept at any single level (root, or a\n * `children` array at any depth) of the raw spec, before it ever reaches\n * `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass\n * can never be made to walk further than this regardless of what the array\n * claims about itself.\n */\nexport const CHILDREN_CAP = 200;\n\n/**\n * The total number of nodes {@link boundSpec} will visit across one walk,\n * regardless of how many times a shared reference recurs. Bounds the\n * combinatorial work a DAG of shared or self-referential nodes would\n * otherwise force even though each individual array stays within\n * {@link CHILDREN_CAP}.\n */\nexport const NODE_BUDGET = 5000;\n\nexport type ClampReason = \"children\" | \"budget\" | \"cycle\" | \"depth\";\n\n/** Renders the warning detail a protocol reports for one {@link ClampReason}. */\nexport function clampReasonDetail(reason: ClampReason): string {\n if (reason === \"budget\") {\n return `the tree was truncated after ${NODE_BUDGET} nodes.`;\n }\n if (reason === \"cycle\") return \"a self-referencing node was dropped.\";\n if (reason === \"depth\") {\n return `nodes deeper than ${MAX_ELEMENT_DEPTH} levels were dropped.`;\n }\n return `children were clamped to ${CHILDREN_CAP} entries.`;\n}\n\ninterface BoundState {\n remaining: number;\n exhausted: boolean;\n}\n\nfunction boundNode(\n value: unknown,\n depth: number,\n onClamp: (reason: ClampReason) => void,\n state: BoundState,\n ancestors: WeakSet<object>,\n): unknown {\n if (state.remaining <= 0) {\n if (!state.exhausted) {\n state.exhausted = true;\n onClamp(\"budget\");\n }\n return null;\n }\n state.remaining -= 1;\n if (depth > MAX_TRAVERSAL_DEPTH) {\n onClamp(\"depth\");\n return null;\n }\n if (Array.isArray(value)) {\n if (ancestors.has(value)) {\n onClamp(\"cycle\");\n return null;\n }\n ancestors.add(value);\n const { items: bounded, truncated } = copyBounded(value, CHILDREN_CAP);\n if (truncated) onClamp(\"children\");\n const result = bounded.map((item) =>\n boundNode(item, depth + 1, onClamp, state, ancestors),\n );\n ancestors.delete(value);\n return result;\n }\n if (value !== null && typeof value === \"object\") {\n const record = value as Record<string, unknown>;\n const children = record[\"children\"];\n // The spread performs a `children` read of its own, so the copy states the\n // bounded value even when there is none.\n if (children === undefined) return { ...record, children: undefined };\n if (ancestors.has(value)) {\n onClamp(\"cycle\");\n return null;\n }\n ancestors.add(value);\n const result = {\n ...record,\n children: boundNode(children, depth + 1, onClamp, state, ancestors),\n };\n ancestors.delete(value);\n return result;\n }\n return value;\n}\n\n/**\n * Produces a bounded plain copy of a raw generative-ui spec before it\n * reaches `normalizeSpec`, whose own traversal of the root array or any\n * `children` array walks the full reported length of a hostile proxied\n * array before any per-field cap downstream ever applies. Every array\n * (root, or `children` at any depth) is capped to {@link CHILDREN_CAP}\n * entries by index, which bounds a proxy however it fabricates `length` or\n * replaces its array methods; recursion itself is capped at\n * {@link MAX_TRAVERSAL_DEPTH}. Every object is returned as a plain copy whose\n * `children` came from a single read, because a record that answers\n * `[[HasProperty]]` and `[[Get]]`, or two `[[Get]]`s, differently would\n * otherwise hand the bound one value and `normalizeSpec` another, skipping\n * every bound here for that subtree. `onClamp` fires once per level that was\n * truncated, receiving the reason for that truncation: `\"children\"`,\n * `\"depth\"`, `\"budget\"`, or `\"cycle\"`. The walk also spends a total budget of\n * {@link NODE_BUDGET} nodes, so shared references cannot multiply work\n * exponentially, and a node that is its own ancestor is cut to `null`.\n */\nexport function boundSpec(\n spec: unknown,\n onClamp: (reason: ClampReason) => void,\n): unknown {\n return boundNode(\n spec,\n 0,\n onClamp,\n { remaining: NODE_BUDGET, exhausted: false },\n new WeakSet(),\n );\n}\n"],"mappings":";;;AAGA,MAAa,sBAAsB;;;;;;;AAQnC,MAAa,oBAAA;;;;;;;;AASb,MAAa,eAAe;;;;;;;;AAS5B,MAAa,cAAc;;AAK3B,SAAgB,kBAAkB,QAA6B;CAC7D,IAAI,WAAW,UACb,OAAO,gCAAgC,YAAY;CAErD,IAAI,WAAW,SAAS,OAAO;CAC/B,IAAI,WAAW,SACb,OAAO;CAET,OAAO;AACT;AAOA,SAAS,UACP,OACA,OACA,SACA,OACA,WACS;CACT,IAAI,MAAM,aAAa,GAAG;EACxB,IAAI,CAAC,MAAM,WAAW;GACpB,MAAM,YAAY;GAClB,QAAQ,QAAQ;EAClB;EACA,OAAO;CACT;CACA,MAAM,aAAa;CACnB,IAAI,QAAA,IAA6B;EAC/B,QAAQ,OAAO;EACf,OAAO;CACT;CACA,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,IAAI,UAAU,IAAI,KAAK,GAAG;GACxB,QAAQ,OAAO;GACf,OAAO;EACT;EACA,UAAU,IAAI,KAAK;EACnB,MAAM,EAAE,OAAO,SAAS,cAAc,YAAY,OAAA,GAAmB;EACrE,IAAI,WAAW,QAAQ,UAAU;EACjC,MAAM,SAAS,QAAQ,KAAK,SAC1B,UAAU,MAAM,QAAQ,GAAG,SAAS,OAAO,SAAS,CACtD;EACA,UAAU,OAAO,KAAK;EACtB,OAAO;CACT;CACA,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;EAC/C,MAAM,SAAS;EACf,MAAM,WAAW,OAAO;EAGxB,IAAI,aAAa,KAAA,GAAW,OAAO;GAAE,GAAG;GAAQ,UAAU,KAAA;EAAU;EACpE,IAAI,UAAU,IAAI,KAAK,GAAG;GACxB,QAAQ,OAAO;GACf,OAAO;EACT;EACA,UAAU,IAAI,KAAK;EACnB,MAAM,SAAS;GACb,GAAG;GACH,UAAU,UAAU,UAAU,QAAQ,GAAG,SAAS,OAAO,SAAS;EACpE;EACA,UAAU,OAAO,KAAK;EACtB,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,UACd,MACA,SACS;CACT,OAAO,UACL,MACA,GACA,SACA;EAAE,WAAW;EAAa,WAAW;CAAM,mBAC3C,IAAI,QAAQ,CACd;AACF"}
@@ -0,0 +1,28 @@
1
+ //#region src/convert/copyBounded.d.ts
2
+ /**
3
+ * Copies at most `cap` entries out of `value` by index.
4
+ *
5
+ * Every array method dispatches on the input: a replaced `slice` is called
6
+ * directly, and the intrinsic `Array.prototype.slice.call` still reaches a
7
+ * `constructor` with a `@@species` whose `[[Construct]]` returns an arbitrary
8
+ * object, which then supplies the `map` or `some` the caller runs next. Reading
9
+ * indices consults neither, so it is the only form that bounds a hostile array.
10
+ *
11
+ * Absent indices stay absent, matching what `slice` produces for a sparse
12
+ * input, so callers that skip holes still skip them. `truncated` comes from the
13
+ * same `length` read as the bound, so an input cannot report one figure to the
14
+ * cap and another to the caller's warning.
15
+ *
16
+ * A reported `length` is normalized the way `ToLength` does before it reaches
17
+ * `count`, because a fractional one would otherwise reach `items.length` and
18
+ * throw `RangeError` where `slice` returns a bounded result. `truncated` reads
19
+ * that same normalized figure, so a `length` whose `valueOf` answers differently
20
+ * on a second conversion cannot separate the warning from the bound.
21
+ */
22
+ declare const copyBounded: <T>(value: readonly T[], cap: number) => {
23
+ readonly items: T[];
24
+ readonly truncated: boolean;
25
+ };
26
+ //#endregion
27
+ export { copyBounded };
28
+ //# sourceMappingURL=copyBounded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copyBounded.d.ts","names":[],"sources":["../../src/convert/copyBounded.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;cAoBa,cAAe,GAC1B,gBAAgB,KAChB;WACY,OAAO;WAAc"}
@@ -0,0 +1,38 @@
1
+ //#region src/convert/copyBounded.ts
2
+ /**
3
+ * Copies at most `cap` entries out of `value` by index.
4
+ *
5
+ * Every array method dispatches on the input: a replaced `slice` is called
6
+ * directly, and the intrinsic `Array.prototype.slice.call` still reaches a
7
+ * `constructor` with a `@@species` whose `[[Construct]]` returns an arbitrary
8
+ * object, which then supplies the `map` or `some` the caller runs next. Reading
9
+ * indices consults neither, so it is the only form that bounds a hostile array.
10
+ *
11
+ * Absent indices stay absent, matching what `slice` produces for a sparse
12
+ * input, so callers that skip holes still skip them. `truncated` comes from the
13
+ * same `length` read as the bound, so an input cannot report one figure to the
14
+ * cap and another to the caller's warning.
15
+ *
16
+ * A reported `length` is normalized the way `ToLength` does before it reaches
17
+ * `count`, because a fractional one would otherwise reach `items.length` and
18
+ * throw `RangeError` where `slice` returns a bounded result. `truncated` reads
19
+ * that same normalized figure, so a `length` whose `valueOf` answers differently
20
+ * on a second conversion cannot separate the warning from the bound.
21
+ */
22
+ const copyBounded = (value, cap) => {
23
+ const reported = value.length;
24
+ const integral = Math.trunc(Number(reported));
25
+ const usable = Number.isNaN(integral) || integral <= 0 ? 0 : Math.min(integral, Number.MAX_SAFE_INTEGER);
26
+ const count = Math.min(cap, usable);
27
+ const items = [];
28
+ for (let index = 0; index < count; index += 1) if (index in value) items[index] = value[index];
29
+ items.length = count;
30
+ return {
31
+ items,
32
+ truncated: usable > cap
33
+ };
34
+ };
35
+ //#endregion
36
+ export { copyBounded };
37
+
38
+ //# sourceMappingURL=copyBounded.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copyBounded.js","names":[],"sources":["../../src/convert/copyBounded.ts"],"sourcesContent":["/**\n * Copies at most `cap` entries out of `value` by index.\n *\n * Every array method dispatches on the input: a replaced `slice` is called\n * directly, and the intrinsic `Array.prototype.slice.call` still reaches a\n * `constructor` with a `@@species` whose `[[Construct]]` returns an arbitrary\n * object, which then supplies the `map` or `some` the caller runs next. Reading\n * indices consults neither, so it is the only form that bounds a hostile array.\n *\n * Absent indices stay absent, matching what `slice` produces for a sparse\n * input, so callers that skip holes still skip them. `truncated` comes from the\n * same `length` read as the bound, so an input cannot report one figure to the\n * cap and another to the caller's warning.\n *\n * A reported `length` is normalized the way `ToLength` does before it reaches\n * `count`, because a fractional one would otherwise reach `items.length` and\n * throw `RangeError` where `slice` returns a bounded result. `truncated` reads\n * that same normalized figure, so a `length` whose `valueOf` answers differently\n * on a second conversion cannot separate the warning from the bound.\n */\nexport const copyBounded = <T>(\n value: readonly T[],\n cap: number,\n): { readonly items: T[]; readonly truncated: boolean } => {\n const reported = value.length;\n const integral = Math.trunc(Number(reported));\n const usable =\n Number.isNaN(integral) || integral <= 0\n ? 0\n : Math.min(integral, Number.MAX_SAFE_INTEGER);\n const count = Math.min(cap, usable);\n const items: T[] = [];\n for (let index = 0; index < count; index += 1) {\n if (index in value) items[index] = value[index] as T;\n }\n items.length = count;\n return { items, truncated: usable > cap };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,eACX,OACA,QACyD;CACzD,MAAM,WAAW,MAAM;CACvB,MAAM,WAAW,KAAK,MAAM,OAAO,QAAQ,CAAC;CAC5C,MAAM,SACJ,OAAO,MAAM,QAAQ,KAAK,YAAY,IAClC,IACA,KAAK,IAAI,UAAU,OAAO,gBAAgB;CAChD,MAAM,QAAQ,KAAK,IAAI,KAAK,MAAM;CAClC,MAAM,QAAa,CAAC;CACpB,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAC1C,IAAI,SAAS,OAAO,MAAM,SAAS,MAAM;CAE3C,MAAM,SAAS;CACf,OAAO;EAAE;EAAO,WAAW,SAAS;CAAI;AAC1C"}
@@ -0,0 +1,6 @@
1
+ import { NormalizedUIElement, NormalizedUINode } from "../ir.js";
2
+ //#region src/convert/isElement.d.ts
3
+ declare const isElement: (node: NormalizedUINode) => node is NormalizedUIElement;
4
+ //#endregion
5
+ export { isElement };
6
+ //# sourceMappingURL=isElement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isElement.d.ts","names":[],"sources":["../../src/convert/isElement.ts"],"mappings":";;cAEa,YACX,MAAM,qBACL,QAAQ"}
@@ -0,0 +1,6 @@
1
+ //#region src/convert/isElement.ts
2
+ const isElement = (node) => typeof node === "object" && node !== null && !Array.isArray(node);
3
+ //#endregion
4
+ export { isElement };
5
+
6
+ //# sourceMappingURL=isElement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isElement.js","names":[],"sources":["../../src/convert/isElement.ts"],"sourcesContent":["import type { NormalizedUIElement, NormalizedUINode } from \"../ir\";\n\nexport const isElement = (\n node: NormalizedUINode,\n): node is NormalizedUIElement =>\n typeof node === \"object\" && node !== null && !Array.isArray(node);\n"],"mappings":";AAEA,MAAa,aACX,SAEA,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI"}
@@ -0,0 +1,9 @@
1
+ import { NormalizedUIElement, NormalizedUINode } from "../ir.js";
2
+ //#region src/convert/takeRun.d.ts
3
+ declare function takeRun(nodes: readonly (NormalizedUINode | undefined)[], index: number, matches: (element: NormalizedUIElement) => boolean): {
4
+ run: NormalizedUIElement[];
5
+ next: number;
6
+ };
7
+ //#endregion
8
+ export { takeRun };
9
+ //# sourceMappingURL=takeRun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"takeRun.d.ts","names":[],"sources":["../../src/convert/takeRun.ts"],"mappings":";;iBAGgB,QACd,iBAAiB,iCACjB,eACA,UAAU,SAAS;EAChB,KAAK;EAAuB"}
@@ -0,0 +1,20 @@
1
+ import { isElement } from "./isElement.js";
2
+ //#region src/convert/takeRun.ts
3
+ function takeRun(nodes, index, matches) {
4
+ const run = [];
5
+ let next = index;
6
+ while (next < nodes.length) {
7
+ const candidate = nodes[next];
8
+ if (candidate === void 0 || !isElement(candidate) || !matches(candidate)) break;
9
+ run.push(candidate);
10
+ next += 1;
11
+ }
12
+ return {
13
+ run,
14
+ next
15
+ };
16
+ }
17
+ //#endregion
18
+ export { takeRun };
19
+
20
+ //# sourceMappingURL=takeRun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"takeRun.js","names":[],"sources":["../../src/convert/takeRun.ts"],"sourcesContent":["import type { NormalizedUIElement, NormalizedUINode } from \"../ir\";\nimport { isElement } from \"./isElement\";\n\nexport function takeRun(\n nodes: readonly (NormalizedUINode | undefined)[],\n index: number,\n matches: (element: NormalizedUIElement) => boolean,\n): { run: NormalizedUIElement[]; next: number } {\n const run: NormalizedUIElement[] = [];\n let next = index;\n while (next < nodes.length) {\n const candidate = nodes[next];\n if (candidate === undefined || !isElement(candidate) || !matches(candidate))\n break;\n run.push(candidate);\n next += 1;\n }\n return { run, next };\n}\n"],"mappings":";;AAGA,SAAgB,QACd,OACA,OACA,SAC8C;CAC9C,MAAM,MAA6B,CAAC;CACpC,IAAI,OAAO;CACX,OAAO,OAAO,MAAM,QAAQ;EAC1B,MAAM,YAAY,MAAM;EACxB,IAAI,cAAc,KAAA,KAAa,CAAC,UAAU,SAAS,KAAK,CAAC,QAAQ,SAAS,GACxE;EACF,IAAI,KAAK,SAAS;EAClB,QAAQ;CACV;CACA,OAAO;EAAE;EAAK;CAAK;AACrB"}
@@ -1,30 +1,5 @@
1
1
  import { SlackAlertBlock, SlackButtonElement, SlackCardBlock, SlackCarouselBlock, SlackContextBlock, SlackDataTableBlock, SlackDataTableCell, SlackImageBlock, SlackSectionBlock, SlackTextObject } from "./types.js";
2
2
  //#region src/slack/constants.d.ts
3
- /** The maximum traversal depth, counted in visited values, not tree levels. */
4
- declare const MAX_TRAVERSAL_DEPTH = 64;
5
- /**
6
- * The element-nesting ceiling a caller observes. `boundNode` visits an element
7
- * and its `children` array separately, so one level of nesting spends two of
8
- * {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never
9
- * the looser of the two. A root array costs one more level than a root object.
10
- */
11
- declare const MAX_ELEMENT_DEPTH: number;
12
- /**
13
- * The maximum number of entries kept at any single level (root, or a
14
- * `children` array at any depth) of the raw spec, before it ever reaches
15
- * `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass
16
- * can never be made to walk further than this regardless of what the array
17
- * claims about itself.
18
- */
19
- declare const CHILDREN_CAP = 200;
20
- /**
21
- * The total number of nodes the pre-normalization bounding walk will visit
22
- * across one call, regardless of how many times a shared reference recurs.
23
- * Bounds the combinatorial work a DAG of shared or self-referential nodes
24
- * would otherwise force even though each individual array stays within
25
- * {@link CHILDREN_CAP}.
26
- */
27
- declare const NODE_BUDGET = 5000;
28
3
  /** The block limit for a Slack message. */
29
4
  declare const MESSAGE_BLOCK_CAP = 50;
30
5
  /** The block limit for a Slack modal. */
@@ -114,5 +89,5 @@ declare function buildDataTableBlock(rows: readonly (readonly SlackDataTableCell
114
89
  /** Builds the final note used when a surface block budget is exceeded. */
115
90
  declare function buildBlockBudgetNote(omitted: number): SlackContextBlock;
116
91
  //#endregion
117
- export { ACTIONS_ELEMENT_CAP, ACTION_ID_CAP, ALERT_TEXT_CAP, BUTTON_VALUE_CAP, CARD_ACTIONS_CAP, CARD_BODY_CAP, CARD_SUBTEXT_CAP, CARD_TITLE_CAP, CAROUSEL_CARD_CAP, CAROUSEL_CARD_MIN, CHILDREN_CAP, CONTEXT_ELEMENT_CAP, CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, DATA_TABLE_COLUMN_CAP, DATA_TABLE_ROW_CAP, FACT_FIELD_CAP, FACT_FIELD_TEXT_CAP, HEADER_TEXT_CAP, INBOUND_BLOCK_CAP, INPUT_LABEL_CAP, INTERACTIVE_TEXT_CAP, MARKDOWN_TEXT_BUDGET, MAX_ELEMENT_DEPTH, MAX_TRAVERSAL_DEPTH, MESSAGE_BLOCK_CAP, MODAL_BLOCK_CAP, NODE_BUDGET, PLACEHOLDER_TEXT_CAP, RADIO_OPTION_CAP, SECTION_TEXT_CAP, SELECT_OPTION_CAP, TABLE_CAPTION, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock };
92
+ export { ACTIONS_ELEMENT_CAP, ACTION_ID_CAP, ALERT_TEXT_CAP, BUTTON_VALUE_CAP, CARD_ACTIONS_CAP, CARD_BODY_CAP, CARD_SUBTEXT_CAP, CARD_TITLE_CAP, CAROUSEL_CARD_CAP, CAROUSEL_CARD_MIN, CONTEXT_ELEMENT_CAP, CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, DATA_TABLE_COLUMN_CAP, DATA_TABLE_ROW_CAP, FACT_FIELD_CAP, FACT_FIELD_TEXT_CAP, HEADER_TEXT_CAP, INBOUND_BLOCK_CAP, INPUT_LABEL_CAP, INTERACTIVE_TEXT_CAP, MARKDOWN_TEXT_BUDGET, MESSAGE_BLOCK_CAP, MODAL_BLOCK_CAP, PLACEHOLDER_TEXT_CAP, RADIO_OPTION_CAP, SECTION_TEXT_CAP, SELECT_OPTION_CAP, TABLE_CAPTION, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock };
118
93
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","names":[],"sources":["../../src/slack/constants.ts"],"mappings":";;;cAca;;;;;;;cAQA;;;;;;;;cASA;;;;;;;;cASA;;cAGA;;cAGA;;;;;;cAOA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;;;;cAMA;;cAGA;;;;;cAMA;;cAGA;;cAGA;;iBAGG,mBACd,eACA,qBACA,4DACW,mBAAmB;;iBAmBhB,gBACd,cACA,kDACC;KAKE;WACM,YAAY;WACZ,QAAQ;WACR,WAAW;WACX,OAAO;WACP,UAAU;WACV,mBAAmB;;;iBAId,eAAe,QAAQ,kBAAkB;;iBAczC,mBACd,mBAAmB,mBAClB;;iBAKa,oBACd,yBAAyB,0BACxB;;iBAKa,qBAAqB,kBAAkB"}
1
+ {"version":3,"file":"constants.d.ts","names":[],"sources":["../../src/slack/constants.ts"],"mappings":";;;cAca;;cAGA;;;;;;cAOA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;cAGA;;;;;cAMA;;cAGA;;;;;cAMA;;cAGA;;cAGA;;iBAGG,mBACd,eACA,qBACA,4DACW,mBAAmB;;iBAmBhB,gBACd,cACA,kDACC;KAKE;WACM,YAAY;WACZ,QAAQ;WACR,WAAW;WACX,OAAO;WACP,UAAU;WACV,mBAAmB;;;iBAId,eAAe,QAAQ,kBAAkB;;iBAczC,mBACd,mBAAmB,mBAClB;;iBAKa,oBACd,yBAAyB,0BACxB;;iBAKa,qBAAqB,kBAAkB"}
@@ -1,29 +1,4 @@
1
1
  //#region src/slack/constants.ts
2
- /** The maximum traversal depth, counted in visited values, not tree levels. */
3
- const MAX_TRAVERSAL_DEPTH = 64;
4
- /**
5
- * The element-nesting ceiling a caller observes. `boundNode` visits an element
6
- * and its `children` array separately, so one level of nesting spends two of
7
- * {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never
8
- * the looser of the two. A root array costs one more level than a root object.
9
- */
10
- const MAX_ELEMENT_DEPTH = 32;
11
- /**
12
- * The maximum number of entries kept at any single level (root, or a
13
- * `children` array at any depth) of the raw spec, before it ever reaches
14
- * `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass
15
- * can never be made to walk further than this regardless of what the array
16
- * claims about itself.
17
- */
18
- const CHILDREN_CAP = 200;
19
- /**
20
- * The total number of nodes the pre-normalization bounding walk will visit
21
- * across one call, regardless of how many times a shared reference recurs.
22
- * Bounds the combinatorial work a DAG of shared or self-referential nodes
23
- * would otherwise force even though each individual array stays within
24
- * {@link CHILDREN_CAP}.
25
- */
26
- const NODE_BUDGET = 5e3;
27
2
  /** The block limit for a Slack message. */
28
3
  const MESSAGE_BLOCK_CAP = 50;
29
4
  /** The block limit for a Slack modal. */
@@ -164,6 +139,6 @@ function buildBlockBudgetNote(omitted) {
164
139
  };
165
140
  }
166
141
  //#endregion
167
- export { ACTIONS_ELEMENT_CAP, ACTION_ID_CAP, ALERT_TEXT_CAP, BUTTON_VALUE_CAP, CARD_ACTIONS_CAP, CARD_BODY_CAP, CARD_SUBTEXT_CAP, CARD_TITLE_CAP, CAROUSEL_CARD_CAP, CAROUSEL_CARD_MIN, CHILDREN_CAP, CONTEXT_ELEMENT_CAP, CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, DATA_TABLE_COLUMN_CAP, DATA_TABLE_ROW_CAP, FACT_FIELD_CAP, FACT_FIELD_TEXT_CAP, HEADER_TEXT_CAP, INBOUND_BLOCK_CAP, INPUT_LABEL_CAP, INTERACTIVE_TEXT_CAP, MARKDOWN_TEXT_BUDGET, MAX_ELEMENT_DEPTH, MAX_TRAVERSAL_DEPTH, MESSAGE_BLOCK_CAP, MODAL_BLOCK_CAP, NODE_BUDGET, PLACEHOLDER_TEXT_CAP, RADIO_OPTION_CAP, SECTION_TEXT_CAP, SELECT_OPTION_CAP, TABLE_CAPTION, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock };
142
+ export { ACTIONS_ELEMENT_CAP, ACTION_ID_CAP, ALERT_TEXT_CAP, BUTTON_VALUE_CAP, CARD_ACTIONS_CAP, CARD_BODY_CAP, CARD_SUBTEXT_CAP, CARD_TITLE_CAP, CAROUSEL_CARD_CAP, CAROUSEL_CARD_MIN, CONTEXT_ELEMENT_CAP, CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, DATA_TABLE_COLUMN_CAP, DATA_TABLE_ROW_CAP, FACT_FIELD_CAP, FACT_FIELD_TEXT_CAP, HEADER_TEXT_CAP, INBOUND_BLOCK_CAP, INPUT_LABEL_CAP, INTERACTIVE_TEXT_CAP, MARKDOWN_TEXT_BUDGET, MESSAGE_BLOCK_CAP, MODAL_BLOCK_CAP, PLACEHOLDER_TEXT_CAP, RADIO_OPTION_CAP, SECTION_TEXT_CAP, SELECT_OPTION_CAP, TABLE_CAPTION, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock };
168
143
 
169
144
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../src/slack/constants.ts"],"sourcesContent":["import type {\n SlackAlertBlock,\n SlackButtonElement,\n SlackCardBlock,\n SlackCarouselBlock,\n SlackContextBlock,\n SlackDataTableBlock,\n SlackDataTableCell,\n SlackImageBlock,\n SlackSectionBlock,\n SlackTextObject,\n} from \"./types\";\n\n/** The maximum traversal depth, counted in visited values, not tree levels. */\nexport const MAX_TRAVERSAL_DEPTH = 64;\n\n/**\n * The element-nesting ceiling a caller observes. `boundNode` visits an element\n * and its `children` array separately, so one level of nesting spends two of\n * {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never\n * the looser of the two. A root array costs one more level than a root object.\n */\nexport const MAX_ELEMENT_DEPTH = MAX_TRAVERSAL_DEPTH / 2;\n\n/**\n * The maximum number of entries kept at any single level (root, or a\n * `children` array at any depth) of the raw spec, before it ever reaches\n * `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass\n * can never be made to walk further than this regardless of what the array\n * claims about itself.\n */\nexport const CHILDREN_CAP = 200;\n\n/**\n * The total number of nodes the pre-normalization bounding walk will visit\n * across one call, regardless of how many times a shared reference recurs.\n * Bounds the combinatorial work a DAG of shared or self-referential nodes\n * would otherwise force even though each individual array stays within\n * {@link CHILDREN_CAP}.\n */\nexport const NODE_BUDGET = 5000;\n\n/** The block limit for a Slack message. */\nexport const MESSAGE_BLOCK_CAP = 50;\n\n/** The block limit for a Slack modal. */\nexport const MODAL_BLOCK_CAP = 100;\n\n/**\n * The maximum number of top-level blocks accepted when parsing Slack Block\n * Kit JSON back into generative UI, guarding against a hostile or malformed\n * payload.\n */\nexport const INBOUND_BLOCK_CAP = 200;\n\n/** The character limit for header text. */\nexport const HEADER_TEXT_CAP = 150;\n\n/** The character limit for section text. */\nexport const SECTION_TEXT_CAP = 3000;\n\n/** The maximum number of fields in one section. */\nexport const FACT_FIELD_CAP = 10;\n\n/** The character limit for one section field. */\nexport const FACT_FIELD_TEXT_CAP = 2000;\n\n/** The character limit for button labels and option labels. */\nexport const INTERACTIVE_TEXT_CAP = 75;\n\n/** The character limit for an action_id. */\nexport const ACTION_ID_CAP = 255;\n\n/** The character limit for a button's serialized value payload. */\nexport const BUTTON_VALUE_CAP = 2000;\n\n/** The maximum number of elements in one actions block. */\nexport const ACTIONS_ELEMENT_CAP = 25;\n\n/** The maximum number of options in a static select. */\nexport const SELECT_OPTION_CAP = 100;\n\n/** The maximum number of options in a radio-button group. */\nexport const RADIO_OPTION_CAP = 10;\n\n/** The character limit for a select or input placeholder. */\nexport const PLACEHOLDER_TEXT_CAP = 150;\n\n/** The character limit for an input's label. */\nexport const INPUT_LABEL_CAP = 2000;\n\n/** The maximum number of elements in one context block. */\nexport const CONTEXT_ELEMENT_CAP = 10;\n\n/** The character limit for one context element's text. */\nexport const CONTEXT_TEXT_CAP = 3000;\n\n/** The character limit for card titles and subtitles. */\nexport const CARD_TITLE_CAP = 150;\n\n/** The character limit for a card's body text. */\nexport const CARD_BODY_CAP = 200;\n\n/** The character limit for a card's subtext. */\nexport const CARD_SUBTEXT_CAP = 200;\n\n/** The maximum number of buttons in a card's actions. */\nexport const CARD_ACTIONS_CAP = 3;\n\n/** The minimum number of elements in a renderable carousel. */\nexport const CAROUSEL_CARD_MIN = 1;\n\n/** The maximum number of elements in a carousel. */\nexport const CAROUSEL_CARD_CAP = 10;\n\n/** The character limit for an alert's text. */\nexport const ALERT_TEXT_CAP = 200;\n\n/**\n * The maximum number of data rows in a data-table block; the emitted `rows`\n * array additionally carries one header row.\n */\nexport const DATA_TABLE_ROW_CAP = 200;\n\n/** The maximum number of columns in a data-table block. */\nexport const DATA_TABLE_COLUMN_CAP = 20;\n\n/**\n * The cumulative character limit (summed across every cell's text) shared by\n * every data-table block in one payload.\n */\nexport const DATA_TABLE_CHAR_BUDGET = 20000;\n\n/** The caption emitted on every native data-table block. */\nexport const TABLE_CAPTION = \"Table\";\n\n/** The cumulative character limit for native markdown blocks. */\nexport const MARKDOWN_TEXT_BUDGET = 12000;\n\n/** Builds the message-surface fallback for an alert. */\nexport function buildAlertFallback(\n title: string,\n description: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): readonly [SlackContextBlock, SlackSectionBlock] {\n const emoji = {\n info: \"ℹ️\",\n success: \"✅\",\n warning: \"⚠️\",\n danger: \"🛑\",\n }[tone];\n return [\n {\n type: \"context\",\n elements: [\n { type: \"mrkdwn\", text: title ? `${emoji} *${title}*` : emoji },\n ],\n },\n { type: \"section\", text: { type: \"mrkdwn\", text: description } },\n ];\n}\n\n/** Builds a 2026 Slack modal-only alert block, mapping `tone` to its `level`. */\nexport function buildAlertBlock(\n text: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): SlackAlertBlock {\n const level = tone === \"danger\" ? \"error\" : tone;\n return { type: \"alert\", text: { type: \"mrkdwn\", text }, level };\n}\n\ntype CardBlockFields = {\n readonly heroImage?: SlackImageBlock;\n readonly title?: SlackTextObject;\n readonly subtitle?: SlackTextObject;\n readonly body?: SlackTextObject;\n readonly subtext?: SlackTextObject;\n readonly actions?: readonly SlackButtonElement[];\n};\n\n/** Builds a 2026 Slack card block from its already-clamped fields. */\nexport function buildCardBlock(fields: CardBlockFields): SlackCardBlock {\n const { heroImage, title, subtitle, body, subtext, actions } = fields;\n return {\n type: \"card\",\n ...(heroImage !== undefined ? { hero_image: heroImage } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(subtitle !== undefined ? { subtitle } : {}),\n ...(body !== undefined ? { body } : {}),\n ...(subtext !== undefined ? { subtext } : {}),\n ...(actions !== undefined ? { actions } : {}),\n };\n}\n\n/** Builds a 2026 Slack carousel block. */\nexport function buildCarouselBlock(\n elements: readonly SlackCardBlock[],\n): SlackCarouselBlock {\n return { type: \"carousel\", elements };\n}\n\n/** Builds a 2026 Slack data-table block; `rows[0]` must be the header row. */\nexport function buildDataTableBlock(\n rows: readonly (readonly SlackDataTableCell[])[],\n): SlackDataTableBlock {\n return { type: \"data_table\", caption: TABLE_CAPTION, rows };\n}\n\n/** Builds the final note used when a surface block budget is exceeded. */\nexport function buildBlockBudgetNote(omitted: number): SlackContextBlock {\n return {\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: `${omitted} ${omitted === 1 ? \"block was\" : \"blocks were\"} omitted.`,\n },\n ],\n };\n}\n"],"mappings":";;AAcA,MAAa,sBAAsB;;;;;;;AAQnC,MAAa,oBAAA;;;;;;;;AASb,MAAa,eAAe;;;;;;;;AAS5B,MAAa,cAAc;;AAG3B,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;;;;;AAO/B,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;AAG/B,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,sBAAsB;;AAGnC,MAAa,oBAAoB;;AAGjC,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB;;AAGpC,MAAa,kBAAkB;;AAG/B,MAAa,sBAAsB;;AAGnC,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,mBAAmB;;AAGhC,MAAa,oBAAoB;;AAGjC,MAAa,oBAAoB;;AAGjC,MAAa,iBAAiB;;;;;AAM9B,MAAa,qBAAqB;;AAGlC,MAAa,wBAAwB;;;;;AAMrC,MAAa,yBAAyB;;AAGtC,MAAa,gBAAgB;;AAG7B,MAAa,uBAAuB;;AAGpC,SAAgB,mBACd,OACA,aACA,MACiD;CACjD,MAAM,QAAQ;EACZ,MAAM;EACN,SAAS;EACT,SAAS;EACT,QAAQ;CACV,EAAE;CACF,OAAO,CACL;EACE,MAAM;EACN,UAAU,CACR;GAAE,MAAM;GAAU,MAAM,QAAQ,GAAG,MAAM,IAAI,MAAM,KAAK;EAAM,CAChE;CACF,GACA;EAAE,MAAM;EAAW,MAAM;GAAE,MAAM;GAAU,MAAM;EAAY;CAAE,CACjE;AACF;;AAGA,SAAgB,gBACd,MACA,MACiB;CAEjB,OAAO;EAAE,MAAM;EAAS,MAAM;GAAE,MAAM;GAAU;EAAK;EAAG,OAD1C,SAAS,WAAW,UAAU;CACkB;AAChE;;AAYA,SAAgB,eAAe,QAAyC;CACtE,MAAM,EAAE,WAAW,OAAO,UAAU,MAAM,SAAS,YAAY;CAC/D,OAAO;EACL,MAAM;EACN,GAAI,cAAc,KAAA,IAAY,EAAE,YAAY,UAAU,IAAI,CAAC;EAC3D,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACvC,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;EACrC,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;EAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;CAC7C;AACF;;AAGA,SAAgB,mBACd,UACoB;CACpB,OAAO;EAAE,MAAM;EAAY;CAAS;AACtC;;AAGA,SAAgB,oBACd,MACqB;CACrB,OAAO;EAAE,MAAM;EAAc,SAAS;EAAe;CAAK;AAC5D;;AAGA,SAAgB,qBAAqB,SAAoC;CACvE,OAAO;EACL,MAAM;EACN,UAAU,CACR;GACE,MAAM;GACN,MAAM,GAAG,QAAQ,GAAG,YAAY,IAAI,cAAc,cAAc;EAClE,CACF;CACF;AACF"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../src/slack/constants.ts"],"sourcesContent":["import type {\n SlackAlertBlock,\n SlackButtonElement,\n SlackCardBlock,\n SlackCarouselBlock,\n SlackContextBlock,\n SlackDataTableBlock,\n SlackDataTableCell,\n SlackImageBlock,\n SlackSectionBlock,\n SlackTextObject,\n} from \"./types\";\n\n/** The block limit for a Slack message. */\nexport const MESSAGE_BLOCK_CAP = 50;\n\n/** The block limit for a Slack modal. */\nexport const MODAL_BLOCK_CAP = 100;\n\n/**\n * The maximum number of top-level blocks accepted when parsing Slack Block\n * Kit JSON back into generative UI, guarding against a hostile or malformed\n * payload.\n */\nexport const INBOUND_BLOCK_CAP = 200;\n\n/** The character limit for header text. */\nexport const HEADER_TEXT_CAP = 150;\n\n/** The character limit for section text. */\nexport const SECTION_TEXT_CAP = 3000;\n\n/** The maximum number of fields in one section. */\nexport const FACT_FIELD_CAP = 10;\n\n/** The character limit for one section field. */\nexport const FACT_FIELD_TEXT_CAP = 2000;\n\n/** The character limit for button labels and option labels. */\nexport const INTERACTIVE_TEXT_CAP = 75;\n\n/** The character limit for an action_id. */\nexport const ACTION_ID_CAP = 255;\n\n/** The character limit for a button's serialized value payload. */\nexport const BUTTON_VALUE_CAP = 2000;\n\n/** The maximum number of elements in one actions block. */\nexport const ACTIONS_ELEMENT_CAP = 25;\n\n/** The maximum number of options in a static select. */\nexport const SELECT_OPTION_CAP = 100;\n\n/** The maximum number of options in a radio-button group. */\nexport const RADIO_OPTION_CAP = 10;\n\n/** The character limit for a select or input placeholder. */\nexport const PLACEHOLDER_TEXT_CAP = 150;\n\n/** The character limit for an input's label. */\nexport const INPUT_LABEL_CAP = 2000;\n\n/** The maximum number of elements in one context block. */\nexport const CONTEXT_ELEMENT_CAP = 10;\n\n/** The character limit for one context element's text. */\nexport const CONTEXT_TEXT_CAP = 3000;\n\n/** The character limit for card titles and subtitles. */\nexport const CARD_TITLE_CAP = 150;\n\n/** The character limit for a card's body text. */\nexport const CARD_BODY_CAP = 200;\n\n/** The character limit for a card's subtext. */\nexport const CARD_SUBTEXT_CAP = 200;\n\n/** The maximum number of buttons in a card's actions. */\nexport const CARD_ACTIONS_CAP = 3;\n\n/** The minimum number of elements in a renderable carousel. */\nexport const CAROUSEL_CARD_MIN = 1;\n\n/** The maximum number of elements in a carousel. */\nexport const CAROUSEL_CARD_CAP = 10;\n\n/** The character limit for an alert's text. */\nexport const ALERT_TEXT_CAP = 200;\n\n/**\n * The maximum number of data rows in a data-table block; the emitted `rows`\n * array additionally carries one header row.\n */\nexport const DATA_TABLE_ROW_CAP = 200;\n\n/** The maximum number of columns in a data-table block. */\nexport const DATA_TABLE_COLUMN_CAP = 20;\n\n/**\n * The cumulative character limit (summed across every cell's text) shared by\n * every data-table block in one payload.\n */\nexport const DATA_TABLE_CHAR_BUDGET = 20000;\n\n/** The caption emitted on every native data-table block. */\nexport const TABLE_CAPTION = \"Table\";\n\n/** The cumulative character limit for native markdown blocks. */\nexport const MARKDOWN_TEXT_BUDGET = 12000;\n\n/** Builds the message-surface fallback for an alert. */\nexport function buildAlertFallback(\n title: string,\n description: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): readonly [SlackContextBlock, SlackSectionBlock] {\n const emoji = {\n info: \"ℹ️\",\n success: \"✅\",\n warning: \"⚠️\",\n danger: \"🛑\",\n }[tone];\n return [\n {\n type: \"context\",\n elements: [\n { type: \"mrkdwn\", text: title ? `${emoji} *${title}*` : emoji },\n ],\n },\n { type: \"section\", text: { type: \"mrkdwn\", text: description } },\n ];\n}\n\n/** Builds a 2026 Slack modal-only alert block, mapping `tone` to its `level`. */\nexport function buildAlertBlock(\n text: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): SlackAlertBlock {\n const level = tone === \"danger\" ? \"error\" : tone;\n return { type: \"alert\", text: { type: \"mrkdwn\", text }, level };\n}\n\ntype CardBlockFields = {\n readonly heroImage?: SlackImageBlock;\n readonly title?: SlackTextObject;\n readonly subtitle?: SlackTextObject;\n readonly body?: SlackTextObject;\n readonly subtext?: SlackTextObject;\n readonly actions?: readonly SlackButtonElement[];\n};\n\n/** Builds a 2026 Slack card block from its already-clamped fields. */\nexport function buildCardBlock(fields: CardBlockFields): SlackCardBlock {\n const { heroImage, title, subtitle, body, subtext, actions } = fields;\n return {\n type: \"card\",\n ...(heroImage !== undefined ? { hero_image: heroImage } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(subtitle !== undefined ? { subtitle } : {}),\n ...(body !== undefined ? { body } : {}),\n ...(subtext !== undefined ? { subtext } : {}),\n ...(actions !== undefined ? { actions } : {}),\n };\n}\n\n/** Builds a 2026 Slack carousel block. */\nexport function buildCarouselBlock(\n elements: readonly SlackCardBlock[],\n): SlackCarouselBlock {\n return { type: \"carousel\", elements };\n}\n\n/** Builds a 2026 Slack data-table block; `rows[0]` must be the header row. */\nexport function buildDataTableBlock(\n rows: readonly (readonly SlackDataTableCell[])[],\n): SlackDataTableBlock {\n return { type: \"data_table\", caption: TABLE_CAPTION, rows };\n}\n\n/** Builds the final note used when a surface block budget is exceeded. */\nexport function buildBlockBudgetNote(omitted: number): SlackContextBlock {\n return {\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: `${omitted} ${omitted === 1 ? \"block was\" : \"blocks were\"} omitted.`,\n },\n ],\n };\n}\n"],"mappings":";;AAcA,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;;;;;AAO/B,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;AAG/B,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,sBAAsB;;AAGnC,MAAa,oBAAoB;;AAGjC,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB;;AAGpC,MAAa,kBAAkB;;AAG/B,MAAa,sBAAsB;;AAGnC,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,mBAAmB;;AAGhC,MAAa,oBAAoB;;AAGjC,MAAa,oBAAoB;;AAGjC,MAAa,iBAAiB;;;;;AAM9B,MAAa,qBAAqB;;AAGlC,MAAa,wBAAwB;;;;;AAMrC,MAAa,yBAAyB;;AAGtC,MAAa,gBAAgB;;AAG7B,MAAa,uBAAuB;;AAGpC,SAAgB,mBACd,OACA,aACA,MACiD;CACjD,MAAM,QAAQ;EACZ,MAAM;EACN,SAAS;EACT,SAAS;EACT,QAAQ;CACV,EAAE;CACF,OAAO,CACL;EACE,MAAM;EACN,UAAU,CACR;GAAE,MAAM;GAAU,MAAM,QAAQ,GAAG,MAAM,IAAI,MAAM,KAAK;EAAM,CAChE;CACF,GACA;EAAE,MAAM;EAAW,MAAM;GAAE,MAAM;GAAU,MAAM;EAAY;CAAE,CACjE;AACF;;AAGA,SAAgB,gBACd,MACA,MACiB;CAEjB,OAAO;EAAE,MAAM;EAAS,MAAM;GAAE,MAAM;GAAU;EAAK;EAAG,OAD1C,SAAS,WAAW,UAAU;CACkB;AAChE;;AAYA,SAAgB,eAAe,QAAyC;CACtE,MAAM,EAAE,WAAW,OAAO,UAAU,MAAM,SAAS,YAAY;CAC/D,OAAO;EACL,MAAM;EACN,GAAI,cAAc,KAAA,IAAY,EAAE,YAAY,UAAU,IAAI,CAAC;EAC3D,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACvC,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;EACrC,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;EAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;CAC7C;AACF;;AAGA,SAAgB,mBACd,UACoB;CACpB,OAAO;EAAE,MAAM;EAAY;CAAS;AACtC;;AAGA,SAAgB,oBACd,MACqB;CACrB,OAAO;EAAE,MAAM;EAAc,SAAS;EAAe;CAAK;AAC5D;;AAGA,SAAgB,qBAAqB,SAAoC;CACvE,OAAO;EACL,MAAM;EACN,UAAU,CACR;GACE,MAAM;GACN,MAAM,GAAG,QAAQ,GAAG,YAAY,IAAI,cAAc,cAAc;EAClE,CACF;CACF;AACF"}
@@ -1,3 +1,4 @@
1
+ import { copyBounded } from "../convert/copyBounded.js";
1
2
  import "./constants.js";
2
3
  //#region src/slack/fromSlackBlocks.ts
3
4
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -12,21 +13,16 @@ const warn = (warnings, code, component, detail) => {
12
13
  });
13
14
  };
14
15
  /**
15
- * The shared bounded-iteration primitive: slices `value` to `cap` entries
16
- * without ever reading past that many indices, so a hostile array (sparse or
17
- * proxied with a fabricated `length`) cannot stall the event loop. `slice`
18
- * itself performs the bounded read; only its own reported length is
19
- * inspected to detect truncation.
16
+ * The shared bounded-iteration primitive: copies `value` to at most `cap`
17
+ * entries without ever reading past that many indices, so a hostile array
18
+ * (sparse or proxied with a fabricated `length`) cannot stall the event loop.
20
19
  */
21
20
  const clampArray = (value, cap) => {
22
21
  if (!Array.isArray(value)) return {
23
22
  items: [],
24
23
  truncated: false
25
24
  };
26
- return {
27
- items: value.slice(0, cap),
28
- truncated: value.length > cap
29
- };
25
+ return copyBounded(value, cap);
30
26
  };
31
27
  const boundedArray = (value, cap, warnings, component, detail) => {
32
28
  const { items, truncated } = clampArray(value, cap);
@@ -157,7 +153,7 @@ const checkboxFrom = (element) => {
157
153
  const first = (Array.isArray(element["options"]) ? element["options"] : [])[0];
158
154
  const initialOptions = Array.isArray(element["initial_options"]) ? element["initial_options"] : [];
159
155
  const firstValue = isRecord(first) && typeof first["value"] === "string" ? first["value"] : "";
160
- const firstChecked = initialOptions.slice(0, 100).some((option) => isRecord(option) && option["value"] === firstValue);
156
+ const firstChecked = copyBounded(initialOptions, 100).items.some((option) => isRecord(option) && option["value"] === firstValue);
161
157
  return {
162
158
  $type: "Checkbox",
163
159
  label: isRecord(first) ? textOf(first["text"]) : "",