@graphrefly/graphrefly 0.47.2 → 0.48.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/dist/base/composition/index.cjs +4 -3
- package/dist/base/composition/index.cjs.map +1 -1
- package/dist/base/composition/index.d.cts +14 -5
- package/dist/base/composition/index.d.ts +14 -5
- package/dist/base/composition/index.js +8 -8
- package/dist/base/index.cjs +152 -78
- package/dist/base/index.cjs.map +1 -1
- package/dist/base/index.d.cts +2 -2
- package/dist/base/index.d.ts +2 -2
- package/dist/base/index.js +75 -70
- package/dist/base/io/index.cjs +31 -17
- package/dist/base/io/index.cjs.map +1 -1
- package/dist/base/io/index.d.cts +32 -5
- package/dist/base/io/index.d.ts +32 -5
- package/dist/base/io/index.js +1 -1
- package/dist/base/mutation/index.cjs +21 -0
- package/dist/base/mutation/index.cjs.map +1 -1
- package/dist/base/mutation/index.d.cts +23 -1
- package/dist/base/mutation/index.d.ts +23 -1
- package/dist/base/mutation/index.js +3 -1
- package/dist/base/sources/browser/index.cjs +5 -3
- package/dist/base/sources/browser/index.cjs.map +1 -1
- package/dist/base/sources/browser/index.d.cts +20 -2
- package/dist/base/sources/browser/index.d.ts +20 -2
- package/dist/base/sources/browser/index.js +5 -3
- package/dist/base/sources/browser/index.js.map +1 -1
- package/dist/base/sources/event/index.cjs +28 -0
- package/dist/base/sources/event/index.cjs.map +1 -1
- package/dist/base/sources/event/index.d.cts +67 -3
- package/dist/base/sources/event/index.d.ts +67 -3
- package/dist/base/sources/event/index.js +4 -1
- package/dist/base/sources/index.cjs +75 -37
- package/dist/base/sources/index.cjs.map +1 -1
- package/dist/base/sources/index.d.cts +1 -1
- package/dist/base/sources/index.d.ts +1 -1
- package/dist/base/sources/index.js +5 -2
- package/dist/{chunk-R6ZCSXKX.js → chunk-23MAWVOJ.js} +3 -3
- package/dist/{chunk-MS3WPRJR.js → chunk-3REMCHSS.js} +6 -6
- package/dist/chunk-3REMCHSS.js.map +1 -0
- package/dist/{chunk-CEVNQ74M.js → chunk-3YGXPUHW.js} +2 -2
- package/dist/{chunk-CEVNQ74M.js.map → chunk-3YGXPUHW.js.map} +1 -1
- package/dist/{chunk-6ZLCPUXS.js → chunk-46X2EFQH.js} +15 -4
- package/dist/chunk-46X2EFQH.js.map +1 -0
- package/dist/{chunk-NY2PYHNC.js → chunk-5UY3PNFY.js} +12 -5
- package/dist/chunk-5UY3PNFY.js.map +1 -0
- package/dist/{chunk-FQSQONOU.js → chunk-65OM4XLQ.js} +49 -3
- package/dist/chunk-65OM4XLQ.js.map +1 -0
- package/dist/{chunk-3PSLNJDU.js → chunk-6DQYBIHW.js} +314 -49
- package/dist/chunk-6DQYBIHW.js.map +1 -0
- package/dist/{chunk-LDCSZ72P.js → chunk-6YBER5UP.js} +3 -3
- package/dist/{chunk-LDCSZ72P.js.map → chunk-6YBER5UP.js.map} +1 -1
- package/dist/{chunk-3O3NKZJW.js → chunk-7T7WLEPM.js} +24 -3
- package/dist/chunk-7T7WLEPM.js.map +1 -0
- package/dist/{chunk-PKPO3JTZ.js → chunk-AQAKDE7F.js} +29 -11
- package/dist/chunk-AQAKDE7F.js.map +1 -0
- package/dist/{chunk-6MRSX3YK.js → chunk-B5Y5GPD5.js} +2 -2
- package/dist/{chunk-BXGZFGZ4.js → chunk-C5QD5DQX.js} +22 -1
- package/dist/chunk-C5QD5DQX.js.map +1 -0
- package/dist/{chunk-4XCHZRUJ.js → chunk-D5YGR4TP.js} +58 -7
- package/dist/chunk-D5YGR4TP.js.map +1 -0
- package/dist/{chunk-NPRP3MCV.js → chunk-DHDCOOJU.js} +2 -2
- package/dist/chunk-DHDCOOJU.js.map +1 -0
- package/dist/{chunk-VP3TIUDF.js → chunk-DVTDF5OI.js} +2 -2
- package/dist/{chunk-OXD5LFQP.js → chunk-G7H6PN7P.js} +2 -2
- package/dist/{chunk-EL5VHUGK.js → chunk-GGKHHG5Y.js} +32 -18
- package/dist/chunk-GGKHHG5Y.js.map +1 -0
- package/dist/{chunk-446I4EGD.js → chunk-J5TBZFBD.js} +2 -2
- package/dist/{chunk-7AVQIGF6.js → chunk-K4ZYJ4EM.js} +554 -460
- package/dist/chunk-K4ZYJ4EM.js.map +1 -0
- package/dist/{chunk-QFE5BQH7.js → chunk-LTSI7ULC.js} +2 -2
- package/dist/{chunk-5GVURVIG.js → chunk-MMHGYX44.js} +12 -2
- package/dist/{chunk-5GVURVIG.js.map → chunk-MMHGYX44.js.map} +1 -1
- package/dist/{chunk-KRFGO5QH.js → chunk-MQMTRKY3.js} +118 -43
- package/dist/chunk-MQMTRKY3.js.map +1 -0
- package/dist/{chunk-42FQ27MQ.js → chunk-MTODGQBR.js} +44 -179
- package/dist/chunk-MTODGQBR.js.map +1 -0
- package/dist/{chunk-FVINAAKA.js → chunk-NBK6QQMG.js} +14 -13
- package/dist/{chunk-FVINAAKA.js.map → chunk-NBK6QQMG.js.map} +1 -1
- package/dist/{chunk-KNU73RZW.js → chunk-NSA5K5G2.js} +2 -2
- package/dist/{chunk-MLTPJMH6.js → chunk-QQYULEZL.js} +2 -2
- package/dist/chunk-QSW4DFKE.js +31 -0
- package/dist/chunk-QSW4DFKE.js.map +1 -0
- package/dist/{chunk-VAZXUK6G.js → chunk-SUNCHMML.js} +2 -2
- package/dist/{chunk-EP4WVQLX.js → chunk-T2U6N3FV.js} +6 -6
- package/dist/{chunk-T7SP3EYR.js → chunk-T5URUIIY.js} +33 -24
- package/dist/chunk-T5URUIIY.js.map +1 -0
- package/dist/{chunk-VNXAF2KE.js → chunk-TPTZZV25.js} +6 -6
- package/dist/chunk-TPTZZV25.js.map +1 -0
- package/dist/{chunk-IOJDYUA7.js → chunk-V46JWFGV.js} +6 -5
- package/dist/chunk-V46JWFGV.js.map +1 -0
- package/dist/{chunk-WGDEBIP4.js → chunk-X6ESZDR6.js} +5 -6
- package/dist/chunk-X6ESZDR6.js.map +1 -0
- package/dist/{chunk-N65E26UL.js → chunk-XEWV254I.js} +2 -2
- package/dist/{chunk-N65E26UL.js.map → chunk-XEWV254I.js.map} +1 -1
- package/dist/{chunk-PTWADEH3.js → chunk-YBJVKMTM.js} +34 -14
- package/dist/chunk-YBJVKMTM.js.map +1 -0
- package/dist/{chunk-DDTS7F5O.js → chunk-ZW32BPXV.js} +12 -3
- package/dist/chunk-ZW32BPXV.js.map +1 -0
- package/dist/compat/index.cjs +51 -4
- package/dist/compat/index.cjs.map +1 -1
- package/dist/compat/index.d.cts +1 -1
- package/dist/compat/index.d.ts +1 -1
- package/dist/compat/index.js +6 -6
- package/dist/compat/nestjs/index.cjs +51 -4
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +1 -1
- package/dist/compat/nestjs/index.d.ts +1 -1
- package/dist/compat/nestjs/index.js +3 -3
- package/dist/{fallback-Bx46zqky.d.cts → fallback-BROR6ZhO.d.cts} +1 -1
- package/dist/{fallback-pIWW8A2d.d.ts → fallback-DO80aM_3.d.ts} +1 -1
- package/dist/{index-B_p8tnvf.d.cts → index-D1z3XcF9.d.cts} +1 -0
- package/dist/{index-_HDSmPyp.d.ts → index-DZ6yua0Q.d.ts} +1 -0
- package/dist/index.cjs +2215 -1676
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +169 -146
- package/dist/index.js.map +1 -1
- package/dist/presets/ai/index.cjs +46 -0
- package/dist/presets/ai/index.cjs.map +1 -1
- package/dist/presets/ai/index.js +12 -12
- package/dist/presets/harness/index.cjs +130 -18
- package/dist/presets/harness/index.cjs.map +1 -1
- package/dist/presets/harness/index.d.cts +15 -5
- package/dist/presets/harness/index.d.ts +15 -5
- package/dist/presets/harness/index.js +22 -22
- package/dist/presets/index.cjs +222 -53
- package/dist/presets/index.cjs.map +1 -1
- package/dist/presets/index.d.cts +2 -2
- package/dist/presets/index.d.ts +2 -2
- package/dist/presets/index.js +45 -45
- package/dist/presets/inspect/index.cjs +63 -14
- package/dist/presets/inspect/index.cjs.map +1 -1
- package/dist/presets/inspect/index.d.cts +1 -1
- package/dist/presets/inspect/index.d.ts +1 -1
- package/dist/presets/inspect/index.js +6 -6
- package/dist/presets/resilience/index.cjs +29 -21
- package/dist/presets/resilience/index.cjs.map +1 -1
- package/dist/presets/resilience/index.d.cts +12 -8
- package/dist/presets/resilience/index.d.ts +12 -8
- package/dist/presets/resilience/index.js +3 -3
- package/dist/{rate-limiter-DpVbSYdH.d.cts → rate-limiter-DC26FM8J.d.cts} +10 -1
- package/dist/{rate-limiter-CEALq4N1.d.ts → rate-limiter-DyWpwpQP.d.ts} +10 -1
- package/dist/{reactive-layout-fswlBUvX.d.ts → reactive-layout-BBBWH0V_.d.cts} +85 -4
- package/dist/{reactive-layout-fswlBUvX.d.cts → reactive-layout-BBBWH0V_.d.ts} +85 -4
- package/dist/solutions/index.cjs +168 -47
- package/dist/solutions/index.cjs.map +1 -1
- package/dist/solutions/index.d.cts +2 -2
- package/dist/solutions/index.d.ts +2 -2
- package/dist/solutions/index.js +28 -28
- package/dist/{spawnable-5mDY501F.d.cts → spawnable-B2IlW60f.d.cts} +23 -2
- package/dist/{spawnable-D3lR0oQu.d.ts → spawnable-tttFz2Nh.d.ts} +23 -2
- package/dist/testing/index.cjs +94 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +59 -0
- package/dist/testing/index.d.ts +59 -0
- package/dist/testing/index.js +73 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/utils/ai/browser.cjs.map +1 -1
- package/dist/utils/ai/browser.d.cts +2 -2
- package/dist/utils/ai/browser.d.ts +2 -2
- package/dist/utils/ai/browser.js +6 -6
- package/dist/utils/ai/browser.js.map +1 -1
- package/dist/utils/ai/index.cjs +250 -166
- package/dist/utils/ai/index.cjs.map +1 -1
- package/dist/utils/ai/index.d.cts +108 -12
- package/dist/utils/ai/index.d.ts +108 -12
- package/dist/utils/ai/index.js +21 -19
- package/dist/utils/ai/node.cjs.map +1 -1
- package/dist/utils/ai/node.d.cts +5 -5
- package/dist/utils/ai/node.d.ts +5 -5
- package/dist/utils/ai/node.js +2 -2
- package/dist/utils/ai/node.js.map +1 -1
- package/dist/utils/cqrs/index.cjs +29 -3
- package/dist/utils/cqrs/index.cjs.map +1 -1
- package/dist/utils/cqrs/index.d.cts +12 -7
- package/dist/utils/cqrs/index.d.ts +12 -7
- package/dist/utils/cqrs/index.js +2 -2
- package/dist/utils/demo-shell/index.cjs +45 -19
- package/dist/utils/demo-shell/index.cjs.map +1 -1
- package/dist/utils/demo-shell/index.d.cts +1 -1
- package/dist/utils/demo-shell/index.d.ts +1 -1
- package/dist/utils/demo-shell/index.js +2 -2
- package/dist/utils/domain-templates/index.cjs.map +1 -1
- package/dist/utils/domain-templates/index.js +3 -3
- package/dist/utils/graphspec/index.cjs.map +1 -1
- package/dist/utils/graphspec/index.js +3 -3
- package/dist/utils/index.cjs +1642 -1225
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +7 -7
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.js +72 -54
- package/dist/utils/inspect/index.cjs +52 -4
- package/dist/utils/inspect/index.cjs.map +1 -1
- package/dist/utils/inspect/index.d.cts +32 -3
- package/dist/utils/inspect/index.d.ts +32 -3
- package/dist/utils/inspect/index.js +4 -4
- package/dist/utils/job-queue/index.cjs +46 -9
- package/dist/utils/job-queue/index.cjs.map +1 -1
- package/dist/utils/job-queue/index.d.cts +33 -3
- package/dist/utils/job-queue/index.d.ts +33 -3
- package/dist/utils/job-queue/index.js +2 -2
- package/dist/utils/memory/index.cjs +556 -462
- package/dist/utils/memory/index.cjs.map +1 -1
- package/dist/utils/memory/index.d.cts +203 -24
- package/dist/utils/memory/index.d.ts +203 -24
- package/dist/utils/memory/index.js +10 -2
- package/dist/utils/messaging/index.cjs.map +1 -1
- package/dist/utils/messaging/index.d.cts +4 -3
- package/dist/utils/messaging/index.d.ts +4 -3
- package/dist/utils/messaging/index.js +2 -2
- package/dist/utils/orchestration/index.cjs +9 -0
- package/dist/utils/orchestration/index.cjs.map +1 -1
- package/dist/utils/orchestration/index.js +3 -3
- package/dist/utils/process/index.cjs +32 -2
- package/dist/utils/process/index.cjs.map +1 -1
- package/dist/utils/process/index.d.cts +4 -3
- package/dist/utils/process/index.d.ts +4 -3
- package/dist/utils/process/index.js +2 -2
- package/dist/utils/reactive-layout/index.cjs +184 -55
- package/dist/utils/reactive-layout/index.cjs.map +1 -1
- package/dist/utils/reactive-layout/index.d.cts +128 -3
- package/dist/utils/reactive-layout/index.d.ts +128 -3
- package/dist/utils/reactive-layout/index.js +16 -8
- package/dist/utils/reduction/index.cjs.map +1 -1
- package/dist/utils/reduction/index.js +2 -2
- package/dist/utils/resilience/index.cjs +29 -20
- package/dist/utils/resilience/index.cjs.map +1 -1
- package/dist/utils/resilience/index.d.cts +1 -1
- package/dist/utils/resilience/index.d.ts +1 -1
- package/dist/utils/resilience/index.js +2 -2
- package/dist/utils/surface/index.cjs.map +1 -1
- package/dist/utils/surface/index.js +4 -4
- package/package.json +15 -3
- package/dist/chunk-3O3NKZJW.js.map +0 -1
- package/dist/chunk-3PSLNJDU.js.map +0 -1
- package/dist/chunk-42FQ27MQ.js.map +0 -1
- package/dist/chunk-4XCHZRUJ.js.map +0 -1
- package/dist/chunk-6ZLCPUXS.js.map +0 -1
- package/dist/chunk-7AVQIGF6.js.map +0 -1
- package/dist/chunk-BXGZFGZ4.js.map +0 -1
- package/dist/chunk-DDTS7F5O.js.map +0 -1
- package/dist/chunk-EL5VHUGK.js.map +0 -1
- package/dist/chunk-FQSQONOU.js.map +0 -1
- package/dist/chunk-IOJDYUA7.js.map +0 -1
- package/dist/chunk-KRFGO5QH.js.map +0 -1
- package/dist/chunk-MS3WPRJR.js.map +0 -1
- package/dist/chunk-NPRP3MCV.js.map +0 -1
- package/dist/chunk-NY2PYHNC.js.map +0 -1
- package/dist/chunk-PKPO3JTZ.js.map +0 -1
- package/dist/chunk-PTWADEH3.js.map +0 -1
- package/dist/chunk-T7SP3EYR.js.map +0 -1
- package/dist/chunk-VNXAF2KE.js.map +0 -1
- package/dist/chunk-W2BOPXTI.js +0 -1
- package/dist/chunk-W2BOPXTI.js.map +0 -1
- package/dist/chunk-WGDEBIP4.js.map +0 -1
- /package/dist/{chunk-R6ZCSXKX.js.map → chunk-23MAWVOJ.js.map} +0 -0
- /package/dist/{chunk-6MRSX3YK.js.map → chunk-B5Y5GPD5.js.map} +0 -0
- /package/dist/{chunk-VP3TIUDF.js.map → chunk-DVTDF5OI.js.map} +0 -0
- /package/dist/{chunk-OXD5LFQP.js.map → chunk-G7H6PN7P.js.map} +0 -0
- /package/dist/{chunk-446I4EGD.js.map → chunk-J5TBZFBD.js.map} +0 -0
- /package/dist/{chunk-QFE5BQH7.js.map → chunk-LTSI7ULC.js.map} +0 -0
- /package/dist/{chunk-KNU73RZW.js.map → chunk-NSA5K5G2.js.map} +0 -0
- /package/dist/{chunk-MLTPJMH6.js.map → chunk-QQYULEZL.js.map} +0 -0
- /package/dist/{chunk-VAZXUK6G.js.map → chunk-SUNCHMML.js.map} +0 -0
- /package/dist/{chunk-EP4WVQLX.js.map → chunk-T2U6N3FV.js.map} +0 -0
|
@@ -3,175 +3,12 @@ import {
|
|
|
3
3
|
carveTextLineSlots,
|
|
4
4
|
computeCharPositions,
|
|
5
5
|
computeLineBreaks,
|
|
6
|
+
getDefaultSegmentAdapter,
|
|
6
7
|
layoutNextLine
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6DQYBIHW.js";
|
|
8
9
|
import {
|
|
9
10
|
emitToMeta
|
|
10
11
|
} from "./chunk-KN3H5CNT.js";
|
|
11
|
-
import {
|
|
12
|
-
countCells
|
|
13
|
-
} from "./chunk-36NMM65U.js";
|
|
14
|
-
|
|
15
|
-
// src/utils/reactive-layout/measurement-adapters.ts
|
|
16
|
-
var CliMeasureAdapter = class {
|
|
17
|
-
cellPx;
|
|
18
|
-
constructor(opts) {
|
|
19
|
-
this.cellPx = opts?.cellPx ?? 8;
|
|
20
|
-
}
|
|
21
|
-
measureSegment(text, _font) {
|
|
22
|
-
return { width: countCells(text) * this.cellPx };
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
var PrecomputedAdapterKeyError = class extends Error {
|
|
26
|
-
name = "KeyError";
|
|
27
|
-
};
|
|
28
|
-
var PrecomputedAdapter = class {
|
|
29
|
-
metrics;
|
|
30
|
-
fallback;
|
|
31
|
-
constructor(opts) {
|
|
32
|
-
this.metrics = opts.metrics;
|
|
33
|
-
const fb = opts.fallback ?? "per-char";
|
|
34
|
-
if (fb !== "per-char" && fb !== "error") {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`fallback must be 'per-char' or 'error', got ${JSON.stringify(opts.fallback)}`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
this.fallback = fb;
|
|
40
|
-
}
|
|
41
|
-
measureSegment(text, font) {
|
|
42
|
-
const fontMap = this.metrics[font];
|
|
43
|
-
if (fontMap) {
|
|
44
|
-
const w = fontMap[text];
|
|
45
|
-
if (w !== void 0) return { width: w };
|
|
46
|
-
}
|
|
47
|
-
if (this.fallback === "error") {
|
|
48
|
-
throw new PrecomputedAdapterKeyError(
|
|
49
|
-
`PrecomputedAdapter: no metrics for segment ${JSON.stringify(text)} in font ${JSON.stringify(font)}`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
let total = 0;
|
|
53
|
-
if (fontMap) {
|
|
54
|
-
for (const ch of text) {
|
|
55
|
-
const cw = fontMap[ch];
|
|
56
|
-
if (cw !== void 0) {
|
|
57
|
-
total += cw;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return { width: total };
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
var CanvasMeasureAdapter = class {
|
|
65
|
-
ctx = null;
|
|
66
|
-
currentFont = "";
|
|
67
|
-
emojiCorrection;
|
|
68
|
-
constructor(opts) {
|
|
69
|
-
this.emojiCorrection = opts?.emojiCorrection ?? 1;
|
|
70
|
-
}
|
|
71
|
-
getContext() {
|
|
72
|
-
if (!this.ctx) {
|
|
73
|
-
if (typeof OffscreenCanvas === "undefined") {
|
|
74
|
-
throw new Error(
|
|
75
|
-
"CanvasMeasureAdapter requires a browser environment with OffscreenCanvas support. Use CliMeasureAdapter or NodeCanvasMeasureAdapter for Node.js."
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
const canvas = new OffscreenCanvas(0, 0);
|
|
79
|
-
const ctx = canvas.getContext("2d");
|
|
80
|
-
if (!ctx) throw new Error("CanvasMeasureAdapter: failed to get 2d context");
|
|
81
|
-
this.ctx = ctx;
|
|
82
|
-
}
|
|
83
|
-
return this.ctx;
|
|
84
|
-
}
|
|
85
|
-
measureSegment(text, font) {
|
|
86
|
-
const ctx = this.getContext();
|
|
87
|
-
if (font !== this.currentFont) {
|
|
88
|
-
ctx.font = font;
|
|
89
|
-
this.currentFont = font;
|
|
90
|
-
}
|
|
91
|
-
let width = ctx.measureText(text).width;
|
|
92
|
-
if (this.emojiCorrection !== 1 && /\p{Emoji_Presentation}/u.test(text)) {
|
|
93
|
-
width *= this.emojiCorrection;
|
|
94
|
-
}
|
|
95
|
-
return { width };
|
|
96
|
-
}
|
|
97
|
-
clearCache() {
|
|
98
|
-
this.currentFont = "";
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
var NodeCanvasMeasureAdapter = class {
|
|
102
|
-
ctx = null;
|
|
103
|
-
currentFont = "";
|
|
104
|
-
canvasModule;
|
|
105
|
-
constructor(canvasModule) {
|
|
106
|
-
this.canvasModule = canvasModule;
|
|
107
|
-
}
|
|
108
|
-
getContext() {
|
|
109
|
-
if (!this.ctx) {
|
|
110
|
-
const canvas = this.canvasModule.createCanvas(0, 0);
|
|
111
|
-
const ctx = canvas.getContext("2d");
|
|
112
|
-
if (!ctx) throw new Error("NodeCanvasMeasureAdapter: failed to get 2d context");
|
|
113
|
-
this.ctx = ctx;
|
|
114
|
-
}
|
|
115
|
-
return this.ctx;
|
|
116
|
-
}
|
|
117
|
-
measureSegment(text, font) {
|
|
118
|
-
const ctx = this.getContext();
|
|
119
|
-
if (font !== this.currentFont) {
|
|
120
|
-
ctx.font = font;
|
|
121
|
-
this.currentFont = font;
|
|
122
|
-
}
|
|
123
|
-
return { width: ctx.measureText(text).width };
|
|
124
|
-
}
|
|
125
|
-
clearCache() {
|
|
126
|
-
this.currentFont = "";
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
var SvgBoundsAdapter = class {
|
|
130
|
-
measureSvg(content) {
|
|
131
|
-
const viewBoxMatch = content.match(/viewBox\s*=\s*["']([^"']+)["']/);
|
|
132
|
-
if (viewBoxMatch) {
|
|
133
|
-
const parts = viewBoxMatch[1].trim().split(/[\s,]+/);
|
|
134
|
-
if (parts.length >= 4) {
|
|
135
|
-
const w = Number.parseFloat(parts[2]);
|
|
136
|
-
const h = Number.parseFloat(parts[3]);
|
|
137
|
-
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
138
|
-
return { width: w, height: h };
|
|
139
|
-
}
|
|
140
|
-
throw new Error(
|
|
141
|
-
"SvgBoundsAdapter: viewBox width/height are missing, non-finite, or not positive"
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const widthMatch = content.match(/<svg[^>]*\bwidth\s*=\s*["']?([\d.]+)/);
|
|
146
|
-
const heightMatch = content.match(/<svg[^>]*\bheight\s*=\s*["']?([\d.]+)/);
|
|
147
|
-
if (widthMatch && heightMatch) {
|
|
148
|
-
const w = Number.parseFloat(widthMatch[1]);
|
|
149
|
-
const h = Number.parseFloat(heightMatch[1]);
|
|
150
|
-
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
151
|
-
return { width: w, height: h };
|
|
152
|
-
}
|
|
153
|
-
throw new Error(
|
|
154
|
-
"SvgBoundsAdapter: svg width/height attributes are non-finite or not positive"
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
throw new Error(
|
|
158
|
-
"SvgBoundsAdapter: cannot determine dimensions \u2014 SVG has no viewBox or width/height attributes"
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
var ImageSizeAdapter = class {
|
|
163
|
-
sizes;
|
|
164
|
-
constructor(sizes) {
|
|
165
|
-
this.sizes = new Map(Object.entries(sizes));
|
|
166
|
-
}
|
|
167
|
-
measureImage(src) {
|
|
168
|
-
const dims = this.sizes.get(src);
|
|
169
|
-
if (!dims) {
|
|
170
|
-
throw new Error(`ImageSizeAdapter: no dimensions registered for ${JSON.stringify(src)}`);
|
|
171
|
-
}
|
|
172
|
-
return { width: dims.width, height: dims.height };
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
12
|
|
|
176
13
|
// src/utils/reactive-layout/reactive-block-layout.ts
|
|
177
14
|
import { monotonicNs, node } from "@graphrefly/pure-ts/core";
|
|
@@ -181,9 +18,24 @@ function measureBlock(block, maxWidth, adapters, measureCache, defaultFont, defa
|
|
|
181
18
|
case "text": {
|
|
182
19
|
const font = block.font ?? defaultFont;
|
|
183
20
|
const lineHeight = block.lineHeight ?? defaultLineHeight;
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
21
|
+
const segAdapter = adapters.segment ?? getDefaultSegmentAdapter();
|
|
22
|
+
const segments = analyzeAndMeasure(
|
|
23
|
+
block.text,
|
|
24
|
+
font,
|
|
25
|
+
adapters.text,
|
|
26
|
+
measureCache,
|
|
27
|
+
void 0,
|
|
28
|
+
segAdapter
|
|
29
|
+
);
|
|
30
|
+
const lineBreaks = computeLineBreaks(
|
|
31
|
+
segments,
|
|
32
|
+
maxWidth,
|
|
33
|
+
adapters.text,
|
|
34
|
+
font,
|
|
35
|
+
measureCache,
|
|
36
|
+
segAdapter
|
|
37
|
+
);
|
|
38
|
+
const charPositions = computeCharPositions(lineBreaks, segments, lineHeight, segAdapter);
|
|
187
39
|
const height = lineBreaks.lineCount * lineHeight;
|
|
188
40
|
let width = 0;
|
|
189
41
|
for (const line of lineBreaks.lines) {
|
|
@@ -401,6 +253,7 @@ function obstacleIntervalForBand(o, bandTop, bandBottom) {
|
|
|
401
253
|
}
|
|
402
254
|
function computeFlowLines(segments, container, columns, obstacles, lineHeight, minSlotWidth, opts) {
|
|
403
255
|
const paragraphSpacing = opts?.paragraphSpacing ?? lineHeight;
|
|
256
|
+
const segmentAdapterCtx = opts?.segmentAdapter;
|
|
404
257
|
const lines = [];
|
|
405
258
|
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
|
|
406
259
|
if (segments.length === 0 || columns.count <= 0 || lineHeight <= 0) {
|
|
@@ -433,7 +286,12 @@ function computeFlowLines(segments, container, columns, obstacles, lineHeight, m
|
|
|
433
286
|
for (let si = 0; si < slots.length; si++) {
|
|
434
287
|
const slot = slots[si];
|
|
435
288
|
const slotW = slot.right - slot.left;
|
|
436
|
-
const line = layoutNextLine(
|
|
289
|
+
const line = layoutNextLine(
|
|
290
|
+
segments,
|
|
291
|
+
cursor,
|
|
292
|
+
slotW,
|
|
293
|
+
segmentAdapterCtx ? { segmentAdapter: segmentAdapterCtx } : void 0
|
|
294
|
+
);
|
|
437
295
|
if (line === null) {
|
|
438
296
|
return { lines, cursor };
|
|
439
297
|
}
|
|
@@ -465,7 +323,13 @@ function computeFlowLines(segments, container, columns, obstacles, lineHeight, m
|
|
|
465
323
|
return { lines, cursor };
|
|
466
324
|
}
|
|
467
325
|
function reactiveFlowLayout(opts) {
|
|
468
|
-
const {
|
|
326
|
+
const {
|
|
327
|
+
adapter,
|
|
328
|
+
segmentAdapter: segmentAdapterOpt,
|
|
329
|
+
name = "reactive-flow-layout",
|
|
330
|
+
minSlotWidth = 20
|
|
331
|
+
} = opts;
|
|
332
|
+
const segmentAdapter = segmentAdapterOpt ?? getDefaultSegmentAdapter();
|
|
469
333
|
const g = new Graph2(name);
|
|
470
334
|
const measureCache = /* @__PURE__ */ new Map();
|
|
471
335
|
const textNode = node2([], { name: "text", initial: opts.text ?? "" });
|
|
@@ -491,7 +355,14 @@ function reactiveFlowLayout(opts) {
|
|
|
491
355
|
const textVal = b0 != null && b0.length > 0 ? b0.at(-1) : ctx.prevData[0];
|
|
492
356
|
const b1 = data[1];
|
|
493
357
|
const fontVal = b1 != null && b1.length > 0 ? b1.at(-1) : ctx.prevData[1];
|
|
494
|
-
const result = analyzeAndMeasure(
|
|
358
|
+
const result = analyzeAndMeasure(
|
|
359
|
+
textVal,
|
|
360
|
+
fontVal,
|
|
361
|
+
adapter,
|
|
362
|
+
measureCache,
|
|
363
|
+
void 0,
|
|
364
|
+
segmentAdapter
|
|
365
|
+
);
|
|
495
366
|
actions.emit(result);
|
|
496
367
|
const flush = () => {
|
|
497
368
|
measureCache.clear();
|
|
@@ -517,7 +388,7 @@ function reactiveFlowLayout(opts) {
|
|
|
517
388
|
data[3],
|
|
518
389
|
data[4],
|
|
519
390
|
minSlotWidth,
|
|
520
|
-
{ paragraphSpacing: effectiveSpacing }
|
|
391
|
+
{ paragraphSpacing: effectiveSpacing, segmentAdapter }
|
|
521
392
|
);
|
|
522
393
|
const elapsed = monotonicNs2() - t0;
|
|
523
394
|
const overflow = Math.max(0, segments.length - cursor.segmentIndex);
|
|
@@ -575,12 +446,6 @@ function reactiveFlowLayout(opts) {
|
|
|
575
446
|
}
|
|
576
447
|
|
|
577
448
|
export {
|
|
578
|
-
CliMeasureAdapter,
|
|
579
|
-
PrecomputedAdapter,
|
|
580
|
-
CanvasMeasureAdapter,
|
|
581
|
-
NodeCanvasMeasureAdapter,
|
|
582
|
-
SvgBoundsAdapter,
|
|
583
|
-
ImageSizeAdapter,
|
|
584
449
|
measureBlock,
|
|
585
450
|
measureBlocks,
|
|
586
451
|
computeBlockFlow,
|
|
@@ -591,4 +456,4 @@ export {
|
|
|
591
456
|
computeFlowLines,
|
|
592
457
|
reactiveFlowLayout
|
|
593
458
|
};
|
|
594
|
-
//# sourceMappingURL=chunk-
|
|
459
|
+
//# sourceMappingURL=chunk-MTODGQBR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/reactive-layout/reactive-block-layout.ts","../src/utils/reactive-layout/reactive-flow-layout.ts"],"sourcesContent":["/**\n * Reactive multi-content block layout engine (roadmap §7.1 — mixed content).\n *\n * Extends the text-only `reactiveLayout` with support for image and SVG blocks.\n * Pure-arithmetic layout over measured child sizes — no DOM, no async.\n *\n * Graph shape:\n * ```\n * Graph(\"reactive-block-layout\")\n * ├── node([], { initial: \"blocks\" }) — ContentBlock[] input\n * ├── node([], { initial: \"max-width\" }) — container constraint\n * ├── node([], { initial: \"gap\" }) — vertical gap between blocks (px)\n * ├── derived(\"measured-blocks\") — blocks → MeasuredBlock[] (per-type measurement)\n * ├── derived(\"block-flow\") — measured-blocks + max-width + gap → PositionedBlock[]\n * ├── derived(\"total-height\") — block-flow → total height\n * └── meta: { block-count, layout-time-ns }\n * ```\n */\nimport { monotonicNs, type Node, node } from \"@graphrefly/pure-ts/core\";\n\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\nimport { emitToMeta } from \"../../base/meta/emit-to-meta.js\";\nimport { getDefaultSegmentAdapter } from \"./measurement-adapters.js\";\nimport {\n\tanalyzeAndMeasure,\n\ttype CharPosition,\n\tcomputeCharPositions,\n\tcomputeLineBreaks,\n\ttype LineBreaksResult,\n\ttype MeasurementAdapter,\n\ttype PreparedSegment,\n\ttype SegmentAdapter,\n} from \"./reactive-layout.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Pluggable measurement backend for SVG content. */\nexport interface SvgMeasurer {\n\tmeasureSvg(content: string): { width: number; height: number };\n}\n\n/** Pluggable measurement backend for image content. */\nexport interface ImageMeasurer {\n\tmeasureImage(src: string): { width: number; height: number };\n}\n\n/** Adapters map for `reactiveBlockLayout`. */\nexport type BlockAdapters = {\n\t/** Text measurement adapter (required — delegates to `reactiveLayout` internals). */\n\ttext: MeasurementAdapter;\n\t/**\n\t * Text segmentation adapter (optional). Defaults to a lazy\n\t * {@link IntlSegmentAdapter}; **must be supplied on Hermes / RN** to avoid\n\t * the missing-`Intl.Segmenter` runtime throw — see {@link SegmentAdapter}.\n\t */\n\tsegment?: SegmentAdapter;\n\t/** SVG measurement (optional — required only if SVG blocks are present). */\n\tsvg?: SvgMeasurer;\n\t/** Image measurement (optional — required only if image blocks without explicit dimensions are present). */\n\timage?: ImageMeasurer;\n};\n\n/** A content block — text, image, or SVG. */\nexport type ContentBlock =\n\t| {\n\t\t\ttype: \"text\";\n\t\t\ttext: string;\n\t\t\tfont?: string;\n\t\t\tlineHeight?: number;\n\t }\n\t| {\n\t\t\ttype: \"image\";\n\t\t\tsrc: string;\n\t\t\t/** Natural width in px. Required if no ImageMeasurer adapter is provided. */\n\t\t\tnaturalWidth?: number;\n\t\t\t/** Natural height in px. Required if no ImageMeasurer adapter is provided. */\n\t\t\tnaturalHeight?: number;\n\t }\n\t| {\n\t\t\ttype: \"svg\";\n\t\t\tcontent: string;\n\t\t\t/** Explicit viewBox dimensions. Required if no SvgMeasurer adapter is provided. */\n\t\t\tviewBox?: { width: number; height: number };\n\t };\n\n/**\n * A block after measurement — knows its natural dimensions.\n *\n * **Equality note:** The reactive `measured-blocks` node uses dimension-only equality\n * (`type`, `width`, `height`, `index`). Inner text layout data (`textSegments`,\n * `textLineBreaks`, `textCharPositions`) is NOT compared for change detection.\n * If you need text-level reactivity, use `reactiveLayout()` directly per text block.\n */\nexport type MeasuredBlock = {\n\tindex: number;\n\ttype: \"text\" | \"image\" | \"svg\";\n\twidth: number;\n\theight: number;\n\t/** For text blocks: the inner layout results. */\n\ttextSegments?: PreparedSegment[];\n\ttextLineBreaks?: LineBreaksResult;\n\ttextCharPositions?: CharPosition[];\n};\n\n/** A block after flow — positioned in the container. */\nexport type PositionedBlock = MeasuredBlock & {\n\tx: number;\n\ty: number;\n};\n\n/** Options for `reactiveBlockLayout`. */\nexport type ReactiveBlockLayoutOptions = {\n\tadapters: BlockAdapters;\n\tname?: string;\n\tblocks?: ContentBlock[];\n\t/** Container max width in px (clamped to ≥ 0 on init and `setMaxWidth`). */\n\tmaxWidth?: number;\n\t/** Vertical gap between blocks in px (default 0). */\n\tgap?: number;\n\t/** Default font for text blocks that don't specify one. */\n\tdefaultFont?: string;\n\t/** Default line height for text blocks that don't specify one. */\n\tdefaultLineHeight?: number;\n};\n\n/** Result bundle from `reactiveBlockLayout`. */\nexport type ReactiveBlockLayoutBundle = {\n\tgraph: Graph;\n\tsetBlocks: (blocks: ContentBlock[]) => void;\n\tsetMaxWidth: (maxWidth: number) => void;\n\tsetGap: (gap: number) => void;\n\tmeasuredBlocks: Node<MeasuredBlock[]>;\n\tblockFlow: Node<PositionedBlock[]>;\n\ttotalHeight: Node<number>;\n};\n\n// ---------------------------------------------------------------------------\n// Block measurement (pure functions)\n// ---------------------------------------------------------------------------\n\n/**\n * Measure a single content block, returning natural (unconstrained) dimensions.\n * Text blocks use the text layout pipeline; image/SVG use adapters or explicit dims.\n */\nexport function measureBlock(\n\tblock: ContentBlock,\n\tmaxWidth: number,\n\tadapters: BlockAdapters,\n\tmeasureCache: Map<string, Map<string, number>>,\n\tdefaultFont: string,\n\tdefaultLineHeight: number,\n\tindex: number,\n): MeasuredBlock {\n\tswitch (block.type) {\n\t\tcase \"text\": {\n\t\t\tconst font = block.font ?? defaultFont;\n\t\t\tconst lineHeight = block.lineHeight ?? defaultLineHeight;\n\t\t\tconst segAdapter = adapters.segment ?? getDefaultSegmentAdapter();\n\t\t\tconst segments = analyzeAndMeasure(\n\t\t\t\tblock.text,\n\t\t\t\tfont,\n\t\t\t\tadapters.text,\n\t\t\t\tmeasureCache,\n\t\t\t\tundefined,\n\t\t\t\tsegAdapter,\n\t\t\t);\n\t\t\tconst lineBreaks = computeLineBreaks(\n\t\t\t\tsegments,\n\t\t\t\tmaxWidth,\n\t\t\t\tadapters.text,\n\t\t\t\tfont,\n\t\t\t\tmeasureCache,\n\t\t\t\tsegAdapter,\n\t\t\t);\n\t\t\tconst charPositions = computeCharPositions(lineBreaks, segments, lineHeight, segAdapter);\n\t\t\tconst height = lineBreaks.lineCount * lineHeight;\n\t\t\t// Width is the max line width (clamped to maxWidth)\n\t\t\tlet width = 0;\n\t\t\tfor (const line of lineBreaks.lines) {\n\t\t\t\tif (line.width > width) width = line.width;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tindex,\n\t\t\t\ttype: \"text\",\n\t\t\t\twidth: Math.min(width, maxWidth),\n\t\t\t\theight,\n\t\t\t\ttextSegments: segments,\n\t\t\t\ttextLineBreaks: lineBreaks,\n\t\t\t\ttextCharPositions: charPositions,\n\t\t\t};\n\t\t}\n\t\tcase \"image\": {\n\t\t\tlet w: number;\n\t\t\tlet h: number;\n\t\t\tif (block.naturalWidth != null && block.naturalHeight != null) {\n\t\t\t\tw = block.naturalWidth;\n\t\t\t\th = block.naturalHeight;\n\t\t\t} else if (adapters.image) {\n\t\t\t\tconst dims = adapters.image.measureImage(block.src);\n\t\t\t\tw = dims.width;\n\t\t\t\th = dims.height;\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Image block at index ${index} has no naturalWidth/naturalHeight and no ImageMeasurer adapter`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Scale proportionally to fit maxWidth\n\t\t\tif (w > maxWidth) {\n\t\t\t\th = (h * maxWidth) / w;\n\t\t\t\tw = maxWidth;\n\t\t\t}\n\t\t\treturn { index, type: \"image\", width: w, height: h };\n\t\t}\n\t\tcase \"svg\": {\n\t\t\tlet w: number;\n\t\t\tlet h: number;\n\t\t\tif (block.viewBox) {\n\t\t\t\tw = block.viewBox.width;\n\t\t\t\th = block.viewBox.height;\n\t\t\t} else if (adapters.svg) {\n\t\t\t\tconst dims = adapters.svg.measureSvg(block.content);\n\t\t\t\tw = dims.width;\n\t\t\t\th = dims.height;\n\t\t\t} else {\n\t\t\t\tthrow new Error(`SVG block at index ${index} has no viewBox and no SvgMeasurer adapter`);\n\t\t\t}\n\t\t\t// Scale proportionally to fit maxWidth\n\t\t\tif (w > maxWidth) {\n\t\t\t\th = (h * maxWidth) / w;\n\t\t\t\tw = maxWidth;\n\t\t\t}\n\t\t\treturn { index, type: \"svg\", width: w, height: h };\n\t\t}\n\t}\n}\n\n/**\n * Measure all blocks in a content array.\n */\nexport function measureBlocks(\n\tblocks: ContentBlock[],\n\tmaxWidth: number,\n\tadapters: BlockAdapters,\n\tmeasureCache: Map<string, Map<string, number>>,\n\tdefaultFont: string,\n\tdefaultLineHeight: number,\n): MeasuredBlock[] {\n\treturn blocks.map((block, i) =>\n\t\tmeasureBlock(block, maxWidth, adapters, measureCache, defaultFont, defaultLineHeight, i),\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Block flow (pure function)\n// ---------------------------------------------------------------------------\n\n/**\n * Vertical stacking flow: blocks are placed top-to-bottom, left-aligned,\n * separated by `gap` pixels. Pure arithmetic over measured sizes.\n */\nexport function computeBlockFlow(measured: MeasuredBlock[], gap: number): PositionedBlock[] {\n\tconst result: PositionedBlock[] = [];\n\tlet y = 0;\n\tfor (let i = 0; i < measured.length; i++) {\n\t\tconst m = measured[i]!;\n\t\tresult.push({ ...m, x: 0, y });\n\t\ty += m.height + (i < measured.length - 1 ? gap : 0);\n\t}\n\treturn result;\n}\n\n/**\n * Compute total height from positioned blocks.\n */\nexport function computeTotalHeight(flow: PositionedBlock[]): number {\n\tif (flow.length === 0) return 0;\n\tconst last = flow[flow.length - 1]!;\n\treturn last.y + last.height;\n}\n\n// ---------------------------------------------------------------------------\n// Reactive graph factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive block layout graph for mixed content (text + image + SVG).\n *\n * ```\n * Graph(\"reactive-block-layout\")\n * ├── node([], { initial: \"blocks\" }) — ContentBlock[] input\n * ├── node([], { initial: \"max-width\" }) — container constraint\n * ├── node([], { initial: \"gap\" }) — vertical gap (px)\n * ├── derived(\"measured-blocks\") — blocks + max-width → MeasuredBlock[]\n * ├── derived(\"block-flow\") — measured-blocks + gap → PositionedBlock[]\n * ├── derived(\"total-height\") — block-flow → number\n * └── meta: { block-count, layout-time-ns }\n * ```\n */\nexport function reactiveBlockLayout(opts: ReactiveBlockLayoutOptions): ReactiveBlockLayoutBundle {\n\tconst {\n\t\tadapters,\n\t\tname = \"reactive-block-layout\",\n\t\tdefaultFont = \"16px sans-serif\",\n\t\tdefaultLineHeight = 20,\n\t} = opts;\n\tconst g = new Graph(name);\n\n\t// Shared text measurement cache (same structure as reactiveLayout)\n\tconst measureCache = new Map<string, Map<string, number>>();\n\n\t// --- State nodes ---\n\tconst blocksNode = node<ContentBlock[]>([], { name: \"blocks\", initial: opts.blocks ?? [] });\n\tconst maxWidthNode = node<number>([], {\n\t\tname: \"max-width\",\n\t\tinitial: Math.max(0, opts.maxWidth ?? 800),\n\t});\n\tconst gapNode = node<number>([], { name: \"gap\", initial: opts.gap ?? 0 });\n\n\t// --- Derived: measured-blocks ---\n\t// Raw `node(...)` instead of `derived(...)` so the fn can return a\n\t// cleanup function. Core fires function-form cleanup on INVALIDATE (see\n\t// `node.ts:_updateState` INVALIDATE branch), which lets us flush the\n\t// measure cache and the downstream adapter cache reactively — replaces\n\t// the old v4 per-node `onMessage` hook that watched for INVALIDATE.\n\tconst measuredBlocksNode: Node<MeasuredBlock[]> = node<MeasuredBlock[]>(\n\t\t[blocksNode, maxWidthNode],\n\t\t(data, actions, ctx) => {\n\t\t\tconst blocksVal = data[0] != null && data[0].length > 0 ? data[0].at(-1) : ctx.prevData[0];\n\t\t\tconst mwVal = data[1] != null && data[1].length > 0 ? data[1].at(-1) : ctx.prevData[1];\n\t\t\tconst t0 = monotonicNs();\n\t\t\tconst result = measureBlocks(\n\t\t\t\tblocksVal as ContentBlock[],\n\t\t\t\tmwVal as number,\n\t\t\t\tadapters,\n\t\t\t\tmeasureCache,\n\t\t\t\tdefaultFont,\n\t\t\t\tdefaultLineHeight,\n\t\t\t);\n\t\t\tconst elapsed = monotonicNs() - t0;\n\n\t\t\t// Phase-3 meta deferral (parity with reactiveLayout)\n\t\t\tconst meta = measuredBlocksNode.meta;\n\t\t\tif (meta) {\n\t\t\t\temitToMeta(meta[\"block-count\"], result.length);\n\t\t\t\temitToMeta(meta[\"layout-time-ns\"], elapsed);\n\t\t\t}\n\n\t\t\tactions.emit(result);\n\n\t\t\t// Object-form cleanup: flush on deactivation + INVALIDATE only,\n\t\t\t// NOT before fn re-runs. Preserves cached measurements across\n\t\t\t// block edits so a single-block change doesn't wipe entries from\n\t\t\t// the other blocks. Image/SVG measurers don't expose a cache hook.\n\t\t\tconst flush = (): void => {\n\t\t\t\tmeasureCache.clear();\n\t\t\t\tadapters.text.clearCache?.();\n\t\t\t};\n\t\t\treturn { onDeactivation: flush, onInvalidate: flush };\n\t\t},\n\t\t{\n\t\t\tname: \"measured-blocks\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tmeta: { \"block-count\": 0, \"layout-time-ns\": 0 },\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst ma = a as MeasuredBlock[] | null;\n\t\t\t\tconst mb = b as MeasuredBlock[] | null;\n\t\t\t\tif (ma == null || mb == null) return ma === mb;\n\t\t\t\tif (ma.length !== mb.length) return false;\n\t\t\t\tfor (let i = 0; i < ma.length; i++) {\n\t\t\t\t\tconst ba = ma[i]!;\n\t\t\t\t\tconst bb = mb[i]!;\n\t\t\t\t\tif (\n\t\t\t\t\t\tba.type !== bb.type ||\n\t\t\t\t\t\tba.width !== bb.width ||\n\t\t\t\t\t\tba.height !== bb.height ||\n\t\t\t\t\t\tba.index !== bb.index\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Derived: block-flow ---\n\tconst blockFlowNode = node<PositionedBlock[]>(\n\t\t[measuredBlocksNode, gapNode],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tactions.emit(computeBlockFlow(data[0] as MeasuredBlock[], data[1] as number));\n\t\t},\n\t\t{\n\t\t\tname: \"block-flow\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst fa = a as PositionedBlock[] | null;\n\t\t\t\tconst fb = b as PositionedBlock[] | null;\n\t\t\t\tif (fa == null || fb == null) return fa === fb;\n\t\t\t\tif (fa.length !== fb.length) return false;\n\t\t\t\tfor (let i = 0; i < fa.length; i++) {\n\t\t\t\t\tconst pa = fa[i]!;\n\t\t\t\t\tconst pb = fb[i]!;\n\t\t\t\t\tif (pa.x !== pb.x || pa.y !== pb.y || pa.width !== pb.width || pa.height !== pb.height)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Derived: total-height ---\n\tconst totalHeightNode = node<number>(\n\t\t[blockFlowNode],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tactions.emit(computeTotalHeight(data[0] as PositionedBlock[]));\n\t\t},\n\t\t{ describeKind: \"derived\", name: \"total-height\" },\n\t);\n\n\t// --- Register in graph ---\n\tg.add(blocksNode, { name: \"blocks\" });\n\tg.add(maxWidthNode, { name: \"max-width\" });\n\tg.add(gapNode, { name: \"gap\" });\n\tg.add(measuredBlocksNode, { name: \"measured-blocks\" });\n\tg.add(blockFlowNode, { name: \"block-flow\" });\n\tg.add(totalHeightNode, { name: \"total-height\" });\n\n\t// --- Edges (for describe() visibility) ---\n\n\treturn {\n\t\tgraph: g,\n\t\tsetBlocks: (blocks: ContentBlock[]) => g.set(\"blocks\", blocks),\n\t\tsetMaxWidth: (mw: number) => g.set(\"max-width\", Math.max(0, mw)),\n\t\tsetGap: (gap: number) => g.set(\"gap\", gap),\n\t\tmeasuredBlocks: measuredBlocksNode,\n\t\tblockFlow: blockFlowNode,\n\t\ttotalHeight: totalHeightNode,\n\t};\n}\n","/**\n * Reactive flow layout — multi-column text flowing around shape obstacles\n * (roadmap §7.1, extends `reactiveLayout`).\n *\n * Unlike `reactiveLayout` (single-column, one `maxWidth`) and\n * `reactiveBlockLayout` (vertical stack of heterogeneous blocks), this engine\n * lays out a single stream of text across **N columns** while wrapping around\n * arbitrary **shape obstacles** (circles, rectangles). Each line's available\n * width can differ from every other line's, enabling magazine-style editorial\n * layouts — and the cursor carries seamlessly from column to column so text\n * never duplicates or gaps at boundaries.\n *\n * ```\n * Graph(\"reactive-flow-layout\")\n * ├── node([], { initial: \"text\" })\n * ├── node([], { initial: \"font\" })\n * ├── node([], { initial: \"line-height\" })\n * ├── node([], { initial: \"container\" }) — { width, height, paddingX, paddingY }\n * ├── node([], { initial: \"columns\" }) — { count, gap }\n * ├── node([], { initial: \"obstacles\" }) — Obstacle[] (moves reactively — rAF-friendly)\n * ├── derived(\"segments\") — text + font → PreparedSegment[] (from reactiveLayout)\n * ├── derived(\"flow-lines\") — segments + container + columns + obstacles + line-height\n * │ → PositionedLine[]\n * └── meta: { line-count, layout-time-ns, overflow-segments }\n * ```\n *\n * Obstacle positions change every frame; `flow-lines` re-runs per change, but\n * `segments` stays cached (text hasn't changed). Callers drive obstacles via a\n * reactive source like `fromRaf()` piped into a state node.\n */\nimport { monotonicNs, type Node, node } from \"@graphrefly/pure-ts/core\";\n\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\nimport { emitToMeta } from \"../../base/meta/emit-to-meta.js\";\nimport { getDefaultSegmentAdapter } from \"./measurement-adapters.js\";\nimport {\n\tanalyzeAndMeasure,\n\tcarveTextLineSlots,\n\ttype Interval,\n\ttype LayoutCursor,\n\tlayoutNextLine,\n\ttype MeasurementAdapter,\n\ttype PreparedSegment,\n\ttype SegmentAdapter,\n} from \"./reactive-layout.js\";\n\n// ---------------------------------------------------------------------------\n// Obstacle types\n// ---------------------------------------------------------------------------\n\n/** A circle obstacle. Center `(cx, cy)`, radius `r`; text keeps `padding` distance. */\nexport type CircleObstacle = {\n\tkind: \"circle\";\n\tcx: number;\n\tcy: number;\n\tr: number;\n\t/** Horizontal padding between obstacle and wrapped text (default 0). */\n\thPad?: number;\n\t/** Vertical padding — band overlap tolerance (default 0). */\n\tvPad?: number;\n};\n\n/** A rectangle obstacle. Top-left `(x, y)`, size `(w, h)`. */\nexport type RectObstacle = {\n\tkind: \"rect\";\n\tx: number;\n\ty: number;\n\tw: number;\n\th: number;\n\thPad?: number;\n\tvPad?: number;\n};\n\n/** Union of built-in obstacle shapes. */\nexport type Obstacle = CircleObstacle | RectObstacle;\n\n/**\n * Compute the horizontal interval occluded by a circle at vertical band\n * `[bandTop, bandBottom]`, or `null` if no occlusion.\n *\n * Exported so consumers that render obstacle outlines in sync with the flow\n * can reuse the same geometry the flow engine uses — no divergence.\n */\nexport function circleIntervalForBand(\n\to: CircleObstacle,\n\tbandTop: number,\n\tbandBottom: number,\n): Interval | null {\n\tconst hPad = o.hPad ?? 0;\n\tconst vPad = o.vPad ?? 0;\n\tconst top = bandTop - vPad;\n\tconst bottom = bandBottom + vPad;\n\tif (top >= o.cy + o.r || bottom <= o.cy - o.r) return null;\n\tconst minDy = o.cy >= top && o.cy <= bottom ? 0 : o.cy < top ? top - o.cy : o.cy - bottom;\n\tif (minDy >= o.r) return null;\n\tconst maxDx = Math.sqrt(o.r * o.r - minDy * minDy);\n\treturn { left: o.cx - maxDx - hPad, right: o.cx + maxDx + hPad };\n}\n\n/** Same as `circleIntervalForBand` for rectangles. */\nexport function rectIntervalForBand(\n\to: RectObstacle,\n\tbandTop: number,\n\tbandBottom: number,\n): Interval | null {\n\tconst hPad = o.hPad ?? 0;\n\tconst vPad = o.vPad ?? 0;\n\tif (bandBottom <= o.y - vPad) return null;\n\tif (bandTop >= o.y + o.h + vPad) return null;\n\treturn { left: o.x - hPad, right: o.x + o.w + hPad };\n}\n\nfunction obstacleIntervalForBand(\n\to: Obstacle,\n\tbandTop: number,\n\tbandBottom: number,\n): Interval | null {\n\treturn o.kind === \"circle\"\n\t\t? circleIntervalForBand(o, bandTop, bandBottom)\n\t\t: rectIntervalForBand(o, bandTop, bandBottom);\n}\n\n// ---------------------------------------------------------------------------\n// Flow layout types\n// ---------------------------------------------------------------------------\n\nexport type FlowContainer = {\n\twidth: number;\n\theight: number;\n\tpaddingX?: number;\n\tpaddingY?: number;\n};\n\nexport type FlowColumns = {\n\tcount: number;\n\tgap: number;\n};\n\n/** A single positioned line after flow layout. */\nexport type PositionedLine = {\n\tx: number;\n\ty: number;\n\t/** Natural measured width of the text content. */\n\twidth: number;\n\t/** Width of the slot this line was placed in — use this as the DOM element's\n\t * `width` when applying `text-align: justify` so the line stretches to the\n\t * obstacle edge on both sides. */\n\tslotWidth: number;\n\ttext: string;\n\t/** Which column index this line belongs to (0-based). */\n\tcolumnIndex: number;\n\t/** `true` iff the slot's right edge was carved short by an obstacle (the\n\t * slot sits to the LEFT of an obstacle). Renderers can right-align text\n\t * in these slots so single-word lines still hug the obstacle — CSS\n\t * `text-align: justify` can't stretch single-word lines, which otherwise\n\t * produces a visible asymmetry vs. the slot on the other side of the\n\t * obstacle (which is flush by default). */\n\tflushToRight: boolean;\n};\n\n/** Options for `reactiveFlowLayout`. */\nexport type ReactiveFlowLayoutOptions = {\n\tadapter: MeasurementAdapter;\n\t/**\n\t * Segmentation backend (optional). Defaults to a lazy {@link IntlSegmentAdapter};\n\t * **must be supplied on Hermes / RN** — see {@link SegmentAdapter}.\n\t */\n\tsegmentAdapter?: SegmentAdapter;\n\tname?: string;\n\ttext?: string;\n\tfont?: string;\n\tlineHeight?: number;\n\tcontainer?: FlowContainer;\n\tcolumns?: FlowColumns;\n\tobstacles?: Obstacle[];\n\t/** Minimum slot width (px) below which a slot is discarded rather than squeezed. Default `20`. */\n\tminSlotWidth?: number;\n\t/**\n\t * Vertical gap (px) inserted after a hard-break segment (the spacing\n\t * between paragraphs). When unset (or explicitly set to `null`), tracks\n\t * the current `lineHeight` reactively — one line-height of visible\n\t * paragraph gap, matching dense editorial layouts. Set to `0` for\n\t * paragraph-preserving layout that reclaims the break line; set larger\n\t * (e.g. `2 * lineHeight`) for looser manuscript settings. Reactive —\n\t * update via `setParagraphSpacing(n)` or restore to track-lineHeight\n\t * mode via `setParagraphSpacing(null)`.\n\t */\n\tparagraphSpacing?: number | null;\n};\n\n/** Result bundle from `reactiveFlowLayout`. */\nexport type ReactiveFlowLayoutBundle = {\n\tgraph: Graph;\n\tsetText: (text: string) => void;\n\tsetFont: (font: string) => void;\n\tsetLineHeight: (lh: number) => void;\n\tsetContainer: (c: FlowContainer) => void;\n\tsetColumns: (c: FlowColumns) => void;\n\tsetObstacles: (o: Obstacle[]) => void;\n\tsetParagraphSpacing: (ps: number | null) => void;\n\tsegments: Node<PreparedSegment[]>;\n\tflowLines: Node<PositionedLine[]>;\n};\n\n// ---------------------------------------------------------------------------\n// Pure flow-layout compute\n// ---------------------------------------------------------------------------\n\n/** Result of `computeFlowLines`. */\nexport type FlowLinesResult = {\n\t/** Positioned lines in render order (columns inner-ordered top-to-bottom). */\n\tlines: PositionedLine[];\n\t/** Cursor position after the last line was placed. If\n\t * `cursor.segmentIndex < segments.length`, the layout **truncated** — the\n\t * container couldn't fit all text. */\n\tcursor: LayoutCursor;\n};\n\n/** Optional tuning knobs for {@link computeFlowLines}. */\nexport type ComputeFlowLinesOptions = {\n\t/**\n\t * Vertical gap (px) inserted after a hard-break segment (the spacing\n\t * between paragraphs). Defaults to `lineHeight` — one line-height of\n\t * visible paragraph gap, which matches dense editorial layouts. Set to\n\t * `0` for paragraph-preserving layout that reclaims the break line;\n\t * set larger (e.g. `2 * lineHeight`) for looser manuscript settings.\n\t */\n\tparagraphSpacing?: number;\n\t/**\n\t * Segmentation adapter passed through to internal `layoutNextLine` calls\n\t * (the grapheme-slicing path triggers on partial-segment breaks). Defaults\n\t * to the lazy {@link IntlSegmentAdapter}; **wire a polyfilled\n\t * {@link SegmentAdapter} on Hermes / RN.**\n\t */\n\tsegmentAdapter?: SegmentAdapter;\n};\n\n/**\n * Lay out `segments` across N columns, wrapping each line around `obstacles`.\n * Pure function — no reactive wiring. Exported for testing and for consumers\n * who want to run flow layout outside a Graph.\n *\n * `carveTextLineSlots` guarantees left-to-right-ordered, non-overlapping slots,\n * so this function does not sort them.\n */\nexport function computeFlowLines(\n\tsegments: PreparedSegment[],\n\tcontainer: FlowContainer,\n\tcolumns: FlowColumns,\n\tobstacles: Obstacle[],\n\tlineHeight: number,\n\tminSlotWidth: number,\n\topts?: ComputeFlowLinesOptions,\n): FlowLinesResult {\n\tconst paragraphSpacing = opts?.paragraphSpacing ?? lineHeight;\n\tconst segmentAdapterCtx = opts?.segmentAdapter;\n\tconst lines: PositionedLine[] = [];\n\tlet cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 };\n\tif (segments.length === 0 || columns.count <= 0 || lineHeight <= 0) {\n\t\treturn { lines, cursor };\n\t}\n\n\tconst padX = container.paddingX ?? 0;\n\tconst padY = container.paddingY ?? 0;\n\tconst availWidth = Math.max(0, container.width - padX * 2);\n\tconst availHeight = Math.max(0, container.height - padY * 2);\n\tconst gapTotal = columns.gap * Math.max(0, columns.count - 1);\n\tconst colWidth = Math.max(0, (availWidth - gapTotal) / columns.count);\n\tif (colWidth <= 0) return { lines, cursor };\n\n\touterCol: for (let col = 0; col < columns.count; col++) {\n\t\tconst colLeft = padX + col * (colWidth + columns.gap);\n\t\tconst colRight = colLeft + colWidth;\n\t\tlet bandTop = padY;\n\n\t\twhile (bandTop + lineHeight <= padY + availHeight) {\n\t\t\tconst bandBottom = bandTop + lineHeight;\n\t\t\tconst blocked: Interval[] = [];\n\t\t\tfor (let oi = 0; oi < obstacles.length; oi++) {\n\t\t\t\tconst iv = obstacleIntervalForBand(obstacles[oi]!, bandTop, bandBottom);\n\t\t\t\tif (iv !== null) blocked.push(iv);\n\t\t\t}\n\t\t\tconst slots = carveTextLineSlots({ left: colLeft, right: colRight }, blocked, minSlotWidth);\n\n\t\t\tif (slots.length === 0) {\n\t\t\t\tbandTop += lineHeight;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet hardBreakThisBand = false;\n\t\t\tfor (let si = 0; si < slots.length; si++) {\n\t\t\t\tconst slot = slots[si]!;\n\t\t\t\tconst slotW = slot.right - slot.left;\n\t\t\t\tconst line = layoutNextLine(\n\t\t\t\t\tsegments,\n\t\t\t\t\tcursor,\n\t\t\t\t\tslotW,\n\t\t\t\t\tsegmentAdapterCtx ? { segmentAdapter: segmentAdapterCtx } : undefined,\n\t\t\t\t);\n\t\t\t\tif (line === null) {\n\t\t\t\t\treturn { lines, cursor };\n\t\t\t\t}\n\t\t\t\tif (line.text.length === 0 && line.width === 0) {\n\t\t\t\t\t// Hard-break — advance cursor past the break segment and end\n\t\t\t\t\t// THIS band so the break produces a visible paragraph gap\n\t\t\t\t\t// rather than being silently absorbed across remaining slots.\n\t\t\t\t\tcursor = line.end;\n\t\t\t\t\thardBreakThisBand = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tlines.push({\n\t\t\t\t\tx: slot.left,\n\t\t\t\t\ty: bandTop,\n\t\t\t\t\twidth: line.width,\n\t\t\t\t\tslotWidth: slotW,\n\t\t\t\t\ttext: line.text,\n\t\t\t\t\tcolumnIndex: col,\n\t\t\t\t\tflushToRight: slot.right < colRight - 0.5,\n\t\t\t\t});\n\t\t\t\tcursor = line.end;\n\t\t\t}\n\n\t\t\tif (hardBreakThisBand) {\n\t\t\t\t// Paragraph gap: `paragraphSpacing` (default lineHeight) of\n\t\t\t\t// vertical space after the break. `0` reclaims the break line;\n\t\t\t\t// larger values space paragraphs further apart.\n\t\t\t\tbandTop += paragraphSpacing;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbandTop += lineHeight;\n\t\t\tif (cursor.segmentIndex >= segments.length) break outerCol;\n\t\t}\n\n\t\tif (cursor.segmentIndex >= segments.length) break;\n\t}\n\n\treturn { lines, cursor };\n}\n\n// ---------------------------------------------------------------------------\n// Reactive graph factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive flow-layout graph: N columns of text wrapping around\n * shape obstacles. Re-runs only the dependent derived nodes on any input\n * change. Obstacle movement (e.g. rAF-driven) invalidates `flow-lines` only;\n * `segments` stays cached as long as `text`/`font` don't change.\n *\n * @example\n * ```ts\n * import { fromRaf, reactiveFlowLayout } from \"@graphrefly/graphrefly-ts\";\n *\n * const flow = reactiveFlowLayout({\n * adapter: new CanvasMeasureAdapter(),\n * text: longEssay,\n * font: \"18px serif\",\n * lineHeight: 26,\n * container: { width: 900, height: 600, paddingX: 40, paddingY: 40 },\n * columns: { count: 2, gap: 32 },\n * obstacles: [{ kind: \"circle\", cx: 450, cy: 300, r: 80 }],\n * });\n *\n * // Animate the obstacle via rAF:\n * fromRaf().subscribe(([[, t]]) => {\n * const x = 450 + 120 * Math.sin((t as number) * 0.001);\n * flow.setObstacles([{ kind: \"circle\", cx: x, cy: 300, r: 80 }]);\n * });\n * ```\n */\nexport function reactiveFlowLayout(opts: ReactiveFlowLayoutOptions): ReactiveFlowLayoutBundle {\n\tconst {\n\t\tadapter,\n\t\tsegmentAdapter: segmentAdapterOpt,\n\t\tname = \"reactive-flow-layout\",\n\t\tminSlotWidth = 20,\n\t} = opts;\n\t// Eager resolve — fail-fast on Hermes-without-explicit-adapter at factory construction.\n\tconst segmentAdapter: SegmentAdapter = segmentAdapterOpt ?? getDefaultSegmentAdapter();\n\tconst g = new Graph(name);\n\n\tconst measureCache = new Map<string, Map<string, number>>();\n\n\tconst textNode = node<string>([], { name: \"text\", initial: opts.text ?? \"\" });\n\tconst fontNode = node<string>([], { name: \"font\", initial: opts.font ?? \"16px sans-serif\" });\n\tconst lineHeightNode = node<number>([], { name: \"line-height\", initial: opts.lineHeight ?? 20 });\n\tconst containerNode = node<FlowContainer>([], {\n\t\tname: \"container\",\n\t\tinitial: opts.container ?? { width: 800, height: 600, paddingX: 0, paddingY: 0 },\n\t});\n\tconst columnsNode = node<FlowColumns>([], {\n\t\tname: \"columns\",\n\t\tinitial: opts.columns ?? { count: 1, gap: 0 },\n\t});\n\tconst obstaclesNode = node<Obstacle[]>([], { name: \"obstacles\", initial: opts.obstacles ?? [] });\n\t// `paragraphSpacing` is reactive with a \"track lineHeight\" default. The\n\t// state node holds `number | null` — when `null`, `flow-lines` substitutes\n\t// the CURRENT `lineHeight` value, so the default stays truly reactive\n\t// as lineHeight updates. A caller who wants a fixed independent gap sets\n\t// an explicit number via the constructor or `setParagraphSpacing`; passing\n\t// `null` back restores the track-lineHeight behavior.\n\tconst paragraphSpacingNode = node<number | null>([], {\n\t\tname: \"paragraph-spacing\",\n\t\tinitial: opts.paragraphSpacing ?? null,\n\t});\n\n\tconst segmentsNode: Node<PreparedSegment[]> = node<PreparedSegment[]>(\n\t\t[textNode, fontNode],\n\t\t(data, actions, ctx) => {\n\t\t\tconst b0 = data[0];\n\t\t\tconst textVal = (b0 != null && b0.length > 0 ? b0.at(-1) : ctx.prevData[0]) as string;\n\t\t\tconst b1 = data[1];\n\t\t\tconst fontVal = (b1 != null && b1.length > 0 ? b1.at(-1) : ctx.prevData[1]) as string;\n\t\t\tconst result = analyzeAndMeasure(\n\t\t\t\ttextVal,\n\t\t\t\tfontVal,\n\t\t\t\tadapter,\n\t\t\t\tmeasureCache,\n\t\t\t\tundefined,\n\t\t\t\tsegmentAdapter,\n\t\t\t);\n\t\t\tactions.emit(result);\n\t\t\t// Flush on deactivation + INVALIDATE only — preserve cache across\n\t\t\t// fn re-runs so text/font edits don't wipe per-segment entries that\n\t\t\t// still match the new text.\n\t\t\tconst flush = (): void => {\n\t\t\t\tmeasureCache.clear();\n\t\t\t\tadapter.clearCache?.();\n\t\t\t};\n\t\t\treturn { onDeactivation: flush, onInvalidate: flush };\n\t\t},\n\t\t{ name: \"segments\", describeKind: \"derived\" },\n\t);\n\n\tconst flowLinesNode = node<PositionedLine[]>(\n\t\t[segmentsNode, containerNode, columnsNode, obstaclesNode, lineHeightNode, paragraphSpacingNode],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst segments = data[0] as PreparedSegment[];\n\t\t\tconst t0 = monotonicNs();\n\t\t\t// `ps === null` → track current lineHeight. Any explicit number\n\t\t\t// (0, 60, …) overrides; passing `null` via `setParagraphSpacing`\n\t\t\t// restores tracking.\n\t\t\tconst effectiveSpacing = (data[5] as number | null) ?? (data[4] as number);\n\t\t\tconst { lines: result, cursor } = computeFlowLines(\n\t\t\t\tsegments,\n\t\t\t\tdata[1] as FlowContainer,\n\t\t\t\tdata[2] as FlowColumns,\n\t\t\t\tdata[3] as Obstacle[],\n\t\t\t\tdata[4] as number,\n\t\t\t\tminSlotWidth,\n\t\t\t\t{ paragraphSpacing: effectiveSpacing, segmentAdapter },\n\t\t\t);\n\t\t\tconst elapsed = monotonicNs() - t0;\n\t\t\t// Overflow signal: segments left unlaid-out after the container is\n\t\t\t// exhausted. `0` means all text fit; `N > 0` means the container\n\t\t\t// occluded/overflowed N segments — consumers can surface a \"…more\"\n\t\t\t// indicator, grow the container, or discard obstacles.\n\t\t\tconst overflow = Math.max(0, segments.length - cursor.segmentIndex);\n\t\t\tconst meta = flowLinesNode.meta;\n\t\t\tif (meta) {\n\t\t\t\temitToMeta(meta[\"line-count\"], result.length);\n\t\t\t\temitToMeta(meta[\"layout-time-ns\"], elapsed);\n\t\t\t\temitToMeta(meta[\"overflow-segments\"], overflow);\n\t\t\t}\n\t\t\tactions.emit(result);\n\t\t},\n\t\t{\n\t\t\tdescribeKind: \"derived\",\n\t\t\tname: \"flow-lines\",\n\t\t\tmeta: {\n\t\t\t\t\"line-count\": 0,\n\t\t\t\t\"layout-time-ns\": 0,\n\t\t\t\t\"overflow-segments\": 0,\n\t\t\t},\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst la = a as PositionedLine[];\n\t\t\t\tconst lb = b as PositionedLine[];\n\t\t\t\tif (la.length !== lb.length) return false;\n\t\t\t\tfor (let i = 0; i < la.length; i++) {\n\t\t\t\t\tconst pa = la[i]!;\n\t\t\t\t\tconst pb = lb[i]!;\n\t\t\t\t\tif (\n\t\t\t\t\t\tpa.x !== pb.x ||\n\t\t\t\t\t\tpa.y !== pb.y ||\n\t\t\t\t\t\tpa.width !== pb.width ||\n\t\t\t\t\t\tpa.slotWidth !== pb.slotWidth ||\n\t\t\t\t\t\tpa.text !== pb.text ||\n\t\t\t\t\t\tpa.columnIndex !== pb.columnIndex ||\n\t\t\t\t\t\tpa.flushToRight !== pb.flushToRight\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\tg.add(textNode, { name: \"text\" });\n\tg.add(fontNode, { name: \"font\" });\n\tg.add(lineHeightNode, { name: \"line-height\" });\n\tg.add(containerNode, { name: \"container\" });\n\tg.add(columnsNode, { name: \"columns\" });\n\tg.add(obstaclesNode, { name: \"obstacles\" });\n\tg.add(paragraphSpacingNode, { name: \"paragraph-spacing\" });\n\tg.add(segmentsNode, { name: \"segments\" });\n\tg.add(flowLinesNode, { name: \"flow-lines\" });\n\n\treturn {\n\t\tgraph: g,\n\t\tsetText: (t: string) => g.set(\"text\", t),\n\t\tsetFont: (f: string) => g.set(\"font\", f),\n\t\tsetLineHeight: (lh: number) => g.set(\"line-height\", lh),\n\t\tsetContainer: (c: FlowContainer) => g.set(\"container\", c),\n\t\tsetColumns: (c: FlowColumns) => g.set(\"columns\", c),\n\t\tsetObstacles: (o: Obstacle[]) => g.set(\"obstacles\", o),\n\t\tsetParagraphSpacing: (ps: number | null) => g.set(\"paragraph-spacing\", ps),\n\t\tsegments: segmentsNode,\n\t\tflowLines: flowLinesNode,\n\t};\n}\n"],"mappings":"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","names":["monotonicNs","node","Graph","Graph","node","monotonicNs"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
policyGate
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3YGXPUHW.js";
|
|
4
4
|
import {
|
|
5
5
|
TopicGraph
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DHDCOOJU.js";
|
|
7
7
|
import {
|
|
8
8
|
domainMeta
|
|
9
9
|
} from "./chunk-FMPF42Q4.js";
|
|
@@ -144,11 +144,12 @@ var GuardedExecutionGraph = class extends Graph {
|
|
|
144
144
|
this.addDisposer(keepalive(this.scope));
|
|
145
145
|
const scopedHandle = target.describe({
|
|
146
146
|
reactive: true,
|
|
147
|
-
// F8 (
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// (full visibility)
|
|
151
|
-
//
|
|
147
|
+
// F8 (resolved 2026-05-18): `GraphDescribeOptions.actor` is now
|
|
148
|
+
// `Actor | Node<Actor | null>`, so `_actorNode` (a
|
|
149
|
+
// `Node<Actor | null>`) passes without a cast. A `null`/`undefined`
|
|
150
|
+
// cache resolves to "no scoping" (full visibility) per
|
|
151
|
+
// `resolveActorOption`. See graph.ts § "Cache-undefined/null
|
|
152
|
+
// semantics".
|
|
152
153
|
actor: this._actorNode,
|
|
153
154
|
reactiveName: "scopedDescribe"
|
|
154
155
|
});
|
|
@@ -194,11 +195,11 @@ var GuardedExecutionGraph = class extends Graph {
|
|
|
194
195
|
const actorNode = actorOverride == null ? this._actorNode : isNode(actorOverride) ? actorOverride : node([], { name: "actor_override", initial: actorOverride });
|
|
195
196
|
const handle = this._target.describe({
|
|
196
197
|
reactive: true,
|
|
197
|
-
// `
|
|
198
|
-
//
|
|
199
|
-
// `
|
|
200
|
-
//
|
|
201
|
-
//
|
|
198
|
+
// F8 (resolved 2026-05-18): `actor` accepts `Node<Actor | null>`
|
|
199
|
+
// directly. `actorNode` is `_actorNode` (`Node<Actor | null>`) or
|
|
200
|
+
// an override (`Node<Actor>`/`Node<Actor | null>`); both assign
|
|
201
|
+
// without a cast. `_describeReactive` resolves via
|
|
202
|
+
// `resolveActorOption` (null/undefined cache → no scoping).
|
|
202
203
|
actor: actorNode,
|
|
203
204
|
...opts ?? {}
|
|
204
205
|
});
|
|
@@ -218,4 +219,4 @@ export {
|
|
|
218
219
|
GuardedExecutionGraph,
|
|
219
220
|
guardedExecution
|
|
220
221
|
};
|
|
221
|
-
//# sourceMappingURL=chunk-
|
|
222
|
+
//# sourceMappingURL=chunk-NBK6QQMG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/presets/inspect/guarded-execution.ts"],"sourcesContent":["/**\n * Composable safety layer (roadmap §9.0b — Tier 5.1 Wave-B rebuild).\n *\n * {@link guardedExecution} wraps any {@link Graph} with:\n *\n * - {@link policyGate} — reactive ABAC (Tier 2.3 rename of `policyEnforcer`),\n * policies stored as a `Node` so LLMs / humans can update them at runtime.\n * Full transitive dynamic coverage via `watchTopologyTree`.\n * - Reactive {@link GuardedExecutionGraph.scopedDescribeNode} — a thin\n * delegate over `target.describe({ reactive: true, actor })` that re-derives\n * on every settle (topology change, error transition, actor swap).\n * - The enforcer's `violations` topic is republished as `violations` on\n * the wrapper, composable with {@link graphLens}'s `health`.\n * - The wrapper-level `lints` topic surfaces non-policy diagnostic warnings\n * (`empty-policies` / `audit-no-effect` / `no-actor`) so misconfigurations\n * are caught reactively rather than via thrown errors at scattered call sites.\n * - The `scope` derived publishes the current configuration tuple\n * (`{actor, mode, policiesCount}`) for dashboards.\n *\n * V1 scope: policies + actor + reactive scoped describe + lints + scope.\n * Budget-as-option is NOT in V1 — it requires a cost-tracking design that\n * hasn't landed yet. Callers who need a budget limit today append a\n * budget-aware {@link PolicyRuleData} to the policies list (check current\n * cost and `deny` when exhausted).\n *\n * @module\n */\nimport type { Actor, PolicyRuleData } from \"@graphrefly/pure-ts/core\";\nimport { DATA, monotonicNs, type Node, node } from \"@graphrefly/pure-ts/core\";\nimport { keepalive } from \"@graphrefly/pure-ts/extra\";\nimport {\n\ttype DescribeFilter,\n\tGraph,\n\ttype GraphDescribeOptions,\n\ttype GraphDescribeOutput,\n\ttype GraphOptions,\n} from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport {\n\ttype PolicyGateGraph,\n\ttype PolicyViolation,\n\tpolicyGate,\n} from \"../../utils/inspect/audit.js\";\nimport { TopicGraph } from \"../../utils/messaging/index.js\";\n\nfunction isNode<T>(x: unknown): x is Node<T> {\n\treturn (\n\t\ttypeof x === \"object\" && x !== null && \"subscribe\" in (x as object) && \"down\" in (x as object)\n\t);\n}\n\nfunction guardedMeta(kind: string): Record<string, unknown> {\n\treturn domainMeta(\"guarded\", kind);\n}\n\n/** Diagnostic warning published on {@link GuardedExecutionGraph.lints}. */\nexport interface GuardedExecutionLint {\n\t/**\n\t * - `\"empty-policies\"` — `policies` Node emitted an empty array in\n\t * `mode: \"enforce\"`. Static empty arrays throw at construction; this\n\t * covers the reactive case.\n\t * - `\"audit-no-effect\"` — `mode: \"audit\"` plus the target has no per-node\n\t * guards, so `scopedDescribeNode` filters by per-node guards only and\n\t * policies will never gate visibility (they still populate `violations`\n\t * on writes).\n\t * - `\"no-actor\"` — neither a wrapper-configured nor per-call actor was\n\t * supplied. `scopedDescribeNode` falls back to \"describe everything\"\n\t * for the corresponding subscription.\n\t */\n\tkind: \"empty-policies\" | \"audit-no-effect\" | \"no-actor\";\n\tmessage: string;\n\ttimestamp_ns: number;\n}\n\n/** Configuration tuple published on {@link GuardedExecutionGraph.scope}. */\nexport interface GuardedScope {\n\t/** The wrapper-configured default actor, or `null` when none configured. */\n\tactor: Actor | null;\n\tmode: \"audit\" | \"enforce\";\n\t/** Current policy count (reactive — re-emits on `policies` Node updates). */\n\tpoliciesCount: number;\n}\n\n/** Options for {@link guardedExecution}. */\nexport interface GuardedExecutionOptions {\n\t/**\n\t * Policies enforced against every guarded write. Static list or a live\n\t * `Node<readonly PolicyRuleData[]>` (LLM-updatable).\n\t *\n\t * **Empty-policies handling:**\n\t * - Static empty array + `mode: \"enforce\"` throws `RangeError` at\n\t * construction (deny-by-default is almost certainly a misconfiguration).\n\t * - Node-supplied empty array + `mode: \"enforce\"` emits a one-time\n\t * `\"empty-policies\"` lint on first such emission (the wrapper can't\n\t * throw mid-run — surface the warning reactively).\n\t * - `mode: \"audit\"` tolerates empty policies (no guards stacked; policies\n\t * only feed the `violations` channel on writes).\n\t */\n\tpolicies: readonly PolicyRuleData[] | Node<readonly PolicyRuleData[]>;\n\t/**\n\t * Default actor used when the caller invokes\n\t * {@link GuardedExecutionGraph.scopedDescribeNode} without an override.\n\t * Accepts a static {@link Actor} or a `Node<Actor>` — when a Node is\n\t * supplied, the reactive describe re-derives on every actor emission so\n\t * harnesses binding a per-turn actor get a single live describe Node\n\t * instead of re-creating one per turn.\n\t *\n\t * Omit to scope per-call only. A `\"no-actor\"` lint fires once per instance\n\t * if neither a configured nor per-call actor is ever supplied (the\n\t * resulting describe is unscoped — full visibility).\n\t */\n\tactor?: Actor | Node<Actor>;\n\t/**\n\t * `\"enforce\"` (default) — push guards onto target nodes so disallowed\n\t * writes throw {@link GuardDenied}.\n\t * `\"audit\"` — record would-be denials to the `violations` topic without\n\t * blocking writes.\n\t */\n\tmode?: \"audit\" | \"enforce\";\n\t/** Ring-buffer cap for the `violations` topic. Default 1000 (inherited from policyGate). */\n\tviolationsLimit?: number;\n\t/** Ring-buffer cap for the `lints` topic. Default 64 — each lint kind fires at most once per instance. */\n\tlintsLimit?: number;\n\t/** Wrapper graph name. Default `${target.name}_guarded`. */\n\tname?: string;\n\t/** Wrapper graph options. */\n\tgraph?: GraphOptions;\n}\n\n/**\n * Wrapper over a target {@link Graph} providing reactive ABAC + reactive\n * scoped describe + diagnostic lints. Mounts a {@link PolicyGateGraph} under\n * `enforcer`, a {@link TopicGraph} of {@link GuardedExecutionLint} under\n * `lints`, and a `scope` derived publishing `{actor, mode, policiesCount}`.\n *\n * @category patterns\n */\nexport class GuardedExecutionGraph extends Graph {\n\treadonly enforcer: PolicyGateGraph;\n\treadonly violations: TopicGraph<PolicyViolation>;\n\treadonly lints: TopicGraph<GuardedExecutionLint>;\n\treadonly scope: Node<GuardedScope>;\n\t/**\n\t * Canonical reactive describe scoped to the wrapper's configured `actor`.\n\t * Subscribes ONCE at construction; lifecycle owned by the wrapper (disposed\n\t * on `wrapper.destroy()`). Use this property for the common case\n\t * (long-lived consumer wanting \"describe scoped to my actor\"); use\n\t * {@link scopedDescribeNode} only when a per-call actor override or\n\t * different `detail`/`fields` is required.\n\t *\n\t * Re-derives on every settle of the target graph: structural changes,\n\t * status transitions (errors flip nodes into `\"errored\"`), and actor\n\t * emissions (when a `Node<Actor>` is bound, including the SENTINEL bridge\n\t * applied internally — see qa G1B).\n\t */\n\treadonly scopedDescribe: Node<GraphDescribeOutput>;\n\tprivate readonly _target: Graph;\n\tprivate readonly _actorNode: Node<Actor | null>;\n\tprivate readonly _mode: \"audit\" | \"enforce\";\n\tprivate readonly _firedLintKinds = new Set<GuardedExecutionLint[\"kind\"]>();\n\n\tconstructor(target: Graph, opts: GuardedExecutionOptions) {\n\t\tsuper(opts.name ?? `${target.name}_guarded`, opts.graph);\n\t\tthis._target = target;\n\t\tthis._mode = opts.mode ?? \"enforce\";\n\n\t\tconst policiesOpt = opts.policies;\n\t\tconst policiesIsNode = isNode<readonly PolicyRuleData[]>(policiesOpt);\n\t\t// Static empty + enforce → throw (deny-by-default = misconfig).\n\t\tif (\n\t\t\t!policiesIsNode &&\n\t\t\tthis._mode === \"enforce\" &&\n\t\t\t(policiesOpt as readonly PolicyRuleData[]).length === 0\n\t\t) {\n\t\t\tthrow new RangeError(\n\t\t\t\t'guardedExecution: empty `policies` in `mode: \"enforce\"` denies every action. ' +\n\t\t\t\t\t'Pass at least `{ effect: \"allow\", action: \"*\" }` and layer deny rules on top.',\n\t\t\t);\n\t\t}\n\n\t\t// Lints topic — mounted before any potential first-DATA emission.\n\t\tthis.lints = new TopicGraph<GuardedExecutionLint>(\"lints\", {\n\t\t\tretainedLimit: opts.lintsLimit ?? 64,\n\t\t});\n\t\tthis.mount(\"lints\", this.lints);\n\n\t\t// Normalize `actor` to a Node<Actor | null>. `null` (a valid DATA in\n\t\t// the v5 sentinel-as-undefined model) means \"no actor configured\" —\n\t\t// `target.describe({ reactive: true })`'s `resolveActorOption` treats\n\t\t// `null` cache as \"no scoping\" (full visibility), and the `scope`\n\t\t// derived can publish `actor: null` cleanly. Using `node([], { initial: undefined })`\n\t\t// here would leave the actor node in sentinel state, which never fires\n\t\t// DATA on subscribe and would block the `scope` derived's first-run\n\t\t// gate from completing.\n\t\t//\n\t\t// qa G1B (EC2 fix): when the caller passes a `Node<Actor>`, that node's\n\t\t// cache may itself be SENTINEL at construction (e.g. a `producer`\n\t\t// awaiting first emission, or a deferred `derived`). Forwarding the raw\n\t\t// Node would re-introduce the same sentinel-stall on `scope`. Bridge\n\t\t// through a node bridge with a `null` initial so the\n\t\t// internal `_actorNode` always carries non-sentinel cache; the bridge\n\t\t// re-emits whenever the caller's Node emits, and forwards `null`\n\t\t// through unchanged.\n\t\tconst actorOpt = opts.actor;\n\t\tif (actorOpt == null) {\n\t\t\tthis._actorNode = node<Actor | null>([], { name: \"actor\", initial: null });\n\t\t} else if (isNode<Actor>(actorOpt)) {\n\t\t\tthis._actorNode = node<Actor | null>(\n\t\t\t\t[actorOpt],\n\t\t\t\t(batchData, actions, ctx) => {\n\t\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t\t);\n\t\t\t\t\tactions.emit((data[0] as Actor | null | undefined) ?? null);\n\t\t\t\t},\n\t\t\t\t{ describeKind: \"derived\", name: \"actor\", initial: null },\n\t\t\t);\n\t\t} else {\n\t\t\tthis._actorNode = node<Actor | null>([], { name: \"actor\", initial: actorOpt });\n\t\t}\n\n\t\t// Mount the enforcer.\n\t\tconst enforcerOpts: { mode: \"audit\" | \"enforce\"; name: string; violationsLimit?: number } = {\n\t\t\tmode: this._mode,\n\t\t\tname: \"enforcer\",\n\t\t};\n\t\tif (opts.violationsLimit != null) enforcerOpts.violationsLimit = opts.violationsLimit;\n\t\tthis.enforcer = policyGate(target, opts.policies, enforcerOpts);\n\t\tthis.violations = this.enforcer.violations;\n\t\tthis.mount(\"enforcer\", this.enforcer);\n\n\t\t// Empty-policies one-time lint (Node form, enforce mode only).\n\t\tif (policiesIsNode && this._mode === \"enforce\") {\n\t\t\tconst policiesNode = policiesOpt as Node<readonly PolicyRuleData[]>;\n\t\t\t// Two paths here intentionally:\n\t\t\t// (1) Synchronous cache seed — fires immediately if the Node was\n\t\t\t// constructed with an empty array as its current cache. This\n\t\t\t// covers `node<…[]>([], { initial: [] })` and any pre-emitted derived.\n\t\t\t// (2) Subscribe path — catches subsequent empty emissions AND\n\t\t\t// handles the SENTINEL case (cache=undefined at construction)\n\t\t\t// where the Node fires its first DATA after the wrapper is\n\t\t\t// built. The `_firedLintKinds` Set keeps the lint one-shot\n\t\t\t// across both paths.\n\t\t\t// qa F6 (deferred): if a Node's initial cache is SENTINEL (undefined)\n\t\t\t// AND it never emits an empty array (only ever non-empty), the lint\n\t\t\t// never fires — that's correct behavior, the configuration is\n\t\t\t// effectively non-empty for the wrapper's lifetime.\n\t\t\tconst cached = policiesNode.cache as readonly PolicyRuleData[] | undefined;\n\t\t\tif (cached != null && cached.length === 0) {\n\t\t\t\tthis._fireLint(\n\t\t\t\t\t\"empty-policies\",\n\t\t\t\t\t'`policies` Node cached an empty array in `mode: \"enforce\"` — every action will be denied. Add at least one allow rule.',\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst offEmpty = policiesNode.subscribe((msgs) => {\n\t\t\t\tfor (const m of msgs) {\n\t\t\t\t\tif (m[0] !== DATA) continue;\n\t\t\t\t\tconst v = m[1] as readonly PolicyRuleData[] | undefined;\n\t\t\t\t\tif (v == null || v.length === 0) {\n\t\t\t\t\t\tthis._fireLint(\n\t\t\t\t\t\t\t\"empty-policies\",\n\t\t\t\t\t\t\t'`policies` Node emitted an empty array in `mode: \"enforce\"` — every action will be denied. Add at least one allow rule.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.addDisposer(offEmpty);\n\t\t}\n\n\t\t// Audit-mode + no per-node guards on the target → fire once.\n\t\t// qa EC5 (deferred): the check is one-shot at construction. If the\n\t\t// caller later mounts a guarded node into the target, the lint stays\n\t\t// retained even though the configuration is now effective. Recommended\n\t\t// pattern: mount per-node guards on the target BEFORE wrapping.\n\t\t// Reactive recompute (subscribe to target.topology, clear lint when a\n\t\t// guard appears) is filed in `docs/optimizations.md` as a follow-up.\n\t\tif (this._mode === \"audit\") {\n\t\t\tconst described = target.describe({ detail: \"full\" });\n\t\t\tconst anyGuard = Object.values(described.nodes).some((n) => n.guard != null);\n\t\t\tif (!anyGuard) {\n\t\t\t\tthis._fireLint(\n\t\t\t\t\t\"audit-no-effect\",\n\t\t\t\t\t'`mode: \"audit\"` + target has no per-node guards — `scopedDescribeNode` filters by per-node guards only, so policy rules will not affect describe() visibility. Policies still populate the `violations` topic on writes.',\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// No-actor lint: fires at construction when neither configured nor\n\t\t// implicitly carried via a Node-cache. Per-call overrides on\n\t\t// `scopedDescribeNode` cannot retroactively suppress this — the\n\t\t// configured-default branch is what callers most often miss.\n\t\tif (actorOpt == null) {\n\t\t\tthis._fireLint(\n\t\t\t\t\"no-actor\",\n\t\t\t\t\"no actor configured — `wrapper.scopedDescribe` and `scopedDescribeNode()` will return an unscoped describe (full visibility) unless a per-call actor is supplied.\",\n\t\t\t);\n\t\t}\n\n\t\t// Scope derived: live tuple of {actor, mode, policiesCount}.\n\t\tthis.scope = node<GuardedScope>(\n\t\t\t[this._actorNode, this.enforcer.policies],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tactions.emit({\n\t\t\t\t\tactor: (data[0] as Actor | null | undefined) ?? null,\n\t\t\t\t\tmode: this._mode,\n\t\t\t\t\tpoliciesCount: (data[1] as readonly PolicyRuleData[]).length,\n\t\t\t\t});\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"scope\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: guardedMeta(\"scope\"),\n\t\t\t},\n\t\t);\n\t\tthis.add(this.scope, { name: \"scope\" });\n\t\tthis.addDisposer(keepalive(this.scope));\n\n\t\t// qa G1A \"same concept\": single canonical reactive describe bound to\n\t\t// the configured actor, mounted once at construction. Removes the\n\t\t// per-call handle proliferation from the prior `scopedDescribeNode`\n\t\t// pattern (the method is retained as the per-call escape hatch). The\n\t\t// reactive describe re-derives on actor swap / topology change /\n\t\t// status transition; lifecycle owned by the wrapper.\n\t\tconst scopedHandle = target.describe({\n\t\t\treactive: true,\n\t\t\t// F8 (Tier 5.2): `_actorNode` is `Node<Actor | null>`. The cast is\n\t\t\t// safe at runtime per the `resolveActorOption` null-tolerance\n\t\t\t// contract — `null` / `undefined` cache is treated as \"no scoping\"\n\t\t\t// (full visibility). See `scopedDescribeNode` for the matching\n\t\t\t// per-call site and graph.ts § \"Cache-undefined semantics\".\n\t\t\tactor: this._actorNode as Node<Actor>,\n\t\t\treactiveName: \"scopedDescribe\",\n\t\t});\n\t\tthis.scopedDescribe = scopedHandle.node;\n\t\tthis.add(this.scopedDescribe, { name: \"scopedDescribe\" });\n\t\tthis.addDisposer(scopedHandle.dispose);\n\t}\n\n\tprivate _fireLint(kind: GuardedExecutionLint[\"kind\"], message: string): void {\n\t\tif (this._firedLintKinds.has(kind)) return;\n\t\tthis._firedLintKinds.add(kind);\n\t\tthis.lints.publish({ kind, message, timestamp_ns: monotonicNs() });\n\t}\n\n\t/**\n\t * **Per-call escape hatch.** Prefer {@link scopedDescribe} (the mounted\n\t * property) for the common case of \"describe scoped to my actor.\" Use\n\t * this method ONLY when you need a per-call actor override or different\n\t * `detail`/`fields`/`filter`.\n\t *\n\t * Returns a live `Node<GraphDescribeOutput>` scoped to the supplied (or\n\t * configured) actor, plus an explicit `dispose` for caller-controlled\n\t * lifecycle. Re-derives on every settle of the target graph: structural\n\t * changes, status transitions, and actor emissions (when a `Node<Actor>`\n\t * is bound).\n\t *\n\t * **Lifecycle (qa G1A — EC1 fix).** Each call instantiates a fresh\n\t * `target.describe({reactive: true})` handle (with its own version state,\n\t * observe handle, transitive topology subscriptions, derived + keepalive).\n\t * The caller MUST invoke the returned `dispose()` when finished to release\n\t * these resources. Disposers ARE also tracked on the wrapper graph so\n\t * `wrapper.destroy()` cleans up any handles the caller forgot — but a\n\t * long-lived wrapper with heavy per-call usage will leak until destroy\n\t * unless `dispose()` is called explicitly.\n\t *\n\t * @param actorOverride - Optional per-call override. Static {@link Actor}\n\t * or `Node<Actor>`. Omit to use the wrapper-configured default.\n\t * @param opts - Standard {@link GraphDescribeOptions} fields (`detail`,\n\t * `fields`, `filter`). `actor` / `reactive` / `reactiveName` are\n\t * controlled by the wrapper.\n\t * @returns `{node, dispose}` — `node` is the live describe Node; `dispose`\n\t * tears down the underlying reactive describe subscription idempotently.\n\t */\n\tscopedDescribeNode(\n\t\tactorOverride?: Actor | Node<Actor>,\n\t\topts?: Omit<GraphDescribeOptions, \"actor\" | \"reactive\" | \"reactiveName\">,\n\t): { node: Node<GraphDescribeOutput>; dispose: () => void } {\n\t\tconst actorNode =\n\t\t\tactorOverride == null\n\t\t\t\t? this._actorNode\n\t\t\t\t: isNode<Actor>(actorOverride)\n\t\t\t\t\t? actorOverride\n\t\t\t\t\t: node<Actor>([], { name: \"actor_override\", initial: actorOverride });\n\t\tconst handle = this._target.describe({\n\t\t\treactive: true,\n\t\t\t// `_actorNode` is `Node<Actor | null>`. The `as Node<Actor>` cast is\n\t\t\t// safe at runtime: `_describeReactive` resolves the actor via\n\t\t\t// `resolveActorOption`, which treats `null`/`undefined` cache as\n\t\t\t// \"no scoping\" (full visibility). Documented in graph.ts §\n\t\t\t// \"Cache-undefined semantics.\"\n\t\t\tactor: actorNode as Node<Actor>,\n\t\t\t...(opts ?? {}),\n\t\t});\n\t\t// Track on the wrapper as a safety net for callers who forget to\n\t\t// dispose; explicit `dispose()` is still the canonical lifecycle path.\n\t\tthis.addDisposer(handle.dispose);\n\t\treturn { node: handle.node, dispose: handle.dispose };\n\t}\n\n\t/** The wrapped graph (escape hatch for tooling). */\n\tget target(): Graph {\n\t\treturn this._target;\n\t}\n}\n\n/**\n * Wrap a {@link Graph} with {@link policyGate} plus a reactive scoped describe\n * lens. Returns a {@link GuardedExecutionGraph} that can be mounted, diffed,\n * or composed with {@link graphLens}.\n *\n * @param target - The graph to guard.\n * @param opts - See {@link GuardedExecutionOptions}.\n *\n * @example\n * ```ts\n * const guarded = guardedExecution(app, {\n * actor: node<Actor>([], { initial: { type: \"human\", id: \"alice\" } }), // reactive — re-derive on swap\n * policies: [\n * { effect: \"allow\", action: \"read\", actorType: \"human\" },\n * { effect: \"deny\", action: \"write\", pathPattern: \"system::*\" },\n * ],\n * mode: \"enforce\",\n * });\n *\n * // Canonical: subscribe to the mounted reactive describe (no per-call leak).\n * guarded.scopedDescribe.subscribe((msgs) => { /* live describe per actor / topology change *\\/ });\n * // Per-call escape hatch (different actor / detail) — caller manages dispose.\n * const detailed = guarded.scopedDescribeNode(undefined, { detail: \"standard\" });\n * try { detailed.node.subscribe(/* … *\\/); } finally { detailed.dispose(); }\n * guarded.violations.events.subscribe(msgs => console.log(\"violations:\", msgs));\n * guarded.lints.events.subscribe(msgs => console.warn(\"lints:\", msgs));\n * guarded.scope.subscribe(msgs => console.log(\"scope:\", msgs));\n * ```\n *\n * @category patterns\n */\nexport function guardedExecution(\n\ttarget: Graph,\n\topts: GuardedExecutionOptions,\n): GuardedExecutionGraph {\n\treturn new GuardedExecutionGraph(target, opts);\n}\n\n// Re-export types useful for call sites.\nexport type { DescribeFilter };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/presets/inspect/guarded-execution.ts"],"sourcesContent":["/**\n * Composable safety layer (roadmap §9.0b — Tier 5.1 Wave-B rebuild).\n *\n * {@link guardedExecution} wraps any {@link Graph} with:\n *\n * - {@link policyGate} — reactive ABAC (Tier 2.3 rename of `policyEnforcer`),\n * policies stored as a `Node` so LLMs / humans can update them at runtime.\n * Full transitive dynamic coverage via `watchTopologyTree`.\n * - Reactive {@link GuardedExecutionGraph.scopedDescribeNode} — a thin\n * delegate over `target.describe({ reactive: true, actor })` that re-derives\n * on every settle (topology change, error transition, actor swap).\n * - The enforcer's `violations` topic is republished as `violations` on\n * the wrapper, composable with {@link graphLens}'s `health`.\n * - The wrapper-level `lints` topic surfaces non-policy diagnostic warnings\n * (`empty-policies` / `audit-no-effect` / `no-actor`) so misconfigurations\n * are caught reactively rather than via thrown errors at scattered call sites.\n * - The `scope` derived publishes the current configuration tuple\n * (`{actor, mode, policiesCount}`) for dashboards.\n *\n * V1 scope: policies + actor + reactive scoped describe + lints + scope.\n * Budget-as-option is NOT in V1 — it requires a cost-tracking design that\n * hasn't landed yet. Callers who need a budget limit today append a\n * budget-aware {@link PolicyRuleData} to the policies list (check current\n * cost and `deny` when exhausted).\n *\n * @module\n */\nimport type { Actor, PolicyRuleData } from \"@graphrefly/pure-ts/core\";\nimport { DATA, monotonicNs, type Node, node } from \"@graphrefly/pure-ts/core\";\nimport { keepalive } from \"@graphrefly/pure-ts/extra\";\nimport {\n\ttype DescribeFilter,\n\tGraph,\n\ttype GraphDescribeOptions,\n\ttype GraphDescribeOutput,\n\ttype GraphOptions,\n} from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport {\n\ttype PolicyGateGraph,\n\ttype PolicyViolation,\n\tpolicyGate,\n} from \"../../utils/inspect/audit.js\";\nimport { TopicGraph } from \"../../utils/messaging/index.js\";\n\nfunction isNode<T>(x: unknown): x is Node<T> {\n\treturn (\n\t\ttypeof x === \"object\" && x !== null && \"subscribe\" in (x as object) && \"down\" in (x as object)\n\t);\n}\n\nfunction guardedMeta(kind: string): Record<string, unknown> {\n\treturn domainMeta(\"guarded\", kind);\n}\n\n/** Diagnostic warning published on {@link GuardedExecutionGraph.lints}. */\nexport interface GuardedExecutionLint {\n\t/**\n\t * - `\"empty-policies\"` — `policies` Node emitted an empty array in\n\t * `mode: \"enforce\"`. Static empty arrays throw at construction; this\n\t * covers the reactive case.\n\t * - `\"audit-no-effect\"` — `mode: \"audit\"` plus the target has no per-node\n\t * guards, so `scopedDescribeNode` filters by per-node guards only and\n\t * policies will never gate visibility (they still populate `violations`\n\t * on writes).\n\t * - `\"no-actor\"` — neither a wrapper-configured nor per-call actor was\n\t * supplied. `scopedDescribeNode` falls back to \"describe everything\"\n\t * for the corresponding subscription.\n\t */\n\tkind: \"empty-policies\" | \"audit-no-effect\" | \"no-actor\";\n\tmessage: string;\n\ttimestamp_ns: number;\n}\n\n/** Configuration tuple published on {@link GuardedExecutionGraph.scope}. */\nexport interface GuardedScope {\n\t/** The wrapper-configured default actor, or `null` when none configured. */\n\tactor: Actor | null;\n\tmode: \"audit\" | \"enforce\";\n\t/** Current policy count (reactive — re-emits on `policies` Node updates). */\n\tpoliciesCount: number;\n}\n\n/** Options for {@link guardedExecution}. */\nexport interface GuardedExecutionOptions {\n\t/**\n\t * Policies enforced against every guarded write. Static list or a live\n\t * `Node<readonly PolicyRuleData[]>` (LLM-updatable).\n\t *\n\t * **Empty-policies handling:**\n\t * - Static empty array + `mode: \"enforce\"` throws `RangeError` at\n\t * construction (deny-by-default is almost certainly a misconfiguration).\n\t * - Node-supplied empty array + `mode: \"enforce\"` emits a one-time\n\t * `\"empty-policies\"` lint on first such emission (the wrapper can't\n\t * throw mid-run — surface the warning reactively).\n\t * - `mode: \"audit\"` tolerates empty policies (no guards stacked; policies\n\t * only feed the `violations` channel on writes).\n\t */\n\tpolicies: readonly PolicyRuleData[] | Node<readonly PolicyRuleData[]>;\n\t/**\n\t * Default actor used when the caller invokes\n\t * {@link GuardedExecutionGraph.scopedDescribeNode} without an override.\n\t * Accepts a static {@link Actor} or a `Node<Actor>` — when a Node is\n\t * supplied, the reactive describe re-derives on every actor emission so\n\t * harnesses binding a per-turn actor get a single live describe Node\n\t * instead of re-creating one per turn.\n\t *\n\t * Omit to scope per-call only. A `\"no-actor\"` lint fires once per instance\n\t * if neither a configured nor per-call actor is ever supplied (the\n\t * resulting describe is unscoped — full visibility).\n\t */\n\tactor?: Actor | Node<Actor>;\n\t/**\n\t * `\"enforce\"` (default) — push guards onto target nodes so disallowed\n\t * writes throw {@link GuardDenied}.\n\t * `\"audit\"` — record would-be denials to the `violations` topic without\n\t * blocking writes.\n\t */\n\tmode?: \"audit\" | \"enforce\";\n\t/** Ring-buffer cap for the `violations` topic. Default 1000 (inherited from policyGate). */\n\tviolationsLimit?: number;\n\t/** Ring-buffer cap for the `lints` topic. Default 64 — each lint kind fires at most once per instance. */\n\tlintsLimit?: number;\n\t/** Wrapper graph name. Default `${target.name}_guarded`. */\n\tname?: string;\n\t/** Wrapper graph options. */\n\tgraph?: GraphOptions;\n}\n\n/**\n * Wrapper over a target {@link Graph} providing reactive ABAC + reactive\n * scoped describe + diagnostic lints. Mounts a {@link PolicyGateGraph} under\n * `enforcer`, a {@link TopicGraph} of {@link GuardedExecutionLint} under\n * `lints`, and a `scope` derived publishing `{actor, mode, policiesCount}`.\n *\n * @category patterns\n */\nexport class GuardedExecutionGraph extends Graph {\n\treadonly enforcer: PolicyGateGraph;\n\treadonly violations: TopicGraph<PolicyViolation>;\n\treadonly lints: TopicGraph<GuardedExecutionLint>;\n\treadonly scope: Node<GuardedScope>;\n\t/**\n\t * Canonical reactive describe scoped to the wrapper's configured `actor`.\n\t * Subscribes ONCE at construction; lifecycle owned by the wrapper (disposed\n\t * on `wrapper.destroy()`). Use this property for the common case\n\t * (long-lived consumer wanting \"describe scoped to my actor\"); use\n\t * {@link scopedDescribeNode} only when a per-call actor override or\n\t * different `detail`/`fields` is required.\n\t *\n\t * Re-derives on every settle of the target graph: structural changes,\n\t * status transitions (errors flip nodes into `\"errored\"`), and actor\n\t * emissions (when a `Node<Actor>` is bound, including the SENTINEL bridge\n\t * applied internally — see qa G1B).\n\t */\n\treadonly scopedDescribe: Node<GraphDescribeOutput>;\n\tprivate readonly _target: Graph;\n\tprivate readonly _actorNode: Node<Actor | null>;\n\tprivate readonly _mode: \"audit\" | \"enforce\";\n\tprivate readonly _firedLintKinds = new Set<GuardedExecutionLint[\"kind\"]>();\n\n\tconstructor(target: Graph, opts: GuardedExecutionOptions) {\n\t\tsuper(opts.name ?? `${target.name}_guarded`, opts.graph);\n\t\tthis._target = target;\n\t\tthis._mode = opts.mode ?? \"enforce\";\n\n\t\tconst policiesOpt = opts.policies;\n\t\tconst policiesIsNode = isNode<readonly PolicyRuleData[]>(policiesOpt);\n\t\t// Static empty + enforce → throw (deny-by-default = misconfig).\n\t\tif (\n\t\t\t!policiesIsNode &&\n\t\t\tthis._mode === \"enforce\" &&\n\t\t\t(policiesOpt as readonly PolicyRuleData[]).length === 0\n\t\t) {\n\t\t\tthrow new RangeError(\n\t\t\t\t'guardedExecution: empty `policies` in `mode: \"enforce\"` denies every action. ' +\n\t\t\t\t\t'Pass at least `{ effect: \"allow\", action: \"*\" }` and layer deny rules on top.',\n\t\t\t);\n\t\t}\n\n\t\t// Lints topic — mounted before any potential first-DATA emission.\n\t\tthis.lints = new TopicGraph<GuardedExecutionLint>(\"lints\", {\n\t\t\tretainedLimit: opts.lintsLimit ?? 64,\n\t\t});\n\t\tthis.mount(\"lints\", this.lints);\n\n\t\t// Normalize `actor` to a Node<Actor | null>. `null` (a valid DATA in\n\t\t// the v5 sentinel-as-undefined model) means \"no actor configured\" —\n\t\t// `target.describe({ reactive: true })`'s `resolveActorOption` treats\n\t\t// `null` cache as \"no scoping\" (full visibility), and the `scope`\n\t\t// derived can publish `actor: null` cleanly. Using `node([], { initial: undefined })`\n\t\t// here would leave the actor node in sentinel state, which never fires\n\t\t// DATA on subscribe and would block the `scope` derived's first-run\n\t\t// gate from completing.\n\t\t//\n\t\t// qa G1B (EC2 fix): when the caller passes a `Node<Actor>`, that node's\n\t\t// cache may itself be SENTINEL at construction (e.g. a `producer`\n\t\t// awaiting first emission, or a deferred `derived`). Forwarding the raw\n\t\t// Node would re-introduce the same sentinel-stall on `scope`. Bridge\n\t\t// through a node bridge with a `null` initial so the\n\t\t// internal `_actorNode` always carries non-sentinel cache; the bridge\n\t\t// re-emits whenever the caller's Node emits, and forwards `null`\n\t\t// through unchanged.\n\t\tconst actorOpt = opts.actor;\n\t\tif (actorOpt == null) {\n\t\t\tthis._actorNode = node<Actor | null>([], { name: \"actor\", initial: null });\n\t\t} else if (isNode<Actor>(actorOpt)) {\n\t\t\tthis._actorNode = node<Actor | null>(\n\t\t\t\t[actorOpt],\n\t\t\t\t(batchData, actions, ctx) => {\n\t\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t\t);\n\t\t\t\t\tactions.emit((data[0] as Actor | null | undefined) ?? null);\n\t\t\t\t},\n\t\t\t\t{ describeKind: \"derived\", name: \"actor\", initial: null },\n\t\t\t);\n\t\t} else {\n\t\t\tthis._actorNode = node<Actor | null>([], { name: \"actor\", initial: actorOpt });\n\t\t}\n\n\t\t// Mount the enforcer.\n\t\tconst enforcerOpts: { mode: \"audit\" | \"enforce\"; name: string; violationsLimit?: number } = {\n\t\t\tmode: this._mode,\n\t\t\tname: \"enforcer\",\n\t\t};\n\t\tif (opts.violationsLimit != null) enforcerOpts.violationsLimit = opts.violationsLimit;\n\t\tthis.enforcer = policyGate(target, opts.policies, enforcerOpts);\n\t\tthis.violations = this.enforcer.violations;\n\t\tthis.mount(\"enforcer\", this.enforcer);\n\n\t\t// Empty-policies one-time lint (Node form, enforce mode only).\n\t\tif (policiesIsNode && this._mode === \"enforce\") {\n\t\t\tconst policiesNode = policiesOpt as Node<readonly PolicyRuleData[]>;\n\t\t\t// Two paths here intentionally:\n\t\t\t// (1) Synchronous cache seed — fires immediately if the Node was\n\t\t\t// constructed with an empty array as its current cache. This\n\t\t\t// covers `node<…[]>([], { initial: [] })` and any pre-emitted derived.\n\t\t\t// (2) Subscribe path — catches subsequent empty emissions AND\n\t\t\t// handles the SENTINEL case (cache=undefined at construction)\n\t\t\t// where the Node fires its first DATA after the wrapper is\n\t\t\t// built. The `_firedLintKinds` Set keeps the lint one-shot\n\t\t\t// across both paths.\n\t\t\t// qa F6 (deferred): if a Node's initial cache is SENTINEL (undefined)\n\t\t\t// AND it never emits an empty array (only ever non-empty), the lint\n\t\t\t// never fires — that's correct behavior, the configuration is\n\t\t\t// effectively non-empty for the wrapper's lifetime.\n\t\t\tconst cached = policiesNode.cache as readonly PolicyRuleData[] | undefined;\n\t\t\tif (cached != null && cached.length === 0) {\n\t\t\t\tthis._fireLint(\n\t\t\t\t\t\"empty-policies\",\n\t\t\t\t\t'`policies` Node cached an empty array in `mode: \"enforce\"` — every action will be denied. Add at least one allow rule.',\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst offEmpty = policiesNode.subscribe((msgs) => {\n\t\t\t\tfor (const m of msgs) {\n\t\t\t\t\tif (m[0] !== DATA) continue;\n\t\t\t\t\tconst v = m[1] as readonly PolicyRuleData[] | undefined;\n\t\t\t\t\tif (v == null || v.length === 0) {\n\t\t\t\t\t\tthis._fireLint(\n\t\t\t\t\t\t\t\"empty-policies\",\n\t\t\t\t\t\t\t'`policies` Node emitted an empty array in `mode: \"enforce\"` — every action will be denied. Add at least one allow rule.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.addDisposer(offEmpty);\n\t\t}\n\n\t\t// Audit-mode + no per-node guards on the target → fire once.\n\t\t// qa EC5 (deferred): the check is one-shot at construction. If the\n\t\t// caller later mounts a guarded node into the target, the lint stays\n\t\t// retained even though the configuration is now effective. Recommended\n\t\t// pattern: mount per-node guards on the target BEFORE wrapping.\n\t\t// Reactive recompute (subscribe to target.topology, clear lint when a\n\t\t// guard appears) is filed in `docs/optimizations.md` as a follow-up.\n\t\tif (this._mode === \"audit\") {\n\t\t\tconst described = target.describe({ detail: \"full\" });\n\t\t\tconst anyGuard = Object.values(described.nodes).some((n) => n.guard != null);\n\t\t\tif (!anyGuard) {\n\t\t\t\tthis._fireLint(\n\t\t\t\t\t\"audit-no-effect\",\n\t\t\t\t\t'`mode: \"audit\"` + target has no per-node guards — `scopedDescribeNode` filters by per-node guards only, so policy rules will not affect describe() visibility. Policies still populate the `violations` topic on writes.',\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// No-actor lint: fires at construction when neither configured nor\n\t\t// implicitly carried via a Node-cache. Per-call overrides on\n\t\t// `scopedDescribeNode` cannot retroactively suppress this — the\n\t\t// configured-default branch is what callers most often miss.\n\t\tif (actorOpt == null) {\n\t\t\tthis._fireLint(\n\t\t\t\t\"no-actor\",\n\t\t\t\t\"no actor configured — `wrapper.scopedDescribe` and `scopedDescribeNode()` will return an unscoped describe (full visibility) unless a per-call actor is supplied.\",\n\t\t\t);\n\t\t}\n\n\t\t// Scope derived: live tuple of {actor, mode, policiesCount}.\n\t\tthis.scope = node<GuardedScope>(\n\t\t\t[this._actorNode, this.enforcer.policies],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tactions.emit({\n\t\t\t\t\tactor: (data[0] as Actor | null | undefined) ?? null,\n\t\t\t\t\tmode: this._mode,\n\t\t\t\t\tpoliciesCount: (data[1] as readonly PolicyRuleData[]).length,\n\t\t\t\t});\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"scope\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: guardedMeta(\"scope\"),\n\t\t\t},\n\t\t);\n\t\tthis.add(this.scope, { name: \"scope\" });\n\t\tthis.addDisposer(keepalive(this.scope));\n\n\t\t// qa G1A \"same concept\": single canonical reactive describe bound to\n\t\t// the configured actor, mounted once at construction. Removes the\n\t\t// per-call handle proliferation from the prior `scopedDescribeNode`\n\t\t// pattern (the method is retained as the per-call escape hatch). The\n\t\t// reactive describe re-derives on actor swap / topology change /\n\t\t// status transition; lifecycle owned by the wrapper.\n\t\tconst scopedHandle = target.describe({\n\t\t\treactive: true,\n\t\t\t// F8 (resolved 2026-05-18): `GraphDescribeOptions.actor` is now\n\t\t\t// `Actor | Node<Actor | null>`, so `_actorNode` (a\n\t\t\t// `Node<Actor | null>`) passes without a cast. A `null`/`undefined`\n\t\t\t// cache resolves to \"no scoping\" (full visibility) per\n\t\t\t// `resolveActorOption`. See graph.ts § \"Cache-undefined/null\n\t\t\t// semantics\".\n\t\t\tactor: this._actorNode,\n\t\t\treactiveName: \"scopedDescribe\",\n\t\t});\n\t\tthis.scopedDescribe = scopedHandle.node;\n\t\tthis.add(this.scopedDescribe, { name: \"scopedDescribe\" });\n\t\tthis.addDisposer(scopedHandle.dispose);\n\t}\n\n\tprivate _fireLint(kind: GuardedExecutionLint[\"kind\"], message: string): void {\n\t\tif (this._firedLintKinds.has(kind)) return;\n\t\tthis._firedLintKinds.add(kind);\n\t\tthis.lints.publish({ kind, message, timestamp_ns: monotonicNs() });\n\t}\n\n\t/**\n\t * **Per-call escape hatch.** Prefer {@link scopedDescribe} (the mounted\n\t * property) for the common case of \"describe scoped to my actor.\" Use\n\t * this method ONLY when you need a per-call actor override or different\n\t * `detail`/`fields`/`filter`.\n\t *\n\t * Returns a live `Node<GraphDescribeOutput>` scoped to the supplied (or\n\t * configured) actor, plus an explicit `dispose` for caller-controlled\n\t * lifecycle. Re-derives on every settle of the target graph: structural\n\t * changes, status transitions, and actor emissions (when a `Node<Actor>`\n\t * is bound).\n\t *\n\t * **Lifecycle (qa G1A — EC1 fix).** Each call instantiates a fresh\n\t * `target.describe({reactive: true})` handle (with its own version state,\n\t * observe handle, transitive topology subscriptions, derived + keepalive).\n\t * The caller MUST invoke the returned `dispose()` when finished to release\n\t * these resources. Disposers ARE also tracked on the wrapper graph so\n\t * `wrapper.destroy()` cleans up any handles the caller forgot — but a\n\t * long-lived wrapper with heavy per-call usage will leak until destroy\n\t * unless `dispose()` is called explicitly.\n\t *\n\t * @param actorOverride - Optional per-call override. Static {@link Actor}\n\t * or `Node<Actor>`. Omit to use the wrapper-configured default.\n\t * @param opts - Standard {@link GraphDescribeOptions} fields (`detail`,\n\t * `fields`, `filter`). `actor` / `reactive` / `reactiveName` are\n\t * controlled by the wrapper.\n\t * @returns `{node, dispose}` — `node` is the live describe Node; `dispose`\n\t * tears down the underlying reactive describe subscription idempotently.\n\t */\n\tscopedDescribeNode(\n\t\tactorOverride?: Actor | Node<Actor>,\n\t\topts?: Omit<GraphDescribeOptions, \"actor\" | \"reactive\" | \"reactiveName\">,\n\t): { node: Node<GraphDescribeOutput>; dispose: () => void } {\n\t\tconst actorNode =\n\t\t\tactorOverride == null\n\t\t\t\t? this._actorNode\n\t\t\t\t: isNode<Actor>(actorOverride)\n\t\t\t\t\t? actorOverride\n\t\t\t\t\t: node<Actor>([], { name: \"actor_override\", initial: actorOverride });\n\t\tconst handle = this._target.describe({\n\t\t\treactive: true,\n\t\t\t// F8 (resolved 2026-05-18): `actor` accepts `Node<Actor | null>`\n\t\t\t// directly. `actorNode` is `_actorNode` (`Node<Actor | null>`) or\n\t\t\t// an override (`Node<Actor>`/`Node<Actor | null>`); both assign\n\t\t\t// without a cast. `_describeReactive` resolves via\n\t\t\t// `resolveActorOption` (null/undefined cache → no scoping).\n\t\t\tactor: actorNode,\n\t\t\t...(opts ?? {}),\n\t\t});\n\t\t// Track on the wrapper as a safety net for callers who forget to\n\t\t// dispose; explicit `dispose()` is still the canonical lifecycle path.\n\t\tthis.addDisposer(handle.dispose);\n\t\treturn { node: handle.node, dispose: handle.dispose };\n\t}\n\n\t/** The wrapped graph (escape hatch for tooling). */\n\tget target(): Graph {\n\t\treturn this._target;\n\t}\n}\n\n/**\n * Wrap a {@link Graph} with {@link policyGate} plus a reactive scoped describe\n * lens. Returns a {@link GuardedExecutionGraph} that can be mounted, diffed,\n * or composed with {@link graphLens}.\n *\n * @param target - The graph to guard.\n * @param opts - See {@link GuardedExecutionOptions}.\n *\n * @example\n * ```ts\n * const guarded = guardedExecution(app, {\n * actor: node<Actor>([], { initial: { type: \"human\", id: \"alice\" } }), // reactive — re-derive on swap\n * policies: [\n * { effect: \"allow\", action: \"read\", actorType: \"human\" },\n * { effect: \"deny\", action: \"write\", pathPattern: \"system::*\" },\n * ],\n * mode: \"enforce\",\n * });\n *\n * // Canonical: subscribe to the mounted reactive describe (no per-call leak).\n * guarded.scopedDescribe.subscribe((msgs) => { /* live describe per actor / topology change *\\/ });\n * // Per-call escape hatch (different actor / detail) — caller manages dispose.\n * const detailed = guarded.scopedDescribeNode(undefined, { detail: \"standard\" });\n * try { detailed.node.subscribe(/* … *\\/); } finally { detailed.dispose(); }\n * guarded.violations.events.subscribe(msgs => console.log(\"violations:\", msgs));\n * guarded.lints.events.subscribe(msgs => console.warn(\"lints:\", msgs));\n * guarded.scope.subscribe(msgs => console.log(\"scope:\", msgs));\n * ```\n *\n * @category patterns\n */\nexport function guardedExecution(\n\ttarget: Graph,\n\topts: GuardedExecutionOptions,\n): GuardedExecutionGraph {\n\treturn new GuardedExecutionGraph(target, opts);\n}\n\n// Re-export types useful for call sites.\nexport type { DescribeFilter };\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
feedback,
|
|
3
3
|
scorer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LTSI7ULC.js";
|
|
5
5
|
import {
|
|
6
6
|
domainMeta
|
|
7
7
|
} from "./chunk-FMPF42Q4.js";
|
|
@@ -581,4 +581,4 @@ export {
|
|
|
581
581
|
contentModerationGraph,
|
|
582
582
|
dataQualityGraph
|
|
583
583
|
};
|
|
584
|
-
//# sourceMappingURL=chunk-
|
|
584
|
+
//# sourceMappingURL=chunk-NSA5K5G2.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeAndMeasure,
|
|
3
3
|
computeLineBreaks
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6DQYBIHW.js";
|
|
5
5
|
import {
|
|
6
6
|
graphSpecToMermaid
|
|
7
7
|
} from "./chunk-PKGQG5QQ.js";
|
|
@@ -414,4 +414,4 @@ function demoShell(opts) {
|
|
|
414
414
|
export {
|
|
415
415
|
demoShell
|
|
416
416
|
};
|
|
417
|
-
//# sourceMappingURL=chunk-
|
|
417
|
+
//# sourceMappingURL=chunk-QQYULEZL.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/base/sources/event/push.ts
|
|
2
|
+
import { node } from "@graphrefly/pure-ts/core";
|
|
3
|
+
function sourceOpts(opts) {
|
|
4
|
+
return { describeKind: "producer", ...opts };
|
|
5
|
+
}
|
|
6
|
+
function fromPushNotification(register, opts) {
|
|
7
|
+
if (typeof register !== "function") {
|
|
8
|
+
throw new TypeError(
|
|
9
|
+
"fromPushNotification: a (deliver) => unsubscribe registration function is required"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return node((_data, a) => {
|
|
13
|
+
let done = false;
|
|
14
|
+
const deliver = (payload) => {
|
|
15
|
+
if (done) return;
|
|
16
|
+
a.emit(payload);
|
|
17
|
+
};
|
|
18
|
+
const unsubscribe = register(deliver);
|
|
19
|
+
return {
|
|
20
|
+
onDeactivation: () => {
|
|
21
|
+
done = true;
|
|
22
|
+
if (typeof unsubscribe === "function") unsubscribe();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}, sourceOpts(opts));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
fromPushNotification
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=chunk-QSW4DFKE.js.map
|