@narrative.io/jsonforms-provider-protocols 3.0.0-beta.17 → 3.0.0-beta.19

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.
@@ -0,0 +1,308 @@
1
+ const RING_SIZE = 250;
2
+ const PANEL_ID = "__pp_debug_panel__";
3
+ const STYLE_ID = "__pp_debug_styles__";
4
+ let entries = [];
5
+ let panel = null;
6
+ let listEl = null;
7
+ function isEnabled() {
8
+ if (typeof window === "undefined") return false;
9
+ const w = window;
10
+ if (w.__PP_DEBUG__ === true) return true;
11
+ try {
12
+ return window.localStorage?.getItem("PP_DEBUG") === "1";
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ function safeStringify(value) {
18
+ const seen = /* @__PURE__ */ new WeakSet();
19
+ try {
20
+ const out = JSON.stringify(
21
+ value,
22
+ (_key, v) => {
23
+ if (typeof v === "object" && v !== null) {
24
+ if (seen.has(v)) return "[Circular]";
25
+ seen.add(v);
26
+ }
27
+ if (typeof v === "function")
28
+ return `[Function ${v.name || "anonymous"}]`;
29
+ if (typeof v === "undefined") return "[undefined]";
30
+ return v;
31
+ },
32
+ 2
33
+ );
34
+ return out ?? String(value);
35
+ } catch (e) {
36
+ return `[unstringifiable: ${e.message}]`;
37
+ }
38
+ }
39
+ function ensureStyles() {
40
+ if (document.getElementById(STYLE_ID)) return;
41
+ const style = document.createElement("style");
42
+ style.id = STYLE_ID;
43
+ style.textContent = `
44
+ #${PANEL_ID} {
45
+ position: fixed;
46
+ top: 12px;
47
+ left: 12px;
48
+ width: 460px;
49
+ max-height: 70vh;
50
+ z-index: 2147483647;
51
+ background: #0b0f17;
52
+ color: #d6e1ff;
53
+ border: 1px solid #2a3656;
54
+ border-radius: 6px;
55
+ font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
56
+ box-shadow: 0 8px 32px rgba(0,0,0,0.45);
57
+ display: flex;
58
+ flex-direction: column;
59
+ resize: both;
60
+ overflow: hidden;
61
+ }
62
+ #${PANEL_ID} .pp-header {
63
+ display: flex;
64
+ gap: 6px;
65
+ align-items: center;
66
+ padding: 6px 8px;
67
+ background: #131a2c;
68
+ border-bottom: 1px solid #2a3656;
69
+ cursor: move;
70
+ user-select: none;
71
+ flex-shrink: 0;
72
+ }
73
+ #${PANEL_ID} .pp-title {
74
+ flex: 1;
75
+ font-weight: 600;
76
+ color: #9fb4ff;
77
+ }
78
+ #${PANEL_ID} .pp-count {
79
+ color: #6f7fa6;
80
+ font-weight: 400;
81
+ margin-left: 4px;
82
+ }
83
+ #${PANEL_ID} button {
84
+ background: #1f2a47;
85
+ color: #d6e1ff;
86
+ border: 1px solid #2a3656;
87
+ border-radius: 3px;
88
+ padding: 2px 6px;
89
+ cursor: pointer;
90
+ font: inherit;
91
+ line-height: 1.2;
92
+ }
93
+ #${PANEL_ID} button:hover { background: #2a3656; }
94
+ #${PANEL_ID} .pp-list {
95
+ flex: 1;
96
+ overflow: auto;
97
+ padding: 4px 6px;
98
+ }
99
+ #${PANEL_ID}.pp-collapsed { resize: none; max-height: none; }
100
+ #${PANEL_ID}.pp-collapsed .pp-list { display: none; }
101
+ #${PANEL_ID} details {
102
+ margin: 4px 0;
103
+ border: 1px solid #2a3656;
104
+ border-radius: 3px;
105
+ background: #0f1422;
106
+ }
107
+ #${PANEL_ID} summary {
108
+ padding: 4px 6px;
109
+ cursor: pointer;
110
+ display: flex;
111
+ gap: 6px;
112
+ align-items: center;
113
+ list-style: none;
114
+ }
115
+ #${PANEL_ID} summary::-webkit-details-marker { display: none; }
116
+ #${PANEL_ID} summary::before {
117
+ content: "▸";
118
+ color: #6f7fa6;
119
+ width: 10px;
120
+ }
121
+ #${PANEL_ID} details[open] > summary::before { content: "▾"; }
122
+ #${PANEL_ID} .pp-label {
123
+ color: #ffd479;
124
+ font-weight: 600;
125
+ }
126
+ #${PANEL_ID} .pp-ts {
127
+ color: #6f7fa6;
128
+ }
129
+ #${PANEL_ID} summary button { margin-left: auto; }
130
+ #${PANEL_ID} pre {
131
+ margin: 0;
132
+ padding: 6px 8px;
133
+ max-height: 320px;
134
+ overflow: auto;
135
+ background: #050811;
136
+ color: #c9d6ff;
137
+ white-space: pre-wrap;
138
+ word-break: break-word;
139
+ }
140
+ `;
141
+ document.head.appendChild(style);
142
+ }
143
+ function makeButton(label, onClick) {
144
+ const btn = document.createElement("button");
145
+ btn.type = "button";
146
+ btn.textContent = label;
147
+ btn.addEventListener("click", onClick);
148
+ return btn;
149
+ }
150
+ function flashButton(btn, msg, original) {
151
+ btn.textContent = msg;
152
+ setTimeout(() => {
153
+ btn.textContent = original;
154
+ }, 1200);
155
+ }
156
+ function copyToClipboard(text, btn, original) {
157
+ const fallback = () => {
158
+ try {
159
+ const ta = document.createElement("textarea");
160
+ ta.value = text;
161
+ ta.style.position = "fixed";
162
+ ta.style.left = "-9999px";
163
+ document.body.appendChild(ta);
164
+ ta.select();
165
+ document.execCommand("copy");
166
+ document.body.removeChild(ta);
167
+ flashButton(btn, "copied", original);
168
+ } catch {
169
+ flashButton(btn, "fail", original);
170
+ }
171
+ };
172
+ if (navigator.clipboard?.writeText) {
173
+ navigator.clipboard.writeText(text).then(
174
+ () => flashButton(btn, "copied", original),
175
+ fallback
176
+ );
177
+ } else {
178
+ fallback();
179
+ }
180
+ }
181
+ function updateCount() {
182
+ if (!panel) return;
183
+ const countEl = panel.querySelector(".pp-count");
184
+ if (countEl) countEl.textContent = `(${entries.length})`;
185
+ }
186
+ function attachDrag(panelEl, header) {
187
+ let dragging = false;
188
+ let offX = 0;
189
+ let offY = 0;
190
+ header.addEventListener("mousedown", (e) => {
191
+ if (e.target.tagName === "BUTTON") return;
192
+ dragging = true;
193
+ const rect = panelEl.getBoundingClientRect();
194
+ offX = e.clientX - rect.left;
195
+ offY = e.clientY - rect.top;
196
+ e.preventDefault();
197
+ });
198
+ window.addEventListener("mousemove", (e) => {
199
+ if (!dragging) return;
200
+ panelEl.style.left = `${e.clientX - offX}px`;
201
+ panelEl.style.top = `${e.clientY - offY}px`;
202
+ });
203
+ window.addEventListener("mouseup", () => {
204
+ dragging = false;
205
+ });
206
+ }
207
+ function ensurePanel() {
208
+ if (panel && document.body.contains(panel)) return panel;
209
+ ensureStyles();
210
+ panel = document.createElement("div");
211
+ panel.id = PANEL_ID;
212
+ const header = document.createElement("div");
213
+ header.className = "pp-header";
214
+ const title = document.createElement("span");
215
+ title.className = "pp-title";
216
+ title.textContent = "[provider-protocols] debug";
217
+ const count = document.createElement("span");
218
+ count.className = "pp-count";
219
+ count.textContent = `(${entries.length})`;
220
+ title.appendChild(count);
221
+ header.appendChild(title);
222
+ const copyAllBtn = makeButton("copy all", (e) => {
223
+ e.stopPropagation();
224
+ const blob = entries.map((en) => `// ${new Date(en.ts).toISOString()} ${en.label}
225
+ ${en.json}`).join("\n\n");
226
+ copyToClipboard(blob, copyAllBtn, "copy all");
227
+ });
228
+ header.appendChild(copyAllBtn);
229
+ const clearBtn = makeButton("clear", (e) => {
230
+ e.stopPropagation();
231
+ entries = [];
232
+ if (listEl) listEl.innerHTML = "";
233
+ updateCount();
234
+ });
235
+ header.appendChild(clearBtn);
236
+ const collapseBtn = makeButton("−", (e) => {
237
+ e.stopPropagation();
238
+ panel.classList.toggle("pp-collapsed");
239
+ collapseBtn.textContent = panel.classList.contains("pp-collapsed") ? "+" : "−";
240
+ });
241
+ header.appendChild(collapseBtn);
242
+ attachDrag(panel, header);
243
+ panel.appendChild(header);
244
+ listEl = document.createElement("div");
245
+ listEl.className = "pp-list";
246
+ panel.appendChild(listEl);
247
+ document.body.appendChild(panel);
248
+ return panel;
249
+ }
250
+ function renderEntry(entry) {
251
+ if (!listEl) return;
252
+ const det = document.createElement("details");
253
+ det.open = false;
254
+ const sum = document.createElement("summary");
255
+ const lbl = document.createElement("span");
256
+ lbl.className = "pp-label";
257
+ lbl.textContent = entry.label;
258
+ const ts = document.createElement("span");
259
+ ts.className = "pp-ts";
260
+ ts.textContent = new Date(entry.ts).toISOString().slice(11, 23);
261
+ const copyBtn = makeButton("copy", (e) => {
262
+ e.preventDefault();
263
+ e.stopPropagation();
264
+ copyToClipboard(entry.json, copyBtn, "copy");
265
+ });
266
+ sum.appendChild(lbl);
267
+ sum.appendChild(ts);
268
+ sum.appendChild(copyBtn);
269
+ det.appendChild(sum);
270
+ const pre = document.createElement("pre");
271
+ pre.textContent = entry.json;
272
+ det.appendChild(pre);
273
+ listEl.appendChild(det);
274
+ listEl.scrollTop = listEl.scrollHeight;
275
+ while (listEl.children.length > RING_SIZE) {
276
+ const first = listEl.firstChild;
277
+ if (first) listEl.removeChild(first);
278
+ else break;
279
+ }
280
+ }
281
+ function debugLog(label, payload) {
282
+ if (!isEnabled()) return;
283
+ if (typeof document === "undefined") return;
284
+ const entry = {
285
+ ts: Date.now(),
286
+ label,
287
+ json: safeStringify(payload)
288
+ };
289
+ entries.push(entry);
290
+ if (entries.length > RING_SIZE) entries.shift();
291
+ const append = () => {
292
+ try {
293
+ ensurePanel();
294
+ renderEntry(entry);
295
+ updateCount();
296
+ } catch {
297
+ }
298
+ };
299
+ if (document.body) {
300
+ append();
301
+ } else {
302
+ document.addEventListener("DOMContentLoaded", append, { once: true });
303
+ }
304
+ }
305
+ export {
306
+ debugLog
307
+ };
308
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sources":["../../src/debug/overlay.ts"],"sourcesContent":["/**\n * In-DOM debug overlay for `@narrative.io/jsonforms-provider-protocols`.\n *\n * Renders a fixed-position panel with a scrollable list of structured log\n * entries. Used in place of `console.*` because some host apps strip console\n * output in production. No-op unless explicitly enabled at runtime via\n * either `window.__PP_DEBUG__ = true` or `localStorage.PP_DEBUG = \"1\"`.\n *\n * Internal API. Not part of the package's public surface.\n */\n\nconst RING_SIZE = 250;\nconst PANEL_ID = \"__pp_debug_panel__\";\nconst STYLE_ID = \"__pp_debug_styles__\";\n\ninterface Entry {\n ts: number;\n label: string;\n json: string;\n}\n\nlet entries: Entry[] = [];\nlet panel: HTMLElement | null = null;\nlet listEl: HTMLElement | null = null;\n\nfunction isEnabled(): boolean {\n if (typeof window === \"undefined\") return false;\n const w = window as unknown as { __PP_DEBUG__?: boolean };\n if (w.__PP_DEBUG__ === true) return true;\n try {\n return window.localStorage?.getItem(\"PP_DEBUG\") === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n const seen = new WeakSet<object>();\n try {\n const out = JSON.stringify(\n value,\n (_key, v) => {\n if (typeof v === \"object\" && v !== null) {\n if (seen.has(v as object)) return \"[Circular]\";\n seen.add(v as object);\n }\n if (typeof v === \"function\")\n return `[Function ${(v as { name?: string }).name || \"anonymous\"}]`;\n if (typeof v === \"undefined\") return \"[undefined]\";\n return v;\n },\n 2,\n );\n return out ?? String(value);\n } catch (e) {\n return `[unstringifiable: ${(e as Error).message}]`;\n }\n}\n\nfunction ensureStyles(): void {\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = `\n#${PANEL_ID} {\n position: fixed;\n top: 12px;\n left: 12px;\n width: 460px;\n max-height: 70vh;\n z-index: 2147483647;\n background: #0b0f17;\n color: #d6e1ff;\n border: 1px solid #2a3656;\n border-radius: 6px;\n font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;\n box-shadow: 0 8px 32px rgba(0,0,0,0.45);\n display: flex;\n flex-direction: column;\n resize: both;\n overflow: hidden;\n}\n#${PANEL_ID} .pp-header {\n display: flex;\n gap: 6px;\n align-items: center;\n padding: 6px 8px;\n background: #131a2c;\n border-bottom: 1px solid #2a3656;\n cursor: move;\n user-select: none;\n flex-shrink: 0;\n}\n#${PANEL_ID} .pp-title {\n flex: 1;\n font-weight: 600;\n color: #9fb4ff;\n}\n#${PANEL_ID} .pp-count {\n color: #6f7fa6;\n font-weight: 400;\n margin-left: 4px;\n}\n#${PANEL_ID} button {\n background: #1f2a47;\n color: #d6e1ff;\n border: 1px solid #2a3656;\n border-radius: 3px;\n padding: 2px 6px;\n cursor: pointer;\n font: inherit;\n line-height: 1.2;\n}\n#${PANEL_ID} button:hover { background: #2a3656; }\n#${PANEL_ID} .pp-list {\n flex: 1;\n overflow: auto;\n padding: 4px 6px;\n}\n#${PANEL_ID}.pp-collapsed { resize: none; max-height: none; }\n#${PANEL_ID}.pp-collapsed .pp-list { display: none; }\n#${PANEL_ID} details {\n margin: 4px 0;\n border: 1px solid #2a3656;\n border-radius: 3px;\n background: #0f1422;\n}\n#${PANEL_ID} summary {\n padding: 4px 6px;\n cursor: pointer;\n display: flex;\n gap: 6px;\n align-items: center;\n list-style: none;\n}\n#${PANEL_ID} summary::-webkit-details-marker { display: none; }\n#${PANEL_ID} summary::before {\n content: \"▸\";\n color: #6f7fa6;\n width: 10px;\n}\n#${PANEL_ID} details[open] > summary::before { content: \"▾\"; }\n#${PANEL_ID} .pp-label {\n color: #ffd479;\n font-weight: 600;\n}\n#${PANEL_ID} .pp-ts {\n color: #6f7fa6;\n}\n#${PANEL_ID} summary button { margin-left: auto; }\n#${PANEL_ID} pre {\n margin: 0;\n padding: 6px 8px;\n max-height: 320px;\n overflow: auto;\n background: #050811;\n color: #c9d6ff;\n white-space: pre-wrap;\n word-break: break-word;\n}\n`;\n document.head.appendChild(style);\n}\n\nfunction makeButton(label: string, onClick: (e: MouseEvent) => void): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.textContent = label;\n btn.addEventListener(\"click\", onClick);\n return btn;\n}\n\nfunction flashButton(btn: HTMLButtonElement, msg: string, original: string): void {\n btn.textContent = msg;\n setTimeout(() => {\n btn.textContent = original;\n }, 1200);\n}\n\nfunction copyToClipboard(text: string, btn: HTMLButtonElement, original: string): void {\n const fallback = () => {\n try {\n const ta = document.createElement(\"textarea\");\n ta.value = text;\n ta.style.position = \"fixed\";\n ta.style.left = \"-9999px\";\n document.body.appendChild(ta);\n ta.select();\n document.execCommand(\"copy\");\n document.body.removeChild(ta);\n flashButton(btn, \"copied\", original);\n } catch {\n flashButton(btn, \"fail\", original);\n }\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(text).then(\n () => flashButton(btn, \"copied\", original),\n fallback,\n );\n } else {\n fallback();\n }\n}\n\nfunction updateCount(): void {\n if (!panel) return;\n const countEl = panel.querySelector(\".pp-count\");\n if (countEl) countEl.textContent = `(${entries.length})`;\n}\n\nfunction attachDrag(panelEl: HTMLElement, header: HTMLElement): void {\n let dragging = false;\n let offX = 0;\n let offY = 0;\n header.addEventListener(\"mousedown\", (e) => {\n if ((e.target as HTMLElement).tagName === \"BUTTON\") return;\n dragging = true;\n const rect = panelEl.getBoundingClientRect();\n offX = e.clientX - rect.left;\n offY = e.clientY - rect.top;\n e.preventDefault();\n });\n window.addEventListener(\"mousemove\", (e) => {\n if (!dragging) return;\n panelEl.style.left = `${e.clientX - offX}px`;\n panelEl.style.top = `${e.clientY - offY}px`;\n });\n window.addEventListener(\"mouseup\", () => {\n dragging = false;\n });\n}\n\nfunction ensurePanel(): HTMLElement {\n if (panel && document.body.contains(panel)) return panel;\n ensureStyles();\n\n panel = document.createElement(\"div\");\n panel.id = PANEL_ID;\n\n const header = document.createElement(\"div\");\n header.className = \"pp-header\";\n\n const title = document.createElement(\"span\");\n title.className = \"pp-title\";\n title.textContent = \"[provider-protocols] debug\";\n const count = document.createElement(\"span\");\n count.className = \"pp-count\";\n count.textContent = `(${entries.length})`;\n title.appendChild(count);\n header.appendChild(title);\n\n const copyAllBtn = makeButton(\"copy all\", (e) => {\n e.stopPropagation();\n const blob = entries\n .map((en) => `// ${new Date(en.ts).toISOString()} ${en.label}\\n${en.json}`)\n .join(\"\\n\\n\");\n copyToClipboard(blob, copyAllBtn, \"copy all\");\n });\n header.appendChild(copyAllBtn);\n\n const clearBtn = makeButton(\"clear\", (e) => {\n e.stopPropagation();\n entries = [];\n if (listEl) listEl.innerHTML = \"\";\n updateCount();\n });\n header.appendChild(clearBtn);\n\n const collapseBtn = makeButton(\"−\", (e) => {\n e.stopPropagation();\n panel!.classList.toggle(\"pp-collapsed\");\n collapseBtn.textContent = panel!.classList.contains(\"pp-collapsed\") ? \"+\" : \"−\";\n });\n header.appendChild(collapseBtn);\n\n attachDrag(panel, header);\n panel.appendChild(header);\n\n listEl = document.createElement(\"div\");\n listEl.className = \"pp-list\";\n panel.appendChild(listEl);\n\n document.body.appendChild(panel);\n return panel;\n}\n\nfunction renderEntry(entry: Entry): void {\n if (!listEl) return;\n const det = document.createElement(\"details\");\n det.open = false;\n\n const sum = document.createElement(\"summary\");\n const lbl = document.createElement(\"span\");\n lbl.className = \"pp-label\";\n lbl.textContent = entry.label;\n const ts = document.createElement(\"span\");\n ts.className = \"pp-ts\";\n ts.textContent = new Date(entry.ts).toISOString().slice(11, 23);\n const copyBtn = makeButton(\"copy\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n copyToClipboard(entry.json, copyBtn, \"copy\");\n });\n sum.appendChild(lbl);\n sum.appendChild(ts);\n sum.appendChild(copyBtn);\n det.appendChild(sum);\n\n const pre = document.createElement(\"pre\");\n pre.textContent = entry.json;\n det.appendChild(pre);\n\n listEl.appendChild(det);\n listEl.scrollTop = listEl.scrollHeight;\n\n while (listEl.children.length > RING_SIZE) {\n const first = listEl.firstChild;\n if (first) listEl.removeChild(first);\n else break;\n }\n}\n\n/**\n * Append a structured debug entry to the in-DOM overlay. No-op unless the\n * runtime gate is on (`window.__PP_DEBUG__ === true` or\n * `localStorage.PP_DEBUG === \"1\"`). Stringification is lazy: the payload is\n * not serialized when the gate is off, so this is safe to leave in hot paths.\n */\nexport function debugLog(label: string, payload: unknown): void {\n if (!isEnabled()) return;\n if (typeof document === \"undefined\") return;\n\n const entry: Entry = {\n ts: Date.now(),\n label,\n json: safeStringify(payload),\n };\n\n entries.push(entry);\n if (entries.length > RING_SIZE) entries.shift();\n\n const append = () => {\n try {\n ensurePanel();\n renderEntry(entry);\n updateCount();\n } catch {\n // never break the host on debug failure\n }\n };\n\n if (document.body) {\n append();\n } else {\n document.addEventListener(\"DOMContentLoaded\", append, { once: true });\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.17",
3
+ "version": "3.0.0-beta.19",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -41,10 +41,12 @@ export function initFormDataFromSchema(
41
41
  }
42
42
  }
43
43
 
44
+ debugLog("[initFormData] initFormDataFromSchema", { schema, seed, output: base });
44
45
  return base;
45
46
  }
46
47
 
47
48
  import { resolveRef } from "./refs";
49
+ import { debugLog } from "../debug/overlay";
48
50
 
49
51
  /**
50
52
  * Initialize a single property value based on its schema definition.
@@ -1,4 +1,8 @@
1
- import { deref as derefSchema } from "./refs";
1
+ import {
2
+ deref as derefSchema,
3
+ tryCombinatorBranches,
4
+ } from "./refs";
5
+ import { debugLog } from "../debug/overlay";
2
6
 
3
7
  /**
4
8
  * Projection utilities for navigating complex data structures
@@ -100,9 +104,9 @@ function setAtPath(
100
104
  * Numeric segments traverse into `items` (array item schema).
101
105
  * String segments traverse into `properties[segment]`.
102
106
  *
103
- * `$ref` nodes are dereferenced transparently at every step, so projections
104
- * that cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve
105
- * to the target schema rather than collapsing to `{}`.
107
+ * Dereferences `$ref` nodes transparently at every step, and falls through
108
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
109
+ * directly — picks the first branch that satisfies the navigation.
106
110
  */
107
111
  export function getProjectedSchema(
108
112
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -116,29 +120,40 @@ export function getProjectedSchema(
116
120
 
117
121
  for (const seg of segments) {
118
122
  current = derefSchema(current, schema);
119
- if (!current) return {};
123
+ if (!current) {
124
+ debugLog("[projection] empty (deref miss)", { path, segment: seg, rootSchema: schema });
125
+ return {};
126
+ }
120
127
 
121
- if (typeof seg === "number") {
122
- // Array index → traverse into items schema
123
- const items = current.items;
124
- if (items && typeof items === "object") {
125
- current = items as Record<string, unknown>;
126
- } else {
127
- return {};
128
+ const navigate = (
129
+ node: Record<string, unknown>,
130
+ ): Record<string, unknown> | undefined => {
131
+ if (typeof seg === "number") {
132
+ const items = (node as { items?: unknown }).items;
133
+ return items && typeof items === "object"
134
+ ? (items as Record<string, unknown>)
135
+ : undefined;
128
136
  }
129
- } else {
130
- // Object key → traverse into properties[key]
131
- const properties = current.properties as
137
+ const properties = (node as { properties?: unknown }).properties as
132
138
  | Record<string, Record<string, unknown>>
133
139
  | undefined;
134
- if (properties && properties[seg]) {
135
- current = properties[seg];
136
- } else {
137
- return {};
138
- }
140
+ if (properties && properties[seg]) return properties[seg];
141
+ return undefined;
142
+ };
143
+
144
+ let next = navigate(current);
145
+ if (next === undefined) {
146
+ next = tryCombinatorBranches(current, schema, navigate);
147
+ }
148
+ if (!next) {
149
+ debugLog("[projection] empty (segment unresolved)", { path, segment: seg, current, rootSchema: schema });
150
+ return {};
139
151
  }
152
+ current = next;
140
153
  }
141
154
 
142
155
  const resolved = derefSchema(current, schema);
143
- return resolved ?? {};
156
+ const out = resolved ?? {};
157
+ debugLog("[projection] getProjectedSchema", { path, output: out, rootSchema: schema });
158
+ return out;
144
159
  }
package/src/core/refs.ts CHANGED
@@ -74,6 +74,58 @@ export function deref(
74
74
  return resolveRef(node, root) as Record<string, unknown> | undefined;
75
75
  }
76
76
 
77
+ /**
78
+ * Depth limit for combinator (oneOf/anyOf/allOf) branch descent. Schemas
79
+ * rarely nest combinators beyond one or two levels; this guard protects
80
+ * against pathological nesting and cycles (e.g. `oneOf: [$ref back to self]`).
81
+ */
82
+ const COMBINATOR_DEPTH_LIMIT = 8;
83
+
84
+ /**
85
+ * Try to navigate a segment through a schema node's combinator branches
86
+ * (`oneOf` / `anyOf` / `allOf`) when direct navigation has failed.
87
+ *
88
+ * Semantics: for walker purposes (renderer-tester matching), we only need
89
+ * ONE concrete schema that satisfies the next navigation step. First-match
90
+ * by structural shape wins, same convention as `initOneOf` uses for seeding.
91
+ *
92
+ * @param node the schema node to search (already dereffed by caller)
93
+ * @param root the root schema, for dereferencing branch `$ref`s
94
+ * @param tryFn predicate that attempts navigation on a candidate branch
95
+ * and returns the navigated value, or `undefined` if the
96
+ * branch doesn't have what the caller's looking for
97
+ * @param depth recursion depth (capped at `COMBINATOR_DEPTH_LIMIT`)
98
+ */
99
+ export function tryCombinatorBranches<T>(
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ node: Record<string, any> | undefined,
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ root: Record<string, any>,
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ tryFn: (candidate: Record<string, any>) => T | undefined,
106
+ depth = 0,
107
+ ): T | undefined {
108
+ if (depth > COMBINATOR_DEPTH_LIMIT) return undefined;
109
+ if (!node || typeof node !== "object") return undefined;
110
+
111
+ const branches = (node.oneOf || node.anyOf || node.allOf) as
112
+ | Record<string, unknown>[]
113
+ | undefined;
114
+ if (!Array.isArray(branches)) return undefined;
115
+
116
+ for (const raw of branches) {
117
+ const branch = deref(raw as Record<string, unknown>, root);
118
+ if (!branch || typeof branch !== "object") continue;
119
+
120
+ const direct = tryFn(branch);
121
+ if (direct !== undefined) return direct;
122
+
123
+ const nested = tryCombinatorBranches(branch, root, tryFn, depth + 1);
124
+ if (nested !== undefined) return nested;
125
+ }
126
+ return undefined;
127
+ }
128
+
77
129
  /**
78
130
  * Recursively dereference every `$ref` in a schema subtree, producing a
79
131
  * concrete schema with no remaining refs. Cycles along any single chain are
@@ -1,4 +1,5 @@
1
- import { deepDeref, deref } from "./refs";
1
+ import { deepDeref, deref, tryCombinatorBranches } from "./refs";
2
+ import { debugLog } from "../debug/overlay";
2
3
 
3
4
  /**
4
5
  * Resolve a JSON Forms scope path to its schema within a root schema.
@@ -9,11 +10,11 @@ import { deepDeref, deref } from "./refs";
9
10
  * - "items" segments navigate into array `.items`
10
11
  * - all other segments index directly into the current object
11
12
  *
12
- * `$ref` nodes are dereferenced transparently at every step, so scopes that
13
- * cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve to
14
- * the target schema rather than returning `{}`. The returned schema is also
15
- * deep-dereferenced so downstream walkers (e.g. `getProjectedSchema`) can
16
- * operate on a self-contained sub-schema without needing the original root.
13
+ * Dereferences `$ref` nodes transparently at every step, and falls through
14
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
15
+ * directly picks the first branch that satisfies the navigation. The
16
+ * returned schema is deep-dereferenced so downstream walkers can operate on
17
+ * a self-contained sub-schema without needing the original root.
17
18
  */
18
19
  export function resolveScopeSchema(
19
20
  scope: string,
@@ -21,11 +22,18 @@ export function resolveScopeSchema(
21
22
  rootSchema: Record<string, any>,
22
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
24
  ): Record<string, any> | undefined {
24
- if (!scope || !rootSchema) return undefined;
25
+ if (!scope || !rootSchema) {
26
+ debugLog("[resolveScope] undefined (empty input)", { scope, rootSchema });
27
+ return undefined;
28
+ }
25
29
 
26
30
  // Remove the leading "#/" and split into segments
27
31
  const path = scope.replace(/^#\/?/, "");
28
- if (!path) return deepDeref(rootSchema, rootSchema);
32
+ if (!path) {
33
+ const out = deepDeref(rootSchema, rootSchema);
34
+ debugLog("[resolveScope] root deepDeref", { scope, output: out, rootSchema });
35
+ return out;
36
+ }
29
37
 
30
38
  const segments = path.split("/");
31
39
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -33,16 +41,30 @@ export function resolveScopeSchema(
33
41
 
34
42
  for (const segment of segments) {
35
43
  current = deref(current, rootSchema);
36
- if (!current || typeof current !== "object") return undefined;
44
+ if (!current || typeof current !== "object") {
45
+ debugLog("[resolveScope] undefined (deref miss)", { scope, segment, rootSchema });
46
+ return undefined;
47
+ }
37
48
 
38
- if (segment === "properties") {
39
- current = current.properties;
40
- } else if (segment === "items") {
41
- current = current.items;
42
- } else {
43
- current = current[segment];
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ const navigate = (node: Record<string, any>): unknown => {
51
+ if (segment === "properties") return node.properties;
52
+ if (segment === "items") return node.items;
53
+ return node[segment];
54
+ };
55
+
56
+ let next = navigate(current);
57
+ if (next === undefined) {
58
+ next = tryCombinatorBranches(current, rootSchema, navigate);
59
+ }
60
+ if (next === undefined) {
61
+ debugLog("[resolveScope] undefined (segment unresolved)", { scope, segment, current, rootSchema });
62
+ return undefined;
44
63
  }
64
+ current = next;
45
65
  }
46
66
 
47
- return deepDeref(current, rootSchema);
67
+ const out = deepDeref(current, rootSchema);
68
+ debugLog("[resolveScope] resolveScopeSchema", { scope, output: out, rootSchema });
69
+ return out;
48
70
  }