@getrheo/flow-runtime 1.0.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/agentPrompt/index.d.ts +72 -0
- package/dist/agentPrompt/index.js +739 -0
- package/dist/agentPrompt/index.js.map +1 -0
- package/dist/aiFlowGenerationMerge.d.ts +32 -0
- package/dist/aiFlowGenerationMerge.js +120 -0
- package/dist/aiFlowGenerationMerge.js.map +1 -0
- package/dist/animations.d.ts +110 -0
- package/dist/animations.js +312 -0
- package/dist/animations.js.map +1 -0
- package/dist/assignment.d.ts +7 -0
- package/dist/assignment.js +25 -0
- package/dist/assignment.js.map +1 -0
- package/dist/brandGradient.d.ts +57 -0
- package/dist/brandGradient.js +137 -0
- package/dist/brandGradient.js.map +1 -0
- package/dist/brandGradientManifestIssues.d.ts +11 -0
- package/dist/brandGradientManifestIssues.js +302 -0
- package/dist/brandGradientManifestIssues.js.map +1 -0
- package/dist/buildFlowPreview.d.ts +7 -0
- package/dist/buildFlowPreview.js +81 -0
- package/dist/buildFlowPreview.js.map +1 -0
- package/dist/buttonVariantChrome.d.ts +26 -0
- package/dist/buttonVariantChrome.js +59 -0
- package/dist/buttonVariantChrome.js.map +1 -0
- package/dist/checkboxGlyphStyle.d.ts +31 -0
- package/dist/checkboxGlyphStyle.js +241 -0
- package/dist/checkboxGlyphStyle.js.map +1 -0
- package/dist/choiceOptionSelection.d.ts +11 -0
- package/dist/choiceOptionSelection.js +120 -0
- package/dist/choiceOptionSelection.js.map +1 -0
- package/dist/colorAlpha.d.ts +8 -0
- package/dist/colorAlpha.js +48 -0
- package/dist/colorAlpha.js.map +1 -0
- package/dist/counterLayer.d.ts +42 -0
- package/dist/counterLayer.js +95 -0
- package/dist/counterLayer.js.map +1 -0
- package/dist/decisionEval.d.ts +27 -0
- package/dist/decisionEval.js +197 -0
- package/dist/decisionEval.js.map +1 -0
- package/dist/dropShadow.d.ts +26 -0
- package/dist/dropShadow.js +76 -0
- package/dist/dropShadow.js.map +1 -0
- package/dist/emailPasswordAuthValidation.d.ts +16 -0
- package/dist/emailPasswordAuthValidation.js +25 -0
- package/dist/emailPasswordAuthValidation.js.map +1 -0
- package/dist/flowBuilderRules.d.ts +15 -0
- package/dist/flowBuilderRules.js +368 -0
- package/dist/flowBuilderRules.js.map +1 -0
- package/dist/flowGraph.d.ts +19 -0
- package/dist/flowGraph.js +373 -0
- package/dist/flowGraph.js.map +1 -0
- package/dist/hyperlinkLabel.d.ts +19 -0
- package/dist/hyperlinkLabel.js +232 -0
- package/dist/hyperlinkLabel.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +4200 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolateTemplate.d.ts +44 -0
- package/dist/interpolateTemplate.js +188 -0
- package/dist/interpolateTemplate.js.map +1 -0
- package/dist/layerRotate.d.ts +10 -0
- package/dist/layerRotate.js +9 -0
- package/dist/layerRotate.js.map +1 -0
- package/dist/layerTypography.d.ts +36 -0
- package/dist/layerTypography.js +68 -0
- package/dist/layerTypography.js.map +1 -0
- package/dist/layers.d.ts +69 -0
- package/dist/layers.js +257 -0
- package/dist/layers.js.map +1 -0
- package/dist/layout/index.d.ts +57 -0
- package/dist/layout/index.js +151 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/manifestBillingSlice.d.ts +17 -0
- package/dist/manifestBillingSlice.js +102 -0
- package/dist/manifestBillingSlice.js.map +1 -0
- package/dist/prepareAiGeneratedScreen.d.ts +17 -0
- package/dist/prepareAiGeneratedScreen.js +99 -0
- package/dist/prepareAiGeneratedScreen.js.map +1 -0
- package/dist/publish-exports.json +166 -0
- package/dist/responsive/breakpoints.d.ts +34 -0
- package/dist/responsive/breakpoints.js +52 -0
- package/dist/responsive/breakpoints.js.map +1 -0
- package/dist/responsive/index.d.ts +8 -0
- package/dist/responsive/index.js +307 -0
- package/dist/responsive/index.js.map +1 -0
- package/dist/responsive/layerResolve.d.ts +43 -0
- package/dist/responsive/layerResolve.js +168 -0
- package/dist/responsive/layerResolve.js.map +1 -0
- package/dist/responsive/merge.d.ts +19 -0
- package/dist/responsive/merge.js +74 -0
- package/dist/responsive/merge.js.map +1 -0
- package/dist/responsive/previewSafeAreaInsets.d.ts +14 -0
- package/dist/responsive/previewSafeAreaInsets.js +24 -0
- package/dist/responsive/previewSafeAreaInsets.js.map +1 -0
- package/dist/responsive/screenContainerResolve.d.ts +11 -0
- package/dist/responsive/screenContainerResolve.js +122 -0
- package/dist/responsive/screenContainerResolve.js.map +1 -0
- package/dist/responsive/screenShellInsets.d.ts +11 -0
- package/dist/responsive/screenShellInsets.js +26 -0
- package/dist/responsive/screenShellInsets.js.map +1 -0
- package/dist/restingMotion.d.ts +167 -0
- package/dist/restingMotion.js +484 -0
- package/dist/restingMotion.js.map +1 -0
- package/dist/rheoAgentManifestMerge.d.ts +33 -0
- package/dist/rheoAgentManifestMerge.js +55 -0
- package/dist/rheoAgentManifestMerge.js.map +1 -0
- package/dist/scaleInputStyle.d.ts +35 -0
- package/dist/scaleInputStyle.js +77 -0
- package/dist/scaleInputStyle.js.map +1 -0
- package/dist/scaleValidation.d.ts +9 -0
- package/dist/scaleValidation.js +21 -0
- package/dist/scaleValidation.js.map +1 -0
- package/dist/stateMachine.d.ts +105 -0
- package/dist/stateMachine.js +674 -0
- package/dist/stateMachine.js.map +1 -0
- package/dist/stepResponse-BXgoZ7o-.d.ts +112 -0
- package/dist/textInputValidation.d.ts +14 -0
- package/dist/textInputValidation.js +46 -0
- package/dist/textInputValidation.js.map +1 -0
- package/dist/translationPlaceholders.d.ts +9 -0
- package/dist/translationPlaceholders.js +52 -0
- package/dist/translationPlaceholders.js.map +1 -0
- package/dist/validation.d.ts +31 -0
- package/dist/validation.js +233 -0
- package/dist/validation.js.map +1 -0
- package/package.json +242 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { isInputLayer, OS_PERMISSION_OUTCOME_END, OS_PERMISSION_OUTCOME_CONTINUE } from '@getrheo/contracts/layers';
|
|
2
|
+
import { collectDecisionFieldKeysFromNode } from '@getrheo/contracts/decisions';
|
|
3
|
+
import '@getrheo/contracts/localized';
|
|
4
|
+
import { FIELD_KEY_RE } from '@getrheo/contracts/fields';
|
|
5
|
+
|
|
6
|
+
// src/flowGraph.ts
|
|
7
|
+
var walkLayers = (root, fn) => {
|
|
8
|
+
const visit = (l, depth) => {
|
|
9
|
+
fn(l, depth);
|
|
10
|
+
if (l.kind === "stack") l.children.forEach((c) => visit(c, depth + 1));
|
|
11
|
+
else if (l.kind === "carousel") l.slides.forEach((c) => visit(c, depth + 1));
|
|
12
|
+
else if (l.kind === "button") l.children.forEach((c) => visit(c, depth + 1));
|
|
13
|
+
else if (l.kind === "back_button") l.children.forEach((c) => visit(c, depth + 1));
|
|
14
|
+
else if (l.kind === "hyperlink") l.children.forEach((c) => visit(c, depth + 1));
|
|
15
|
+
else if (l.kind === "single_choice" || l.kind === "multiple_choice") {
|
|
16
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
17
|
+
} else if (l.kind === "text_input" || l.kind === "scale_input") {
|
|
18
|
+
l.children?.forEach((c) => visit(c, depth + 1));
|
|
19
|
+
} else if (l.kind === "oauth_login") {
|
|
20
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
21
|
+
} else if (l.kind === "oauth_provider" && l.variant === "custom") {
|
|
22
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
23
|
+
} else if (l.kind === "email_password_auth") {
|
|
24
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
25
|
+
} else if (l.kind === "email_password_field") {
|
|
26
|
+
l.children?.forEach((c) => visit(c, depth + 1));
|
|
27
|
+
} else if (l.kind === "email_password_submit") {
|
|
28
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
visit(root, 0);
|
|
32
|
+
};
|
|
33
|
+
var walkScreen = (screen, fn) => {
|
|
34
|
+
if (screen.regions.header) walkLayers(screen.regions.header, fn);
|
|
35
|
+
walkLayers(screen.regions.body, fn);
|
|
36
|
+
if (screen.regions.footer) walkLayers(screen.regions.footer, fn);
|
|
37
|
+
};
|
|
38
|
+
var findInputLayer = (screen) => {
|
|
39
|
+
let found = null;
|
|
40
|
+
walkScreen(screen, (l) => {
|
|
41
|
+
if (!found && isInputLayer(l)) found = l;
|
|
42
|
+
});
|
|
43
|
+
return found;
|
|
44
|
+
};
|
|
45
|
+
var collectFieldKeys = (manifest) => {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const screen of manifest.screens) {
|
|
48
|
+
walkScreen(screen, (l) => {
|
|
49
|
+
if (isInputLayer(l)) out.push({ fieldKey: l.fieldKey, screenId: screen.id });
|
|
50
|
+
if (l.kind === "checkbox") out.push({ fieldKey: l.fieldKey, screenId: screen.id });
|
|
51
|
+
if (l.kind === "email_password_auth") {
|
|
52
|
+
out.push({ fieldKey: l.fieldKey, screenId: screen.id });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/stateMachine/flowSession.ts
|
|
60
|
+
var findScreen = (manifest, screenId) => manifest.screens.find((s) => s.id === screenId);
|
|
61
|
+
var extractLiquidTemplateBodies = (s) => {
|
|
62
|
+
const out = [];
|
|
63
|
+
let i = 0;
|
|
64
|
+
while (i < s.length) {
|
|
65
|
+
const open = s.indexOf("{{", i);
|
|
66
|
+
if (open === -1) break;
|
|
67
|
+
const close = s.indexOf("}}", open + 2);
|
|
68
|
+
if (close === -1) break;
|
|
69
|
+
out.push(s.slice(open + 2, close));
|
|
70
|
+
i = close + 2;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
};
|
|
74
|
+
var FIELD_KEY_SOURCE = FIELD_KEY_RE.source.replace(/^\^|\$$/g, "");
|
|
75
|
+
var parseDefaultFilter = (inner) => {
|
|
76
|
+
const m = inner.match(/\s*\|\s*default\s*:/i);
|
|
77
|
+
if (!m || m.index === void 0) return { exprPart: inner.trim() };
|
|
78
|
+
const exprPart = inner.slice(0, m.index).trim();
|
|
79
|
+
let tail = inner.slice(m.index + m[0].length).trim();
|
|
80
|
+
if (tail.startsWith('"')) {
|
|
81
|
+
const end = tail.indexOf('"', 1);
|
|
82
|
+
if (end > 0) tail = tail.slice(1, end);
|
|
83
|
+
else tail = tail.slice(1);
|
|
84
|
+
}
|
|
85
|
+
return { exprPart, defaultValue: tail };
|
|
86
|
+
};
|
|
87
|
+
var parseExpression = (exprRaw) => {
|
|
88
|
+
const expr = exprRaw.trim();
|
|
89
|
+
const customM = expr.match(new RegExp(`^custom\\.(${FIELD_KEY_SOURCE})$`));
|
|
90
|
+
if (customM) return { kind: "custom", key: customM[1] };
|
|
91
|
+
const idM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})\\.id$`));
|
|
92
|
+
if (idM) return { kind: "field", fieldKey: idM[1], mode: "id" };
|
|
93
|
+
const plainM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})$`));
|
|
94
|
+
if (plainM) return { kind: "field", fieldKey: plainM[1], mode: "label" };
|
|
95
|
+
return { kind: "invalid" };
|
|
96
|
+
};
|
|
97
|
+
var analyzeLiquidTemplateInner = (inner) => {
|
|
98
|
+
const { exprPart, defaultValue } = parseDefaultFilter(inner);
|
|
99
|
+
const p = parseExpression(exprPart);
|
|
100
|
+
if (p.kind === "invalid") return { expr: { kind: "invalid" }, defaultValue };
|
|
101
|
+
if (p.kind === "custom") return { expr: { kind: "custom", key: p.key }, defaultValue };
|
|
102
|
+
return { expr: { kind: "field", fieldKey: p.fieldKey, mode: p.mode }, defaultValue };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/flowGraph.ts
|
|
106
|
+
var localizedStrings = (t) => {
|
|
107
|
+
const out = [t.default];
|
|
108
|
+
if (t.translations) {
|
|
109
|
+
for (const loc of Object.keys(t.translations)) {
|
|
110
|
+
if (/^[a-z]{2}(-[A-Z]{2})?$/.test(loc)) {
|
|
111
|
+
const v = t.translations[loc];
|
|
112
|
+
if (v) out.push(v);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
};
|
|
118
|
+
var collectOutgoingTargets = (screen) => {
|
|
119
|
+
const out = [];
|
|
120
|
+
if (screen.next.default) out.push(screen.next.default);
|
|
121
|
+
walkScreen(screen, (l) => {
|
|
122
|
+
if (l.kind === "single_choice" || l.kind === "multiple_choice") {
|
|
123
|
+
if (l.branching.enabled) {
|
|
124
|
+
for (const c of l.branching.conditions) out.push(c.goTo);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (l.kind === "button" && l.action.kind === "go_to_step") out.push(l.action.screenId);
|
|
128
|
+
if (l.kind === "button" && l.action.kind === "request_os_permission") {
|
|
129
|
+
const o = l.action.outcomes;
|
|
130
|
+
for (const t of [o.granted, o.denied, o.blocked]) {
|
|
131
|
+
if (t === OS_PERMISSION_OUTCOME_END) ; else if (t === OS_PERMISSION_OUTCOME_CONTINUE) {
|
|
132
|
+
if (screen.next.default) out.push(screen.next.default);
|
|
133
|
+
} else {
|
|
134
|
+
out.push(t);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (l.kind === "button" && l.action.kind === "go_back_one_screen" && l.action.fallbackScreenId) {
|
|
139
|
+
out.push(l.action.fallbackScreenId);
|
|
140
|
+
}
|
|
141
|
+
if (l.kind === "back_button" && l.fallbackScreenId) out.push(l.fallbackScreenId);
|
|
142
|
+
});
|
|
143
|
+
return out;
|
|
144
|
+
};
|
|
145
|
+
var buildForwardAdjacency = (manifest) => {
|
|
146
|
+
const m = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const s of manifest.screens) {
|
|
148
|
+
m.set(s.id, collectOutgoingTargets(s));
|
|
149
|
+
}
|
|
150
|
+
for (const d of manifest.decisionNodes ?? []) {
|
|
151
|
+
const outs = [
|
|
152
|
+
...d.cases.map((c) => c.next).filter((x) => x != null),
|
|
153
|
+
d.elseNext
|
|
154
|
+
].filter((x) => x != null);
|
|
155
|
+
m.set(d.id, outs);
|
|
156
|
+
}
|
|
157
|
+
return m;
|
|
158
|
+
};
|
|
159
|
+
var reverseAdjacency = (forward) => {
|
|
160
|
+
const rev = /* @__PURE__ */ new Map();
|
|
161
|
+
for (const [u, vs] of forward) {
|
|
162
|
+
for (const v of vs) {
|
|
163
|
+
const arr = rev.get(v) ?? [];
|
|
164
|
+
arr.push(u);
|
|
165
|
+
rev.set(v, arr);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return rev;
|
|
169
|
+
};
|
|
170
|
+
var bfsForward = (entry, forward) => {
|
|
171
|
+
if (entry == null) return /* @__PURE__ */ new Set();
|
|
172
|
+
const seen = /* @__PURE__ */ new Set([entry]);
|
|
173
|
+
const q = [entry];
|
|
174
|
+
while (q.length) {
|
|
175
|
+
const u = q.shift();
|
|
176
|
+
for (const v of forward.get(u) ?? []) {
|
|
177
|
+
if (!seen.has(v)) {
|
|
178
|
+
seen.add(v);
|
|
179
|
+
q.push(v);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return seen;
|
|
184
|
+
};
|
|
185
|
+
var reverseReachable = (targetId, rev, allowed) => {
|
|
186
|
+
const seen = /* @__PURE__ */ new Set([targetId]);
|
|
187
|
+
const q = [targetId];
|
|
188
|
+
while (q.length) {
|
|
189
|
+
const u = q.shift();
|
|
190
|
+
for (const v of rev.get(u) ?? []) {
|
|
191
|
+
if (!allowed.has(v)) continue;
|
|
192
|
+
if (!seen.has(v)) {
|
|
193
|
+
seen.add(v);
|
|
194
|
+
q.push(v);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return seen;
|
|
199
|
+
};
|
|
200
|
+
var upstreamScreenIdsForPicker = (manifest, screenId) => {
|
|
201
|
+
const forward = buildForwardAdjacency(manifest);
|
|
202
|
+
const rev = reverseAdjacency(forward);
|
|
203
|
+
const entry = manifest.entryScreenId;
|
|
204
|
+
if (entry == null) return [];
|
|
205
|
+
const reachFromEntry = bfsForward(entry, forward);
|
|
206
|
+
const ancestors = reverseReachable(screenId, rev, reachFromEntry);
|
|
207
|
+
const screenSet = new Set(manifest.screens.map((s) => s.id));
|
|
208
|
+
return [...ancestors].filter((id) => id !== screenId && screenSet.has(id));
|
|
209
|
+
};
|
|
210
|
+
var computeDominators = (manifest) => {
|
|
211
|
+
const forward = buildForwardAdjacency(manifest);
|
|
212
|
+
const entry = manifest.entryScreenId;
|
|
213
|
+
if (entry == null) return /* @__PURE__ */ new Map();
|
|
214
|
+
const reachable = bfsForward(entry, forward);
|
|
215
|
+
const nodes = [...reachable];
|
|
216
|
+
const pred = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const [u, vs] of forward) {
|
|
218
|
+
for (const v of vs) {
|
|
219
|
+
if (!reachable.has(v)) continue;
|
|
220
|
+
const arr = pred.get(v) ?? [];
|
|
221
|
+
arr.push(u);
|
|
222
|
+
pred.set(v, arr);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const all = new Set(nodes);
|
|
226
|
+
const dom = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const n of nodes) {
|
|
228
|
+
if (n === entry) dom.set(n, /* @__PURE__ */ new Set([entry]));
|
|
229
|
+
else dom.set(n, new Set(all));
|
|
230
|
+
}
|
|
231
|
+
let changed = true;
|
|
232
|
+
while (changed) {
|
|
233
|
+
changed = false;
|
|
234
|
+
for (const n of nodes) {
|
|
235
|
+
if (n === entry) continue;
|
|
236
|
+
const ps = (pred.get(n) ?? []).filter((p) => reachable.has(p));
|
|
237
|
+
let nextDom;
|
|
238
|
+
if (ps.length === 0) {
|
|
239
|
+
nextDom = /* @__PURE__ */ new Set([n]);
|
|
240
|
+
} else {
|
|
241
|
+
nextDom = /* @__PURE__ */ new Set([n]);
|
|
242
|
+
const first = dom.get(ps[0]) ?? new Set(all);
|
|
243
|
+
let inter = new Set(first);
|
|
244
|
+
for (let i = 1; i < ps.length; i++) {
|
|
245
|
+
const d = dom.get(ps[i]) ?? new Set(all);
|
|
246
|
+
inter = new Set([...inter].filter((x) => d.has(x)));
|
|
247
|
+
}
|
|
248
|
+
for (const x of inter) nextDom.add(x);
|
|
249
|
+
}
|
|
250
|
+
const cur = dom.get(n);
|
|
251
|
+
if (cur.size !== nextDom.size || [...nextDom].some((x) => !cur.has(x))) {
|
|
252
|
+
dom.set(n, nextDom);
|
|
253
|
+
changed = true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return dom;
|
|
258
|
+
};
|
|
259
|
+
var fieldKeyOwnerScreenId = (manifest, fieldKey) => {
|
|
260
|
+
for (const e of collectFieldKeys(manifest)) {
|
|
261
|
+
if (e.fieldKey === fieldKey) return e.screenId;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
};
|
|
265
|
+
var scanLocalizedForWarnings = (manifest, hostScreenId, text, layerId, dom, seen, out) => {
|
|
266
|
+
const screenLabel = manifest.screens.find((s) => s.id === hostScreenId)?.name ?? hostScreenId;
|
|
267
|
+
for (const str of localizedStrings(text)) {
|
|
268
|
+
for (const inner of extractLiquidTemplateBodies(str)) {
|
|
269
|
+
const { expr } = analyzeLiquidTemplateInner(inner);
|
|
270
|
+
if (expr.kind === "invalid") {
|
|
271
|
+
const key = `invalid:${hostScreenId}:${layerId}:${inner}`;
|
|
272
|
+
if (seen.has(key)) continue;
|
|
273
|
+
seen.add(key);
|
|
274
|
+
out.push(
|
|
275
|
+
`Warning: Screen "${screenLabel}" text layer "${layerId}" has an invalid template token "{{${inner}}}".`
|
|
276
|
+
);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (expr.kind === "custom") {
|
|
280
|
+
const key = `custom:${hostScreenId}`;
|
|
281
|
+
if (!seen.has(key)) {
|
|
282
|
+
seen.add(key);
|
|
283
|
+
out.push(
|
|
284
|
+
`Warning: Screen "${screenLabel}" uses \`custom.*\` template variables \u2014 values are supplied by the host SDK at runtime.`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const ownerId = fieldKeyOwnerScreenId(manifest, expr.fieldKey);
|
|
290
|
+
if (!ownerId) {
|
|
291
|
+
const key = `unknown:${hostScreenId}:${layerId}:${expr.fieldKey}`;
|
|
292
|
+
if (seen.has(key)) continue;
|
|
293
|
+
seen.add(key);
|
|
294
|
+
out.push(
|
|
295
|
+
`Warning: Screen "${screenLabel}" references unknown variable "{{${expr.fieldKey}}}" (no matching input fieldKey).`
|
|
296
|
+
);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (expr.mode === "id") {
|
|
300
|
+
const ownerScreen = findScreen(manifest, ownerId);
|
|
301
|
+
const input = ownerScreen ? findInputLayer(ownerScreen) : null;
|
|
302
|
+
if (input?.kind !== "single_choice" && input?.kind !== "multiple_choice") {
|
|
303
|
+
const key = `id:${hostScreenId}:${layerId}:${expr.fieldKey}`;
|
|
304
|
+
if (seen.has(key)) continue;
|
|
305
|
+
seen.add(key);
|
|
306
|
+
out.push(
|
|
307
|
+
`Warning: Screen "${screenLabel}" uses "{{${expr.fieldKey}.id}}" but "${expr.fieldKey}" is not a choice input \u2014 \`.id\` only applies to single or multiple choice.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (ownerId === hostScreenId) {
|
|
312
|
+
const key = `same:${hostScreenId}:${layerId}:${expr.fieldKey}`;
|
|
313
|
+
if (seen.has(key)) continue;
|
|
314
|
+
seen.add(key);
|
|
315
|
+
out.push(
|
|
316
|
+
`Warning: Screen "${screenLabel}" references "{{${expr.fieldKey}}}" which is collected on the same screen \u2014 it will be empty until after the user submits.`
|
|
317
|
+
);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const dset = dom.get(hostScreenId);
|
|
321
|
+
if (dset && !dset.has(ownerId)) {
|
|
322
|
+
const key = `path:${hostScreenId}:${layerId}:${expr.fieldKey}`;
|
|
323
|
+
if (seen.has(key)) continue;
|
|
324
|
+
seen.add(key);
|
|
325
|
+
out.push(
|
|
326
|
+
`Warning: Screen "${screenLabel}" references "{{${expr.fieldKey}}}" which may be empty on some paths (not collected before this screen on every route).`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var collectInterpolationWarnings = (manifest) => {
|
|
333
|
+
const dom = computeDominators(manifest);
|
|
334
|
+
const out = [];
|
|
335
|
+
const seen = /* @__PURE__ */ new Set();
|
|
336
|
+
for (const screen of manifest.screens) {
|
|
337
|
+
walkScreen(screen, (l) => {
|
|
338
|
+
if (l.kind !== "text") return;
|
|
339
|
+
scanLocalizedForWarnings(manifest, screen.id, l.text, l.id, dom, seen, out);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
};
|
|
344
|
+
var collectDecisionWarnings = (manifest) => {
|
|
345
|
+
const dom = computeDominators(manifest);
|
|
346
|
+
const out = [];
|
|
347
|
+
for (const dn of manifest.decisionNodes ?? []) {
|
|
348
|
+
const label = dn.name ?? dn.id;
|
|
349
|
+
const dset = dom.get(dn.id);
|
|
350
|
+
for (const fk of collectDecisionFieldKeysFromNode(dn)) {
|
|
351
|
+
const ownerId = fieldKeyOwnerScreenId(manifest, fk);
|
|
352
|
+
if (!ownerId) {
|
|
353
|
+
out.push(`Warning: Decision "${label}" references unknown fieldKey "${fk}".`);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (dset && !dset.has(ownerId)) {
|
|
357
|
+
out.push(
|
|
358
|
+
`Warning: Decision "${label}" references "${fk}" which may be empty on some paths (not collected before this decision on every route).`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
};
|
|
365
|
+
var upstreamFieldKeysForPicker = (manifest, screenId) => {
|
|
366
|
+
const upstream = new Set(upstreamScreenIdsForPicker(manifest, screenId));
|
|
367
|
+
const keys = collectFieldKeys(manifest).filter((e) => upstream.has(e.screenId)).map((e) => e.fieldKey);
|
|
368
|
+
return [...new Set(keys)].sort();
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export { collectDecisionWarnings, collectInterpolationWarnings, computeDominators, upstreamFieldKeysForPicker, upstreamScreenIdsForPicker };
|
|
372
|
+
//# sourceMappingURL=flowGraph.js.map
|
|
373
|
+
//# sourceMappingURL=flowGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/layers.ts","../src/stateMachine/flowSession.ts","../src/interpolateTemplate.ts","../src/flowGraph.ts"],"names":["OS_PERMISSION_OUTCOME_END","OS_PERMISSION_OUTCOME_CONTINUE"],"mappings":"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","file":"flowGraph.js","sourcesContent":["import type { Branding } from '@getrheo/contracts/dashboard';\nimport type { FlowManifest, Theme } from '@getrheo/contracts/manifest';\nimport type { Screen } from '@getrheo/contracts/screens';\nimport {\n brandGradientFromThemedColor,\n brandGradientNativeLinear,\n brandGradientSolidFallback,\n isStoredLinearGradientCss,\n nativeLinearFromAngleAndStops,\n parseLinearGradientCss,\n resolveBrandGradientToken,\n type BrandGradientNativeLinear,\n} from './brandGradient';\nimport type {\n InputLayer,\n Layer,\n MultipleChoiceLayer,\n ScaleInputLayer,\n SingleChoiceLayer,\n StackLayer,\n TextInputLayer,\n ThemedColor,\n} from '@getrheo/contracts/layers';\nimport type { LocalizedText } from '@getrheo/contracts/localized';\nimport { resolveLocalizedText } from '@getrheo/contracts/localized';\nimport { isInputLayer } from '@getrheo/contracts/layers';\n\n/** Walk a layer tree depth-first. */\nexport const walkLayers = (root: Layer, fn: (l: Layer, depth: number) => void): void => {\n const visit = (l: Layer, depth: number): void => {\n fn(l, depth);\n if (l.kind === 'stack') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'carousel') l.slides.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'button') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'back_button') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'hyperlink') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'single_choice' || l.kind === 'multiple_choice') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'text_input' || l.kind === 'scale_input') {\n l.children?.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'oauth_login') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'oauth_provider' && l.variant === 'custom') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'email_password_auth') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'email_password_field') {\n l.children?.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'email_password_submit') {\n l.children.forEach((c) => visit(c, depth + 1));\n }\n };\n visit(root, 0);\n};\n\n/** Walk every layer in a screen's regions (header → body → footer). */\nexport const walkScreen = (screen: Screen, fn: (l: Layer) => void): void => {\n if (screen.regions.header) walkLayers(screen.regions.header, fn);\n walkLayers(screen.regions.body, fn);\n if (screen.regions.footer) walkLayers(screen.regions.footer, fn);\n};\n\n/** Find the screen's lone input layer (if any). Schema enforces ≤1. */\nexport const findInputLayer = (screen: Screen): InputLayer | null => {\n let found: InputLayer | null = null;\n walkScreen(screen, (l) => {\n if (!found && isInputLayer(l)) found = l;\n });\n return found;\n};\n\n/** Input kinds that use a screen draft and require an explicit Continue to submit. */\nexport const findManualSubmitInputLayer = (\n screen: Screen,\n): MultipleChoiceLayer | TextInputLayer | ScaleInputLayer | null => {\n const input = findInputLayer(screen);\n if (!input) return null;\n if (input.kind === 'multiple_choice' || input.kind === 'text_input' || input.kind === 'scale_input') {\n return input;\n }\n return null;\n};\n\n/**\n * Whether the screen contains any Button layer that submits the screen\n * (i.e. `action.kind === 'continue'`). Used by input layers to decide\n * between auto-submit-on-tap (legacy behaviour for choice-only screens)\n * and writing into the screen-level draft for a Button to submit.\n */\nexport const screenHasContinueButton = (screen: Screen): boolean => {\n let found = false;\n walkScreen(screen, (l) => {\n if (l.kind === 'button' && l.action.kind === 'continue') found = true;\n });\n return found;\n};\n\n/**\n * Resolve a choice layer's option stack by stable optionId via its\n * binding. Returns null when the binding is missing or the bound child\n * was removed (manifest validation rejects that, but runtime stays safe).\n */\nexport const findOptionStackForChoice = (\n layer: SingleChoiceLayer | MultipleChoiceLayer,\n optionId: string,\n): StackLayer | null => {\n const binding = layer.optionBindings.find((b) => b.optionId === optionId);\n if (!binding) return null;\n const stack = layer.children.find((c) => c.id === binding.rootLayerId);\n return stack ?? null;\n};\n\n/**\n * Best-effort textual label for a choice option, used by interpolation\n * (e.g. `{{ goal }}` rendering the chosen option's label) and by editor\n * surfaces that show option rows. Walks the option's child subtree\n * depth-first and returns the first `text` layer's content; falls back\n * to the option's stable id when no text is present.\n */\nexport const choiceOptionLabel = (\n layer: SingleChoiceLayer | MultipleChoiceLayer,\n optionId: string,\n locale: string,\n): string => {\n const stack = findOptionStackForChoice(layer, optionId);\n if (!stack) return '';\n let foundText: LocalizedText | null = null;\n walkLayers(stack, (l) => {\n if (foundText) return;\n if (l.kind === 'text') foundText = l.text;\n });\n if (foundText) return resolveLocalizedText(foundText, locale);\n return optionId;\n};\n\n/** Find a layer in a screen by id, including nested children/slides. */\nexport const findLayerById = (screen: Screen, id: string): Layer | null => {\n let found: Layer | null = null;\n walkScreen(screen, (l) => {\n if (!found && l.id === id) found = l;\n });\n return found;\n};\n\n/** Collect all input fieldKeys across a manifest. */\nexport const collectFieldKeys = (manifest: FlowManifest): { fieldKey: string; screenId: string }[] => {\n const out: { fieldKey: string; screenId: string }[] = [];\n for (const screen of manifest.screens) {\n walkScreen(screen as unknown as Screen, (l) => {\n if (isInputLayer(l)) out.push({ fieldKey: l.fieldKey, screenId: screen.id });\n if (l.kind === 'checkbox') out.push({ fieldKey: l.fieldKey, screenId: screen.id });\n if (l.kind === 'email_password_auth') {\n out.push({ fieldKey: l.fieldKey, screenId: screen.id });\n }\n });\n }\n return out;\n};\n\n/**\n * Pick a snake_case field key starting from `base` that is not in `used`\n * (e.g. `text` → `text_2` → `text_3` when `text` is taken).\n */\nexport const nextUniqueFieldKey = (base: string, used: Iterable<string>): string => {\n const set = used instanceof Set ? used : new Set(used);\n if (!set.has(base)) return base;\n let n = 2;\n while (set.has(`${base}_${n}`)) n += 1;\n return `${base}_${n}`;\n};\n\n/**\n * Resolve a token reference like `$primary` to a literal value from `theme`.\n * Pass-through for non-token strings; returns undefined for `undefined`.\n */\nexport const resolveTokens = <T extends string | undefined>(\n theme: Theme | undefined,\n value: T,\n): T | string => {\n if (value === undefined) return value;\n if (typeof value !== 'string' || !value.startsWith('$')) return value;\n const key = value.slice(1) as keyof Theme;\n const literal = theme?.[key];\n if (typeof literal === 'string') return literal;\n return value;\n};\n\n/**\n * Resolve a layer color for the current appearance (`light` | `dark`).\n * Plain string uses `resolveTokens` for both modes (legacy). Object form\n * picks `light` / `dark` with fallback to the other key when one is omitted.\n */\nexport const resolveThemedColor = (\n theme: Theme | undefined,\n palette: 'light' | 'dark',\n value: ThemedColor | undefined,\n): string | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === 'string') return resolveTokens(theme, value) as string;\n const raw = palette === 'dark' ? (value.dark ?? value.light) : (value.light ?? value.dark);\n if (raw === undefined) return undefined;\n return resolveTokens(theme, raw) as string;\n};\n\n/**\n * Resolve a themed value used for CSS `background` (or RN background fill).\n * Supports `$brandGradient:<uuid>` when branding presets are provided; other values match {@link resolveThemedColor}.\n */\nexport const resolveThemedBackground = (\n theme: Theme | undefined,\n branding: Branding | undefined,\n palette: 'light' | 'dark',\n value: ThemedColor | undefined,\n): string | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === 'string') {\n if (value.startsWith('$brandGradient:')) {\n return resolveBrandGradientToken(branding, value);\n }\n return resolveTokens(theme, value) as string;\n }\n const raw = palette === 'dark' ? (value.dark ?? value.light) : (value.light ?? value.dark);\n if (raw === undefined) return undefined;\n if (raw.startsWith('$brandGradient:')) {\n return resolveBrandGradientToken(branding, raw);\n }\n return resolveTokens(theme, raw) as string;\n};\n\nexport const nativeBrandBackgroundFromThemedColor = (\n theme: Theme | undefined,\n branding: Branding | undefined,\n palette: 'light' | 'dark',\n value: ThemedColor | undefined,\n): { solid?: string; linear?: BrandGradientNativeLinear } => {\n const preset = brandGradientFromThemedColor(branding, palette, value);\n if (preset) {\n const lin = brandGradientNativeLinear(preset);\n if (lin) return { linear: lin };\n return { solid: brandGradientSolidFallback(preset) };\n }\n const bg = resolveThemedBackground(theme, branding, palette, value) as string | undefined;\n if (!bg) return {};\n const parsed = parseLinearGradientCss(bg);\n if (parsed) {\n return {\n linear: nativeLinearFromAngleAndStops(\n parsed.angleDeg,\n parsed.stops.map((s) => ({ color: s.color, offsetPct: s.offsetPct })),\n ),\n };\n }\n if (isStoredLinearGradientCss(bg)) {\n const first = bg.match(/#[0-9a-fA-F]{3,8}/);\n return { solid: first ? first[0] : '#808080' };\n }\n return { solid: bg };\n};\n","import type { FlowManifest } from '@getrheo/contracts/manifest';\nimport type { Screen } from '@getrheo/contracts/screens';\n\n\n\n\nimport type {\n ExternalSurfaceNode,\n} from '@getrheo/contracts/externalSurfaces';\n\nimport type { DecisionEvaluationTelemetry } from '../decisionEval.js';\nimport type { StepResponse } from './stepResponse.js';\n\nexport type FlowSessionContext = {\n locale: string;\n platform: string;\n sdkAttributes: Record<string, unknown>;\n};\n\nexport type FlowState = {\n manifest: FlowManifest;\n currentScreenId: string | null;\n /**\n * Non-null while the flow is awaiting an outcome from an external surface\n * (e.g. RevenueCat paywall). `currentScreenId` is null in this state so the\n * native renderer doesn't try to paint a screen.\n */\n pendingExternalSurface: { nodeId: string } | null;\n history: string[];\n /** Responses keyed by `fieldKey` (or screen id for non-input responses like CTA/skip). */\n responses: Record<string, StepResponse>;\n session: FlowSessionContext;\n status: 'idle' | 'running' | 'completed' | 'abandoned';\n startedAt: string | null;\n completedAt: string | null;\n};\n\nexport type SubmitResponseOptions = {\n now?: string;\n onDecisionEvaluated?: (payload: DecisionEvaluationTelemetry) => void;\n};\n\n\nexport const findScreen = (manifest: FlowManifest, screenId: string): Screen | undefined =>\n manifest.screens.find((s) => s.id === screenId) as Screen | undefined;\n\nexport const findExternalSurface = (\n manifest: FlowManifest,\n nodeId: string,\n): ExternalSurfaceNode | undefined =>\n // Older / hand-constructed manifests may omit `externalSurfaceNodes` entirely;\n // treat that as \"no surfaces\" rather than crashing the state machine.\n (manifest.externalSurfaceNodes ?? []).find((n) => n.id === nodeId);\n\nexport const initFlowState = (\n manifest: FlowManifest,\n sessionPartial?: Partial<FlowSessionContext>,\n): FlowState => ({\n manifest,\n currentScreenId: null,\n pendingExternalSurface: null,\n history: [],\n responses: {},\n session: {\n locale: sessionPartial?.locale ?? manifest.defaultLocale,\n platform: sessionPartial?.platform ?? 'unknown',\n sdkAttributes: sessionPartial?.sdkAttributes ?? {},\n },\n status: 'idle',\n startedAt: null,\n completedAt: null,\n});\n","import { FIELD_KEY_RE } from '@getrheo/contracts/fields';\nimport type { FlowManifest } from '@getrheo/contracts/manifest';\nimport type { Screen } from '@getrheo/contracts/screens';\nimport type { LocalizedText } from '@getrheo/contracts/localized';\nimport { resolveLocalizedText } from '@getrheo/contracts/localized';\nimport { choiceOptionLabel, findInputLayer } from './layers';\nimport type { StepResponse } from './stateMachine';\n\n/** Runtime substitution for Text layer copy (after locale resolution). */\nexport type InterpolationRuntimeContext = {\n manifest: FlowManifest;\n locale: string;\n responses: Record<string, StepResponse>;\n customProperties?: Record<string, string>;\n};\n\n/** Props-sized slice passed into renderers. */\nexport type InterpolationContext = {\n responses: Record<string, StepResponse>;\n customProperties?: Record<string, string>;\n /** When true, the flow has a prior step so back navigation is available. */\n canGoBack?: boolean;\n};\n\nexport const extractLiquidTemplateBodies = (s: string): string[] => {\n const out: string[] = [];\n let i = 0;\n while (i < s.length) {\n const open = s.indexOf('{{', i);\n if (open === -1) break;\n const close = s.indexOf('}}', open + 2);\n if (close === -1) break;\n out.push(s.slice(open + 2, close));\n i = close + 2;\n }\n return out;\n};\n\nconst FIELD_KEY_SOURCE = FIELD_KEY_RE.source.replace(/^\\^|\\$$/g, '');\n\nconst parseDefaultFilter = (\n inner: string,\n): { exprPart: string; defaultValue?: string } => {\n const m = inner.match(/\\s*\\|\\s*default\\s*:/i);\n if (!m || m.index === undefined) return { exprPart: inner.trim() };\n\n const exprPart = inner.slice(0, m.index).trim();\n let tail = inner.slice(m.index + m[0].length).trim();\n if (tail.startsWith('\"')) {\n const end = tail.indexOf('\"', 1);\n if (end > 0) tail = tail.slice(1, end);\n else tail = tail.slice(1);\n }\n return { exprPart, defaultValue: tail };\n};\n\nconst parseExpression = (\n exprRaw: string,\n):\n | { kind: 'custom'; key: string }\n | { kind: 'field'; fieldKey: string; mode: 'label' | 'id' }\n | { kind: 'invalid' } => {\n const expr = exprRaw.trim();\n const customM = expr.match(new RegExp(`^custom\\\\.(${FIELD_KEY_SOURCE})$`));\n if (customM) return { kind: 'custom', key: customM[1]! };\n\n const idM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})\\\\.id$`));\n if (idM) return { kind: 'field', fieldKey: idM[1]!, mode: 'id' };\n\n const plainM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})$`));\n if (plainM) return { kind: 'field', fieldKey: plainM[1]!, mode: 'label' };\n\n return { kind: 'invalid' };\n};\n\nexport type InterpolationExprAnalysis =\n | { kind: 'custom'; key: string }\n | { kind: 'field'; fieldKey: string; mode: 'label' | 'id' }\n | { kind: 'invalid' };\n\n/** Parse one `{{ … }}` inner (after stripping delimiters) for diagnostics / validation. */\nexport const analyzeLiquidTemplateInner = (\n inner: string,\n): { expr: InterpolationExprAnalysis; defaultValue?: string } => {\n const { exprPart, defaultValue } = parseDefaultFilter(inner);\n const p = parseExpression(exprPart);\n if (p.kind === 'invalid') return { expr: { kind: 'invalid' }, defaultValue };\n if (p.kind === 'custom') return { expr: { kind: 'custom', key: p.key }, defaultValue };\n return { expr: { kind: 'field', fieldKey: p.fieldKey, mode: p.mode }, defaultValue };\n};\n\nconst findInputOwner = (\n manifest: FlowManifest,\n fieldKey: string,\n): { screen: Screen; input: NonNullable<ReturnType<typeof findInputLayer>> } | null => {\n for (const screen of manifest.screens) {\n const input = findInputLayer(screen as Screen);\n if (input && input.fieldKey === fieldKey) return { screen: screen as Screen, input };\n }\n return null;\n};\n\nconst choiceLabelFor = (\n manifest: FlowManifest,\n fieldKey: string,\n choiceId: string,\n locale: string,\n): string => {\n const owner = findInputOwner(manifest, fieldKey);\n if (!owner) return '';\n const { input } = owner;\n if (input.kind !== 'single_choice' && input.kind !== 'multiple_choice') return '';\n return choiceOptionLabel(input, choiceId, locale);\n};\n\nconst responseToDisplayString = (\n manifest: FlowManifest,\n fieldKey: string,\n response: StepResponse | undefined,\n mode: 'label' | 'id',\n locale: string,\n): string => {\n if (!response) return '';\n switch (response.kind) {\n case 'text':\n return mode === 'id' ? '' : response.value;\n case 'scale':\n return mode === 'id' ? '' : String(response.value);\n case 'choice':\n if (mode === 'id') return response.choiceId;\n return choiceLabelFor(manifest, fieldKey, response.choiceId, locale);\n case 'multiChoice':\n if (mode === 'id') return response.choiceIds.join(', ');\n return response.choiceIds\n .map((id) => choiceLabelFor(manifest, fieldKey, id, locale))\n .filter(Boolean)\n .join(', ');\n case 'checkbox':\n return mode === 'id' ? '' : response.value ? 'true' : 'false';\n case 'bypass_input':\n return '';\n default:\n return '';\n }\n};\n\nconst evalParsed = (\n parsed:\n | { kind: 'custom'; key: string }\n | { kind: 'field'; fieldKey: string; mode: 'label' | 'id' }\n | { kind: 'invalid' },\n ctx: InterpolationRuntimeContext,\n): string => {\n if (parsed.kind === 'invalid') return '';\n if (parsed.kind === 'custom') {\n const v = ctx.customProperties?.[parsed.key];\n return v === undefined || v === '' ? '' : String(v);\n }\n const owner = findInputOwner(ctx.manifest, parsed.fieldKey);\n const r = ctx.responses[parsed.fieldKey];\n if (parsed.mode === 'id' && owner && owner.input.kind !== 'single_choice' && owner.input.kind !== 'multiple_choice') {\n return '';\n }\n return responseToDisplayString(ctx.manifest, parsed.fieldKey, r, parsed.mode, ctx.locale);\n};\n\n/**\n * Interpolate `{{ … }}` segments in a plain string (already localized).\n * Supports `custom.key`, `field_key`, `field_key.id`, and `| default: …`.\n */\nexport const interpolateTemplateString = (\n template: string,\n ctx: InterpolationRuntimeContext,\n): string => {\n let i = 0;\n let out = '';\n while (i < template.length) {\n const open = template.indexOf('{{', i);\n if (open === -1) {\n out += template.slice(i);\n break;\n }\n out += template.slice(i, open);\n const close = template.indexOf('}}', open + 2);\n if (close === -1) {\n out += template.slice(open);\n break;\n }\n const inner = template.slice(open + 2, close);\n const { exprPart, defaultValue } = parseDefaultFilter(inner);\n const parsed = parseExpression(exprPart);\n let value = evalParsed(parsed, ctx);\n if ((value === '' || value === undefined) && defaultValue !== undefined) {\n value = defaultValue;\n }\n out += value;\n i = close + 2;\n }\n return out;\n};\n\nexport const resolveAndInterpolateLocalizedText = (\n text: LocalizedText,\n ctx: InterpolationRuntimeContext,\n): string => {\n const localized = resolveLocalizedText(text, ctx.locale);\n return interpolateTemplateString(localized, ctx);\n};\n","import type { FlowManifest } from '@getrheo/contracts/manifest';\nimport type { Screen } from '@getrheo/contracts/screens';\nimport type { LocalizedText } from '@getrheo/contracts/localized';\nimport { OS_PERMISSION_OUTCOME_CONTINUE, OS_PERMISSION_OUTCOME_END } from '@getrheo/contracts/layers';\nimport { collectDecisionFieldKeysFromNode } from '@getrheo/contracts/decisions';\nimport { collectFieldKeys, findInputLayer, walkScreen } from './layers';\nimport { findScreen } from './stateMachine';\nimport { analyzeLiquidTemplateInner, extractLiquidTemplateBodies } from './interpolateTemplate';\n\nconst localizedStrings = (t: LocalizedText): string[] => {\n const out: string[] = [t.default];\n if (t.translations) {\n for (const loc of Object.keys(t.translations)) {\n if (/^[a-z]{2}(-[A-Z]{2})?$/.test(loc)) {\n const v = t.translations[loc];\n if (v) out.push(v);\n }\n }\n }\n return out;\n};\n\nconst collectOutgoingTargets = (screen: Screen): string[] => {\n const out: string[] = [];\n if (screen.next.default) out.push(screen.next.default);\n walkScreen(screen, (l) => {\n if (l.kind === 'single_choice' || l.kind === 'multiple_choice') {\n if (l.branching.enabled) {\n for (const c of l.branching.conditions) out.push(c.goTo);\n }\n }\n if (l.kind === 'button' && l.action.kind === 'go_to_step') out.push(l.action.screenId);\n if (l.kind === 'button' && l.action.kind === 'request_os_permission') {\n const o = l.action.outcomes;\n for (const t of [o.granted, o.denied, o.blocked]) {\n if (t === OS_PERMISSION_OUTCOME_END) {\n // Terminal branch — no outgoing edge.\n } else if (t === OS_PERMISSION_OUTCOME_CONTINUE) {\n if (screen.next.default) out.push(screen.next.default);\n } else {\n out.push(t);\n }\n }\n }\n if (l.kind === 'button' && l.action.kind === 'go_back_one_screen' && l.action.fallbackScreenId) {\n out.push(l.action.fallbackScreenId);\n }\n if (l.kind === 'back_button' && l.fallbackScreenId) out.push(l.fallbackScreenId);\n });\n return out;\n};\n\nconst buildForwardAdjacency = (manifest: FlowManifest): Map<string, string[]> => {\n const m = new Map<string, string[]>();\n for (const s of manifest.screens) {\n m.set(s.id, collectOutgoingTargets(s as Screen));\n }\n for (const d of manifest.decisionNodes ?? []) {\n const outs = [\n ...d.cases.map((c) => c.next).filter((x): x is string => x != null),\n d.elseNext,\n ].filter((x): x is string => x != null);\n m.set(d.id, outs);\n }\n return m;\n};\n\nconst reverseAdjacency = (forward: Map<string, string[]>): Map<string, string[]> => {\n const rev = new Map<string, string[]>();\n for (const [u, vs] of forward) {\n for (const v of vs) {\n const arr = rev.get(v) ?? [];\n arr.push(u);\n rev.set(v, arr);\n }\n }\n return rev;\n};\n\nconst bfsForward = (entry: string | null, forward: Map<string, string[]>): Set<string> => {\n if (entry == null) return new Set();\n const seen = new Set<string>([entry]);\n const q = [entry];\n while (q.length) {\n const u = q.shift()!;\n for (const v of forward.get(u) ?? []) {\n if (!seen.has(v)) {\n seen.add(v);\n q.push(v);\n }\n }\n }\n return seen;\n};\n\n/** All screens `t` (including `targetId`) such that some path `t → … → targetId` exists, restricted to `allowed` nodes. */\nconst reverseReachable = (\n targetId: string,\n rev: Map<string, string[]>,\n allowed: Set<string>,\n): Set<string> => {\n const seen = new Set<string>([targetId]);\n const q = [targetId];\n while (q.length) {\n const u = q.shift()!;\n for (const v of rev.get(u) ?? []) {\n if (!allowed.has(v)) continue;\n if (!seen.has(v)) {\n seen.add(v);\n q.push(v);\n }\n }\n }\n return seen;\n};\n\n/**\n * Screens that may have already been completed before `screenId` is shown\n * (strict predecessors on some path from entry), intersected with reachability from entry.\n */\nexport const upstreamScreenIdsForPicker = (manifest: FlowManifest, screenId: string): string[] => {\n const forward = buildForwardAdjacency(manifest);\n const rev = reverseAdjacency(forward);\n const entry = manifest.entryScreenId;\n if (entry == null) return [];\n const reachFromEntry = bfsForward(entry, forward);\n const ancestors = reverseReachable(screenId, rev, reachFromEntry);\n const screenSet = new Set(manifest.screens.map((s) => s.id));\n return [...ancestors].filter((id) => id !== screenId && screenSet.has(id));\n};\n\n/** Dominator sets for nodes reachable from entry (each set includes the node itself). */\nexport const computeDominators = (manifest: FlowManifest): Map<string, Set<string>> => {\n const forward = buildForwardAdjacency(manifest);\n const entry = manifest.entryScreenId;\n if (entry == null) return new Map();\n const reachable = bfsForward(entry, forward);\n const nodes = [...reachable];\n\n const pred = new Map<string, string[]>();\n for (const [u, vs] of forward) {\n for (const v of vs) {\n if (!reachable.has(v)) continue;\n const arr = pred.get(v) ?? [];\n arr.push(u);\n pred.set(v, arr);\n }\n }\n\n const all = new Set(nodes);\n const dom = new Map<string, Set<string>>();\n for (const n of nodes) {\n if (n === entry) dom.set(n, new Set([entry]));\n else dom.set(n, new Set(all));\n }\n\n let changed = true;\n while (changed) {\n changed = false;\n for (const n of nodes) {\n if (n === entry) continue;\n const ps = (pred.get(n) ?? []).filter((p) => reachable.has(p));\n let nextDom: Set<string>;\n if (ps.length === 0) {\n nextDom = new Set([n]);\n } else {\n nextDom = new Set([n]);\n const first = dom.get(ps[0]!) ?? new Set(all);\n let inter = new Set(first);\n for (let i = 1; i < ps.length; i++) {\n const d = dom.get(ps[i]!) ?? new Set(all);\n inter = new Set([...inter].filter((x) => d.has(x)));\n }\n for (const x of inter) nextDom.add(x);\n }\n const cur = dom.get(n)!;\n if (cur.size !== nextDom.size || [...nextDom].some((x) => !cur.has(x))) {\n dom.set(n, nextDom);\n changed = true;\n }\n }\n }\n return dom;\n};\n\nconst fieldKeyOwnerScreenId = (manifest: FlowManifest, fieldKey: string): string | null => {\n for (const e of collectFieldKeys(manifest)) {\n if (e.fieldKey === fieldKey) return e.screenId;\n }\n return null;\n};\n\nconst scanLocalizedForWarnings = (\n manifest: FlowManifest,\n hostScreenId: string,\n text: LocalizedText,\n layerId: string,\n dom: Map<string, Set<string>>,\n seen: Set<string>,\n out: string[],\n): void => {\n const screenLabel =\n manifest.screens.find((s) => s.id === hostScreenId)?.name ?? hostScreenId;\n\n for (const str of localizedStrings(text)) {\n for (const inner of extractLiquidTemplateBodies(str)) {\n const { expr } = analyzeLiquidTemplateInner(inner);\n if (expr.kind === 'invalid') {\n const key = `invalid:${hostScreenId}:${layerId}:${inner}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(\n `Warning: Screen \"${screenLabel}\" text layer \"${layerId}\" has an invalid template token \"{{${inner}}}\".`,\n );\n continue;\n }\n if (expr.kind === 'custom') {\n const key = `custom:${hostScreenId}`;\n if (!seen.has(key)) {\n seen.add(key);\n out.push(\n `Warning: Screen \"${screenLabel}\" uses \\`custom.*\\` template variables — values are supplied by the host SDK at runtime.`,\n );\n }\n continue;\n }\n\n const ownerId = fieldKeyOwnerScreenId(manifest, expr.fieldKey);\n if (!ownerId) {\n const key = `unknown:${hostScreenId}:${layerId}:${expr.fieldKey}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(\n `Warning: Screen \"${screenLabel}\" references unknown variable \"{{${expr.fieldKey}}}\" (no matching input fieldKey).`,\n );\n continue;\n }\n\n if (expr.mode === 'id') {\n const ownerScreen = findScreen(manifest, ownerId);\n const input = ownerScreen ? findInputLayer(ownerScreen) : null;\n if (input?.kind !== 'single_choice' && input?.kind !== 'multiple_choice') {\n const key = `id:${hostScreenId}:${layerId}:${expr.fieldKey}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(\n `Warning: Screen \"${screenLabel}\" uses \"{{${expr.fieldKey}.id}}\" but \"${expr.fieldKey}\" is not a choice input — \\`.id\\` only applies to single or multiple choice.`,\n );\n }\n }\n\n if (ownerId === hostScreenId) {\n const key = `same:${hostScreenId}:${layerId}:${expr.fieldKey}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(\n `Warning: Screen \"${screenLabel}\" references \"{{${expr.fieldKey}}}\" which is collected on the same screen — it will be empty until after the user submits.`,\n );\n continue;\n }\n\n const dset = dom.get(hostScreenId);\n if (dset && !dset.has(ownerId)) {\n const key = `path:${hostScreenId}:${layerId}:${expr.fieldKey}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(\n `Warning: Screen \"${screenLabel}\" references \"{{${expr.fieldKey}}}\" which may be empty on some paths (not collected before this screen on every route).`,\n );\n }\n }\n }\n};\n\n/**\n * Non-blocking warnings for Text layer `{{ … }}` usage (dominance, same-screen, invalid tokens).\n */\nexport const collectInterpolationWarnings = (manifest: FlowManifest): string[] => {\n const dom = computeDominators(manifest);\n const out: string[] = [];\n const seen = new Set<string>();\n\n for (const screen of manifest.screens as Screen[]) {\n walkScreen(screen, (l) => {\n if (l.kind !== 'text') return;\n scanLocalizedForWarnings(manifest, screen.id, l.text, l.id, dom, seen, out);\n });\n }\n\n return out;\n};\n\n/** Dominance warnings for `fieldKey` references inside decision expressions. */\nexport const collectDecisionWarnings = (manifest: FlowManifest): string[] => {\n const dom = computeDominators(manifest);\n const out: string[] = [];\n for (const dn of manifest.decisionNodes ?? []) {\n const label = dn.name ?? dn.id;\n const dset = dom.get(dn.id);\n for (const fk of collectDecisionFieldKeysFromNode(dn)) {\n const ownerId = fieldKeyOwnerScreenId(manifest, fk);\n if (!ownerId) {\n out.push(`Warning: Decision \"${label}\" references unknown fieldKey \"${fk}\".`);\n continue;\n }\n if (dset && !dset.has(ownerId)) {\n out.push(\n `Warning: Decision \"${label}\" references \"${fk}\" which may be empty on some paths (not collected before this decision on every route).`,\n );\n }\n }\n }\n return out;\n};\n\n/** `fieldKey` values available in the variable picker for `screenId`. */\nexport const upstreamFieldKeysForPicker = (manifest: FlowManifest, screenId: string): string[] => {\n const upstream = new Set(upstreamScreenIdsForPicker(manifest, screenId));\n const keys = collectFieldKeys(manifest)\n .filter((e) => upstream.has(e.screenId))\n .map((e) => e.fieldKey);\n return [...new Set(keys)].sort();\n};\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FlowManifest } from '@getrheo/contracts/manifest';
|
|
2
|
+
import { Layer } from '@getrheo/contracts/layers';
|
|
3
|
+
import { InterpolationContext } from './interpolateTemplate.js';
|
|
4
|
+
import '@getrheo/contracts/localized';
|
|
5
|
+
import './stepResponse-BXgoZ7o-.js';
|
|
6
|
+
import '@getrheo/contracts/externalSurfaces';
|
|
7
|
+
|
|
8
|
+
type ResolveHyperlinkPreviewLabelArgs = {
|
|
9
|
+
manifest: FlowManifest;
|
|
10
|
+
locale: string;
|
|
11
|
+
interpolationContext?: InterpolationContext;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* First resolved text substring under a hyperlink’s children (preview / dialogs / a11y).
|
|
15
|
+
* Depth-first; ignores non-text subtrees until a Text layer is found.
|
|
16
|
+
*/
|
|
17
|
+
declare const resolveHyperlinkPreviewLabel: (roots: Layer[], args: ResolveHyperlinkPreviewLabelArgs) => string;
|
|
18
|
+
|
|
19
|
+
export { type ResolveHyperlinkPreviewLabelArgs, resolveHyperlinkPreviewLabel };
|