@narrative.io/jsonforms-provider-protocols 3.0.0-beta.18 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"initFormData.d.ts","sourceRoot":"","sources":["../../src/core/initFormData.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB"}
1
+ {"version":3,"file":"initFormData.d.ts","sourceRoot":"","sources":["../../src/core/initFormData.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2BzB"}
@@ -1,4 +1,5 @@
1
1
  import { resolveRef } from "./refs.js";
2
+ import { debugLog } from "../debug/overlay.js";
2
3
  function initFormDataFromSchema(schema, seed) {
3
4
  const result = initProperty(schema, schema, seed, true);
4
5
  const base = result && typeof result === "object" && !Array.isArray(result) ? result : {};
@@ -14,6 +15,7 @@ function initFormDataFromSchema(schema, seed) {
14
15
  }
15
16
  }
16
17
  }
18
+ debugLog("[initFormData] initFormDataFromSchema", { schema, seed, output: base });
17
19
  return base;
18
20
  }
19
21
  function initProperty(property, root, seed, required) {
@@ -1 +1 @@
1
- {"version":3,"file":"initFormData.js","sources":["../../src/core/initFormData.ts"],"sourcesContent":["/**\n * Initialize a form data object from a JSON Schema.\n * Resolves $ref, const, default, oneOf/discriminator, and typed empty values.\n *\n * Optional fields (not listed in the parent schema's `required` array) that\n * have no `const`, no single-value `enum`, and no `default` are omitted\n * entirely from the result. This avoids seeding values (e.g. `null` for\n * `type: \"integer\"`) that fail AJV's type check and surface spurious errors\n * on untouched fields. Required fields retain legacy typed-empty seeding\n * (`\"\"`, `null`, `false`, `[]`) so that \"is required\" surfaces cleanly.\n *\n * @param schema - The full JSON Schema (must include $defs if $refs are used)\n * @param seed - Optional existing data to merge (seed values take priority)\n * @returns A data object with schema-defined fields initialized\n */\nexport function initFormDataFromSchema(\n schema: Record<string, unknown>,\n seed?: Record<string, unknown>,\n): Record<string, unknown> {\n const result = initProperty(schema, schema, seed, true) as\n | Record<string, unknown>\n | undefined;\n\n const base =\n result && typeof result === \"object\" && !Array.isArray(result)\n ? result\n : {};\n\n // Preserve seed keys not described by the schema's properties.\n if (seed && typeof seed === \"object\") {\n const schemaKeys = new Set(\n Object.keys(\n (resolveRef(schema, schema) as Record<string, unknown>)?.properties ??\n {},\n ),\n );\n for (const key of Object.keys(seed)) {\n if (!schemaKeys.has(key) && !(key in base)) {\n base[key] = seed[key];\n }\n }\n }\n\n return base;\n}\n\nimport { resolveRef } from \"./refs\";\n\n/**\n * Initialize a single property value based on its schema definition.\n * Returns `undefined` when the property is optional and has nothing\n * concrete to seed — the caller then omits the key from its result.\n */\nfunction initProperty(\n property: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: unknown,\n required: boolean,\n): unknown {\n if (!property || typeof property !== \"object\") {\n return required ? null : undefined;\n }\n\n const resolved = resolveRef(property, root);\n const type = resolved.type as string | undefined;\n const isOneOf = Array.isArray(resolved.oneOf);\n\n // Priority 1: seed wins for non-object, non-oneOf types. For oneOf we still\n // descend so that a partial seed (e.g. `{value: 5}` missing the\n // discriminator) picks up the variant's const/default for untouched fields.\n if (seed !== undefined && seed !== null && type !== \"object\" && !isOneOf) {\n return seed;\n }\n\n // Priority 2: const (schema-invariant — always set).\n if (\"const\" in resolved) {\n return resolved.const;\n }\n\n // Priority 3: single-value enum (same reasoning — only one valid value).\n if (\n Array.isArray(resolved.enum) &&\n (resolved.enum as unknown[]).length === 1\n ) {\n return (resolved.enum as unknown[])[0];\n }\n\n // Priority 4: default (explicit author intent — always honored).\n if (\"default\" in resolved) {\n return resolved.default;\n }\n\n // Priority 5: oneOf. Recurse into the first variant, propagating the\n // `required` flag so that the variant's own schema-declared required fields\n // only get typed-empty seeding when the container is required. For optional\n // containers, the variant's const / default / single-value enum still seed\n // (author intent is unconditional) but typed-empty placeholders do not.\n // If recursion yields nothing forced, we collapse back to undefined.\n if (isOneOf) {\n const value = initOneOf(resolved, root, seed, required);\n if (\n !required &&\n (value === undefined ||\n (value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0))\n ) {\n return undefined;\n }\n return value;\n }\n\n // Priority 6: objects recurse. The `required` flag propagates downward:\n // inside an optional ancestor, nested required primitives also collapse,\n // so an optional object with nested required fields stays absent instead\n // of materializing a shell that AJV would flag.\n if (type === \"object\") {\n const obj = initObject(\n resolved,\n root,\n seed as Record<string, unknown>,\n required,\n );\n if (!required && Object.keys(obj).length === 0) return undefined;\n return obj;\n }\n\n // Priority 7: optional primitives — omit.\n if (!required) return undefined;\n\n // Priority 8: required primitives — legacy typed empty.\n switch (type) {\n case \"array\":\n return seed !== undefined && seed !== null ? seed : [];\n case \"string\":\n return \"\";\n case \"boolean\":\n return false;\n case \"number\":\n case \"integer\":\n default:\n return null;\n }\n}\n\n/**\n * Initialize an object type by recursing into its properties.\n * Keys whose initialization returns `undefined` are omitted.\n *\n * `parentRequired` controls how schema.required propagates: a child is\n * treated as required only if both its parent is required AND it appears in\n * the parent's required array. Inside an optional ancestor the whole\n * subtree collapses (except for author-forced values: const, default,\n * single-value enum, or seeded values).\n */\nfunction initObject(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: Record<string, unknown> | undefined,\n parentRequired: boolean,\n): Record<string, unknown> {\n const properties = schema.properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (!properties) {\n return seed && typeof seed === \"object\" ? { ...seed } : {};\n }\n\n const requiredSet = new Set<string>(\n Array.isArray(schema.required) ? (schema.required as string[]) : [],\n );\n\n const result: Record<string, unknown> = {};\n\n for (const [key, propSchema] of Object.entries(properties)) {\n const seedValue = seed && typeof seed === \"object\" ? seed[key] : undefined;\n const effectiveRequired = parentRequired && requiredSet.has(key);\n const value = initProperty(propSchema, root, seedValue, effectiveRequired);\n if (value !== undefined) {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Handle oneOf schemas — pick the first variant and initialize it.\n * `required` propagates: if the outer oneOf is required, the variant is\n * materialized with typed-empty seeding for its required fields; if optional,\n * only author-forced values (const / default / single-enum / seed) survive.\n */\nfunction initOneOf(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: unknown,\n required: boolean,\n): unknown {\n const variants = schema.oneOf as Record<string, unknown>[];\n if (!variants || variants.length === 0) return required ? null : undefined;\n\n const first = variants[0];\n if (!first) return required ? null : undefined;\n\n const firstVariant = resolveRef(first, root);\n return initProperty(firstVariant, root, seed, required);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"initFormData.js","sources":["../../src/core/initFormData.ts"],"sourcesContent":["/**\n * Initialize a form data object from a JSON Schema.\n * Resolves $ref, const, default, oneOf/discriminator, and typed empty values.\n *\n * Optional fields (not listed in the parent schema's `required` array) that\n * have no `const`, no single-value `enum`, and no `default` are omitted\n * entirely from the result. This avoids seeding values (e.g. `null` for\n * `type: \"integer\"`) that fail AJV's type check and surface spurious errors\n * on untouched fields. Required fields retain legacy typed-empty seeding\n * (`\"\"`, `null`, `false`, `[]`) so that \"is required\" surfaces cleanly.\n *\n * @param schema - The full JSON Schema (must include $defs if $refs are used)\n * @param seed - Optional existing data to merge (seed values take priority)\n * @returns A data object with schema-defined fields initialized\n */\nexport function initFormDataFromSchema(\n schema: Record<string, unknown>,\n seed?: Record<string, unknown>,\n): Record<string, unknown> {\n const result = initProperty(schema, schema, seed, true) as\n | Record<string, unknown>\n | undefined;\n\n const base =\n result && typeof result === \"object\" && !Array.isArray(result)\n ? result\n : {};\n\n // Preserve seed keys not described by the schema's properties.\n if (seed && typeof seed === \"object\") {\n const schemaKeys = new Set(\n Object.keys(\n (resolveRef(schema, schema) as Record<string, unknown>)?.properties ??\n {},\n ),\n );\n for (const key of Object.keys(seed)) {\n if (!schemaKeys.has(key) && !(key in base)) {\n base[key] = seed[key];\n }\n }\n }\n\n debugLog(\"[initFormData] initFormDataFromSchema\", { schema, seed, output: base });\n return base;\n}\n\nimport { resolveRef } from \"./refs\";\nimport { debugLog } from \"../debug/overlay\";\n\n/**\n * Initialize a single property value based on its schema definition.\n * Returns `undefined` when the property is optional and has nothing\n * concrete to seed — the caller then omits the key from its result.\n */\nfunction initProperty(\n property: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: unknown,\n required: boolean,\n): unknown {\n if (!property || typeof property !== \"object\") {\n return required ? null : undefined;\n }\n\n const resolved = resolveRef(property, root);\n const type = resolved.type as string | undefined;\n const isOneOf = Array.isArray(resolved.oneOf);\n\n // Priority 1: seed wins for non-object, non-oneOf types. For oneOf we still\n // descend so that a partial seed (e.g. `{value: 5}` missing the\n // discriminator) picks up the variant's const/default for untouched fields.\n if (seed !== undefined && seed !== null && type !== \"object\" && !isOneOf) {\n return seed;\n }\n\n // Priority 2: const (schema-invariant — always set).\n if (\"const\" in resolved) {\n return resolved.const;\n }\n\n // Priority 3: single-value enum (same reasoning — only one valid value).\n if (\n Array.isArray(resolved.enum) &&\n (resolved.enum as unknown[]).length === 1\n ) {\n return (resolved.enum as unknown[])[0];\n }\n\n // Priority 4: default (explicit author intent — always honored).\n if (\"default\" in resolved) {\n return resolved.default;\n }\n\n // Priority 5: oneOf. Recurse into the first variant, propagating the\n // `required` flag so that the variant's own schema-declared required fields\n // only get typed-empty seeding when the container is required. For optional\n // containers, the variant's const / default / single-value enum still seed\n // (author intent is unconditional) but typed-empty placeholders do not.\n // If recursion yields nothing forced, we collapse back to undefined.\n if (isOneOf) {\n const value = initOneOf(resolved, root, seed, required);\n if (\n !required &&\n (value === undefined ||\n (value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0))\n ) {\n return undefined;\n }\n return value;\n }\n\n // Priority 6: objects recurse. The `required` flag propagates downward:\n // inside an optional ancestor, nested required primitives also collapse,\n // so an optional object with nested required fields stays absent instead\n // of materializing a shell that AJV would flag.\n if (type === \"object\") {\n const obj = initObject(\n resolved,\n root,\n seed as Record<string, unknown>,\n required,\n );\n if (!required && Object.keys(obj).length === 0) return undefined;\n return obj;\n }\n\n // Priority 7: optional primitives — omit.\n if (!required) return undefined;\n\n // Priority 8: required primitives — legacy typed empty.\n switch (type) {\n case \"array\":\n return seed !== undefined && seed !== null ? seed : [];\n case \"string\":\n return \"\";\n case \"boolean\":\n return false;\n case \"number\":\n case \"integer\":\n default:\n return null;\n }\n}\n\n/**\n * Initialize an object type by recursing into its properties.\n * Keys whose initialization returns `undefined` are omitted.\n *\n * `parentRequired` controls how schema.required propagates: a child is\n * treated as required only if both its parent is required AND it appears in\n * the parent's required array. Inside an optional ancestor the whole\n * subtree collapses (except for author-forced values: const, default,\n * single-value enum, or seeded values).\n */\nfunction initObject(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: Record<string, unknown> | undefined,\n parentRequired: boolean,\n): Record<string, unknown> {\n const properties = schema.properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (!properties) {\n return seed && typeof seed === \"object\" ? { ...seed } : {};\n }\n\n const requiredSet = new Set<string>(\n Array.isArray(schema.required) ? (schema.required as string[]) : [],\n );\n\n const result: Record<string, unknown> = {};\n\n for (const [key, propSchema] of Object.entries(properties)) {\n const seedValue = seed && typeof seed === \"object\" ? seed[key] : undefined;\n const effectiveRequired = parentRequired && requiredSet.has(key);\n const value = initProperty(propSchema, root, seedValue, effectiveRequired);\n if (value !== undefined) {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Handle oneOf schemas — pick the first variant and initialize it.\n * `required` propagates: if the outer oneOf is required, the variant is\n * materialized with typed-empty seeding for its required fields; if optional,\n * only author-forced values (const / default / single-enum / seed) survive.\n */\nfunction initOneOf(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: unknown,\n required: boolean,\n): unknown {\n const variants = schema.oneOf as Record<string, unknown>[];\n if (!variants || variants.length === 0) return required ? null : undefined;\n\n const first = variants[0];\n if (!first) return required ? null : undefined;\n\n const firstVariant = resolveRef(first, root);\n return initProperty(firstVariant, root, seed, required);\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/core/projection.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAMrE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,OAAO,CAGT;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAEhC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GAEX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAmCrB"}
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/core/projection.ts"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAMrE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,OAAO,CAGT;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAEhC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GAEX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CA2CrB"}
@@ -1,4 +1,5 @@
1
1
  import { deref, tryCombinatorBranches } from "./refs.js";
2
+ import { debugLog } from "../debug/overlay.js";
2
3
  function parseProjectionPath(path) {
3
4
  if (!path) return [];
4
5
  return path.split(".").map((s) => {
@@ -48,7 +49,10 @@ function getProjectedSchema(schema, path) {
48
49
  let current = schema;
49
50
  for (const seg of segments) {
50
51
  current = deref(current, schema);
51
- if (!current) return {};
52
+ if (!current) {
53
+ debugLog("[projection] empty (deref miss)", { path, segment: seg, rootSchema: schema });
54
+ return {};
55
+ }
52
56
  const navigate = (node) => {
53
57
  if (typeof seg === "number") {
54
58
  const items = node.items;
@@ -62,11 +66,16 @@ function getProjectedSchema(schema, path) {
62
66
  if (next === void 0) {
63
67
  next = tryCombinatorBranches(current, schema, navigate);
64
68
  }
65
- if (!next) return {};
69
+ if (!next) {
70
+ debugLog("[projection] empty (segment unresolved)", { path, segment: seg, current, rootSchema: schema });
71
+ return {};
72
+ }
66
73
  current = next;
67
74
  }
68
75
  const resolved = deref(current, schema);
69
- return resolved ?? {};
76
+ const out = resolved ?? {};
77
+ debugLog("[projection] getProjectedSchema", { path, output: out, rootSchema: schema });
78
+ return out;
70
79
  }
71
80
  export {
72
81
  getProjectedSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"projection.js","sources":["../../src/core/projection.ts"],"sourcesContent":["import {\n deref as derefSchema,\n tryCombinatorBranches,\n} from \"./refs\";\n\n/**\n * Projection utilities for navigating complex data structures\n * through a dot-separated path where numeric segments are array indices.\n *\n * Examples:\n * \"0\" → first element of an array\n * \"include\" → the `include` property of an object\n * \"0.video_rate_usd\" → nested property inside the first array element\n */\n\nexport type ProjectionSegment = string | number;\n\n/**\n * Parse a projection path string into typed segments.\n * Numeric strings become numbers (array indices), others stay as strings (object keys).\n */\nexport function parseProjectionPath(path: string): ProjectionSegment[] {\n if (!path) return [];\n return path.split(\".\").map((s) => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 ? n : s;\n });\n}\n\n/**\n * Read a value from `data` by following the projection path.\n * Returns `undefined` if any segment along the path is missing.\n */\nexport function getProjectedValue(data: unknown, path: string): unknown {\n const segments = parseProjectionPath(path);\n let current: unknown = data;\n\n for (const seg of segments) {\n if (current === null || current === undefined) return undefined;\n\n if (typeof seg === \"number\") {\n if (!Array.isArray(current)) return undefined;\n current = current[seg];\n } else {\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n }\n\n return current;\n}\n\n/**\n * Immutably set a value at the projection path, preserving all sibling data.\n * Constructs missing intermediate structures (arrays for numeric segments, objects for string segments).\n */\nexport function setProjectedValue(\n data: unknown,\n path: string,\n value: unknown,\n): unknown {\n const segments = parseProjectionPath(path);\n return setAtPath(data, segments, 0, value);\n}\n\nfunction setAtPath(\n current: unknown,\n segments: ProjectionSegment[],\n index: number,\n value: unknown,\n): unknown {\n if (index === segments.length) {\n return value;\n }\n\n const seg = segments[index]!;\n\n if (typeof seg === \"number\") {\n // Array index — ensure we have an array\n const arr = Array.isArray(current) ? [...current] : [];\n // Pad array if index is out of bounds\n while (arr.length <= seg) {\n arr.push(undefined);\n }\n arr[seg] = setAtPath(arr[seg], segments, index + 1, value);\n return arr;\n } else {\n // Object key — ensure we have an object\n const obj: Record<string, unknown> =\n current !== null &&\n current !== undefined &&\n typeof current === \"object\" &&\n !Array.isArray(current)\n ? { ...(current as Record<string, unknown>) }\n : {};\n obj[seg] = setAtPath(obj[seg], segments, index + 1, value);\n return obj;\n }\n}\n\n/**\n * Resolve the schema at the projected path.\n * Numeric segments traverse into `items` (array item schema).\n * String segments traverse into `properties[segment]`.\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation.\n */\nexport function getProjectedSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: Record<string, any>,\n path: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> {\n const segments = parseProjectionPath(path);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: Record<string, any> | undefined = schema;\n\n for (const seg of segments) {\n current = derefSchema(current, schema);\n if (!current) return {};\n\n const navigate = (\n node: Record<string, unknown>,\n ): Record<string, unknown> | undefined => {\n if (typeof seg === \"number\") {\n const items = (node as { items?: unknown }).items;\n return items && typeof items === \"object\"\n ? (items as Record<string, unknown>)\n : undefined;\n }\n const properties = (node as { properties?: unknown }).properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (properties && properties[seg]) return properties[seg];\n return undefined;\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, schema, navigate);\n }\n if (!next) return {};\n current = next;\n }\n\n const resolved = derefSchema(current, schema);\n return resolved ?? {};\n}\n"],"names":["derefSchema"],"mappings":";AAqBO,SAAS,oBAAoB,MAAmC;AACrE,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,SAAO,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM;AAChC,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI;AAAA,EAC7C,CAAC;AACH;AAMO,SAAS,kBAAkB,MAAe,MAAuB;AACtE,QAAM,WAAW,oBAAoB,IAAI;AACzC,MAAI,UAAmB;AAEvB,aAAW,OAAO,UAAU;AAC1B,QAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AAEtD,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,gBAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AACL,UAAI,OAAO,YAAY,SAAU,QAAO;AACxC,gBAAW,QAAoC,GAAG;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,kBACd,MACA,MACA,OACS;AACT,QAAM,WAAW,oBAAoB,IAAI;AACzC,SAAO,UAAU,MAAM,UAAU,GAAG,KAAK;AAC3C;AAEA,SAAS,UACP,SACA,UACA,OACA,OACS;AACT,MAAI,UAAU,SAAS,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,SAAS,KAAK;AAE1B,MAAI,OAAO,QAAQ,UAAU;AAE3B,UAAM,MAAM,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAA;AAEpD,WAAO,IAAI,UAAU,KAAK;AACxB,UAAI,KAAK,MAAS;AAAA,IACpB;AACA,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT,OAAO;AAEL,UAAM,MACJ,YAAY,QACZ,YAAY,UACZ,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,OAAO,IAClB,EAAE,GAAI,QAAA,IACN,CAAA;AACN,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT;AACF;AAWO,SAAS,mBAEd,QACA,MAEqB;AACrB,QAAM,WAAW,oBAAoB,IAAI;AAEzC,MAAI,UAA2C;AAE/C,aAAW,OAAO,UAAU;AAC1B,cAAUA,MAAY,SAAS,MAAM;AACrC,QAAI,CAAC,QAAS,QAAO,CAAA;AAErB,UAAM,WAAW,CACf,SACwC;AACxC,UAAI,OAAO,QAAQ,UAAU;AAC3B,cAAM,QAAS,KAA6B;AAC5C,eAAO,SAAS,OAAO,UAAU,WAC5B,QACD;AAAA,MACN;AACA,YAAM,aAAc,KAAkC;AAGtD,UAAI,cAAc,WAAW,GAAG,EAAG,QAAO,WAAW,GAAG;AACxD,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,QAAQ,QAAQ;AAAA,IACxD;AACA,QAAI,CAAC,KAAM,QAAO,CAAA;AAClB,cAAU;AAAA,EACZ;AAEA,QAAM,WAAWA,MAAY,SAAS,MAAM;AAC5C,SAAO,YAAY,CAAA;AACrB;"}
1
+ {"version":3,"file":"projection.js","sources":["../../src/core/projection.ts"],"sourcesContent":["import {\n deref as derefSchema,\n tryCombinatorBranches,\n} from \"./refs\";\nimport { debugLog } from \"../debug/overlay\";\n\n/**\n * Projection utilities for navigating complex data structures\n * through a dot-separated path where numeric segments are array indices.\n *\n * Examples:\n * \"0\" → first element of an array\n * \"include\" → the `include` property of an object\n * \"0.video_rate_usd\" → nested property inside the first array element\n */\n\nexport type ProjectionSegment = string | number;\n\n/**\n * Parse a projection path string into typed segments.\n * Numeric strings become numbers (array indices), others stay as strings (object keys).\n */\nexport function parseProjectionPath(path: string): ProjectionSegment[] {\n if (!path) return [];\n return path.split(\".\").map((s) => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 ? n : s;\n });\n}\n\n/**\n * Read a value from `data` by following the projection path.\n * Returns `undefined` if any segment along the path is missing.\n */\nexport function getProjectedValue(data: unknown, path: string): unknown {\n const segments = parseProjectionPath(path);\n let current: unknown = data;\n\n for (const seg of segments) {\n if (current === null || current === undefined) return undefined;\n\n if (typeof seg === \"number\") {\n if (!Array.isArray(current)) return undefined;\n current = current[seg];\n } else {\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n }\n\n return current;\n}\n\n/**\n * Immutably set a value at the projection path, preserving all sibling data.\n * Constructs missing intermediate structures (arrays for numeric segments, objects for string segments).\n */\nexport function setProjectedValue(\n data: unknown,\n path: string,\n value: unknown,\n): unknown {\n const segments = parseProjectionPath(path);\n return setAtPath(data, segments, 0, value);\n}\n\nfunction setAtPath(\n current: unknown,\n segments: ProjectionSegment[],\n index: number,\n value: unknown,\n): unknown {\n if (index === segments.length) {\n return value;\n }\n\n const seg = segments[index]!;\n\n if (typeof seg === \"number\") {\n // Array index — ensure we have an array\n const arr = Array.isArray(current) ? [...current] : [];\n // Pad array if index is out of bounds\n while (arr.length <= seg) {\n arr.push(undefined);\n }\n arr[seg] = setAtPath(arr[seg], segments, index + 1, value);\n return arr;\n } else {\n // Object key — ensure we have an object\n const obj: Record<string, unknown> =\n current !== null &&\n current !== undefined &&\n typeof current === \"object\" &&\n !Array.isArray(current)\n ? { ...(current as Record<string, unknown>) }\n : {};\n obj[seg] = setAtPath(obj[seg], segments, index + 1, value);\n return obj;\n }\n}\n\n/**\n * Resolve the schema at the projected path.\n * Numeric segments traverse into `items` (array item schema).\n * String segments traverse into `properties[segment]`.\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation.\n */\nexport function getProjectedSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: Record<string, any>,\n path: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> {\n const segments = parseProjectionPath(path);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: Record<string, any> | undefined = schema;\n\n for (const seg of segments) {\n current = derefSchema(current, schema);\n if (!current) {\n debugLog(\"[projection] empty (deref miss)\", { path, segment: seg, rootSchema: schema });\n return {};\n }\n\n const navigate = (\n node: Record<string, unknown>,\n ): Record<string, unknown> | undefined => {\n if (typeof seg === \"number\") {\n const items = (node as { items?: unknown }).items;\n return items && typeof items === \"object\"\n ? (items as Record<string, unknown>)\n : undefined;\n }\n const properties = (node as { properties?: unknown }).properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (properties && properties[seg]) return properties[seg];\n return undefined;\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, schema, navigate);\n }\n if (!next) {\n debugLog(\"[projection] empty (segment unresolved)\", { path, segment: seg, current, rootSchema: schema });\n return {};\n }\n current = next;\n }\n\n const resolved = derefSchema(current, schema);\n const out = resolved ?? {};\n debugLog(\"[projection] getProjectedSchema\", { path, output: out, rootSchema: schema });\n return out;\n}\n"],"names":["derefSchema"],"mappings":";;AAsBO,SAAS,oBAAoB,MAAmC;AACrE,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,SAAO,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM;AAChC,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI;AAAA,EAC7C,CAAC;AACH;AAMO,SAAS,kBAAkB,MAAe,MAAuB;AACtE,QAAM,WAAW,oBAAoB,IAAI;AACzC,MAAI,UAAmB;AAEvB,aAAW,OAAO,UAAU;AAC1B,QAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AAEtD,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,gBAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AACL,UAAI,OAAO,YAAY,SAAU,QAAO;AACxC,gBAAW,QAAoC,GAAG;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,kBACd,MACA,MACA,OACS;AACT,QAAM,WAAW,oBAAoB,IAAI;AACzC,SAAO,UAAU,MAAM,UAAU,GAAG,KAAK;AAC3C;AAEA,SAAS,UACP,SACA,UACA,OACA,OACS;AACT,MAAI,UAAU,SAAS,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,SAAS,KAAK;AAE1B,MAAI,OAAO,QAAQ,UAAU;AAE3B,UAAM,MAAM,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAA;AAEpD,WAAO,IAAI,UAAU,KAAK;AACxB,UAAI,KAAK,MAAS;AAAA,IACpB;AACA,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT,OAAO;AAEL,UAAM,MACJ,YAAY,QACZ,YAAY,UACZ,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,OAAO,IAClB,EAAE,GAAI,QAAA,IACN,CAAA;AACN,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT;AACF;AAWO,SAAS,mBAEd,QACA,MAEqB;AACrB,QAAM,WAAW,oBAAoB,IAAI;AAEzC,MAAI,UAA2C;AAE/C,aAAW,OAAO,UAAU;AAC1B,cAAUA,MAAY,SAAS,MAAM;AACrC,QAAI,CAAC,SAAS;AACZ,eAAS,mCAAmC,EAAE,MAAM,SAAS,KAAK,YAAY,QAAQ;AACtF,aAAO,CAAA;AAAA,IACT;AAEA,UAAM,WAAW,CACf,SACwC;AACxC,UAAI,OAAO,QAAQ,UAAU;AAC3B,cAAM,QAAS,KAA6B;AAC5C,eAAO,SAAS,OAAO,UAAU,WAC5B,QACD;AAAA,MACN;AACA,YAAM,aAAc,KAAkC;AAGtD,UAAI,cAAc,WAAW,GAAG,EAAG,QAAO,WAAW,GAAG;AACxD,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,QAAQ,QAAQ;AAAA,IACxD;AACA,QAAI,CAAC,MAAM;AACT,eAAS,2CAA2C,EAAE,MAAM,SAAS,KAAK,SAAS,YAAY,QAAQ;AACvG,aAAO,CAAA;AAAA,IACT;AACA,cAAU;AAAA,EACZ;AAEA,QAAM,WAAWA,MAAY,SAAS,MAAM;AAC5C,QAAM,MAAM,YAAY,CAAA;AACxB,WAAS,mCAAmC,EAAE,MAAM,QAAQ,KAAK,YAAY,QAAQ;AACrF,SAAO;AACT;"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolveScope.d.ts","sourceRoot":"","sources":["../../src/core/resolveScope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EAEb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA+BjC"}
1
+ {"version":3,"file":"resolveScope.d.ts","sourceRoot":"","sources":["../../src/core/resolveScope.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EAEb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA8CjC"}
@@ -1,13 +1,24 @@
1
1
  import { deepDeref, deref, tryCombinatorBranches } from "./refs.js";
2
+ import { debugLog } from "../debug/overlay.js";
2
3
  function resolveScopeSchema(scope, rootSchema) {
3
- if (!scope || !rootSchema) return void 0;
4
+ if (!scope || !rootSchema) {
5
+ debugLog("[resolveScope] undefined (empty input)", { scope, rootSchema });
6
+ return void 0;
7
+ }
4
8
  const path = scope.replace(/^#\/?/, "");
5
- if (!path) return deepDeref(rootSchema, rootSchema);
9
+ if (!path) {
10
+ const out2 = deepDeref(rootSchema, rootSchema);
11
+ debugLog("[resolveScope] root deepDeref", { scope, output: out2, rootSchema });
12
+ return out2;
13
+ }
6
14
  const segments = path.split("/");
7
15
  let current = rootSchema;
8
16
  for (const segment of segments) {
9
17
  current = deref(current, rootSchema);
10
- if (!current || typeof current !== "object") return void 0;
18
+ if (!current || typeof current !== "object") {
19
+ debugLog("[resolveScope] undefined (deref miss)", { scope, segment, rootSchema });
20
+ return void 0;
21
+ }
11
22
  const navigate = (node) => {
12
23
  if (segment === "properties") return node.properties;
13
24
  if (segment === "items") return node.items;
@@ -17,10 +28,15 @@ function resolveScopeSchema(scope, rootSchema) {
17
28
  if (next === void 0) {
18
29
  next = tryCombinatorBranches(current, rootSchema, navigate);
19
30
  }
20
- if (next === void 0) return void 0;
31
+ if (next === void 0) {
32
+ debugLog("[resolveScope] undefined (segment unresolved)", { scope, segment, current, rootSchema });
33
+ return void 0;
34
+ }
21
35
  current = next;
22
36
  }
23
- return deepDeref(current, rootSchema);
37
+ const out = deepDeref(current, rootSchema);
38
+ debugLog("[resolveScope] resolveScopeSchema", { scope, output: out, rootSchema });
39
+ return out;
24
40
  }
25
41
  export {
26
42
  resolveScopeSchema
@@ -1 +1 @@
1
- {"version":3,"file":"resolveScope.js","sources":["../../src/core/resolveScope.ts"],"sourcesContent":["import { deepDeref, deref, tryCombinatorBranches } from \"./refs\";\n\n/**\n * Resolve a JSON Forms scope path to its schema within a root schema.\n * Handles nested paths like \"#/properties/parent/properties/child\".\n *\n * Follows JSON Schema structure:\n * - \"properties\" segments navigate into object `.properties`\n * - \"items\" segments navigate into array `.items`\n * - all other segments index directly into the current object\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation. The\n * returned schema is deep-dereferenced so downstream walkers can operate on\n * a self-contained sub-schema without needing the original root.\n */\nexport function resolveScopeSchema(\n scope: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!scope || !rootSchema) return undefined;\n\n // Remove the leading \"#/\" and split into segments\n const path = scope.replace(/^#\\/?/, \"\");\n if (!path) return deepDeref(rootSchema, rootSchema);\n\n const segments = path.split(\"/\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = rootSchema;\n\n for (const segment of segments) {\n current = deref(current, rootSchema);\n if (!current || typeof current !== \"object\") return undefined;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const navigate = (node: Record<string, any>): unknown => {\n if (segment === \"properties\") return node.properties;\n if (segment === \"items\") return node.items;\n return node[segment];\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, rootSchema, navigate);\n }\n if (next === undefined) return undefined;\n current = next;\n }\n\n return deepDeref(current, rootSchema);\n}\n"],"names":[],"mappings":";AAiBO,SAAS,mBACd,OAEA,YAEiC;AACjC,MAAI,CAAC,SAAS,CAAC,WAAY,QAAO;AAGlC,QAAM,OAAO,MAAM,QAAQ,SAAS,EAAE;AACtC,MAAI,CAAC,KAAM,QAAO,UAAU,YAAY,UAAU;AAElD,QAAM,WAAW,KAAK,MAAM,GAAG;AAE/B,MAAI,UAAe;AAEnB,aAAW,WAAW,UAAU;AAC9B,cAAU,MAAM,SAAS,UAAU;AACnC,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AAGpD,UAAM,WAAW,CAAC,SAAuC;AACvD,UAAI,YAAY,aAAc,QAAO,KAAK;AAC1C,UAAI,YAAY,QAAS,QAAO,KAAK;AACrC,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,YAAY,QAAQ;AAAA,IAC5D;AACA,QAAI,SAAS,OAAW,QAAO;AAC/B,cAAU;AAAA,EACZ;AAEA,SAAO,UAAU,SAAS,UAAU;AACtC;"}
1
+ {"version":3,"file":"resolveScope.js","sources":["../../src/core/resolveScope.ts"],"sourcesContent":["import { deepDeref, deref, tryCombinatorBranches } from \"./refs\";\nimport { debugLog } from \"../debug/overlay\";\n\n/**\n * Resolve a JSON Forms scope path to its schema within a root schema.\n * Handles nested paths like \"#/properties/parent/properties/child\".\n *\n * Follows JSON Schema structure:\n * - \"properties\" segments navigate into object `.properties`\n * - \"items\" segments navigate into array `.items`\n * - all other segments index directly into the current object\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation. The\n * returned schema is deep-dereferenced so downstream walkers can operate on\n * a self-contained sub-schema without needing the original root.\n */\nexport function resolveScopeSchema(\n scope: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!scope || !rootSchema) {\n debugLog(\"[resolveScope] undefined (empty input)\", { scope, rootSchema });\n return undefined;\n }\n\n // Remove the leading \"#/\" and split into segments\n const path = scope.replace(/^#\\/?/, \"\");\n if (!path) {\n const out = deepDeref(rootSchema, rootSchema);\n debugLog(\"[resolveScope] root deepDeref\", { scope, output: out, rootSchema });\n return out;\n }\n\n const segments = path.split(\"/\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = rootSchema;\n\n for (const segment of segments) {\n current = deref(current, rootSchema);\n if (!current || typeof current !== \"object\") {\n debugLog(\"[resolveScope] undefined (deref miss)\", { scope, segment, rootSchema });\n return undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const navigate = (node: Record<string, any>): unknown => {\n if (segment === \"properties\") return node.properties;\n if (segment === \"items\") return node.items;\n return node[segment];\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, rootSchema, navigate);\n }\n if (next === undefined) {\n debugLog(\"[resolveScope] undefined (segment unresolved)\", { scope, segment, current, rootSchema });\n return undefined;\n }\n current = next;\n }\n\n const out = deepDeref(current, rootSchema);\n debugLog(\"[resolveScope] resolveScopeSchema\", { scope, output: out, rootSchema });\n return out;\n}\n"],"names":["out"],"mappings":";;AAkBO,SAAS,mBACd,OAEA,YAEiC;AACjC,MAAI,CAAC,SAAS,CAAC,YAAY;AACzB,aAAS,0CAA0C,EAAE,OAAO,WAAA,CAAY;AACxE,WAAO;AAAA,EACT;AAGA,QAAM,OAAO,MAAM,QAAQ,SAAS,EAAE;AACtC,MAAI,CAAC,MAAM;AACT,UAAMA,OAAM,UAAU,YAAY,UAAU;AAC5C,aAAS,iCAAiC,EAAE,OAAO,QAAQA,MAAK,YAAY;AAC5E,WAAOA;AAAAA,EACT;AAEA,QAAM,WAAW,KAAK,MAAM,GAAG;AAE/B,MAAI,UAAe;AAEnB,aAAW,WAAW,UAAU;AAC9B,cAAU,MAAM,SAAS,UAAU;AACnC,QAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,eAAS,yCAAyC,EAAE,OAAO,SAAS,YAAY;AAChF,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,CAAC,SAAuC;AACvD,UAAI,YAAY,aAAc,QAAO,KAAK;AAC1C,UAAI,YAAY,QAAS,QAAO,KAAK;AACrC,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,YAAY,QAAQ;AAAA,IAC5D;AACA,QAAI,SAAS,QAAW;AACtB,eAAS,iDAAiD,EAAE,OAAO,SAAS,SAAS,YAAY;AACjG,aAAO;AAAA,IACT;AACA,cAAU;AAAA,EACZ;AAEA,QAAM,MAAM,UAAU,SAAS,UAAU;AACzC,WAAS,qCAAqC,EAAE,OAAO,QAAQ,KAAK,YAAY;AAChF,SAAO;AACT;"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * In-DOM debug overlay for `@narrative.io/jsonforms-provider-protocols`.
3
+ *
4
+ * Renders a fixed-position panel with a scrollable list of structured log
5
+ * entries. Used in place of `console.*` because some host apps strip console
6
+ * output in production. No-op unless explicitly enabled at runtime via
7
+ * either `window.__PP_DEBUG__ = true` or `localStorage.PP_DEBUG = "1"`.
8
+ *
9
+ * Internal API. Not part of the package's public surface.
10
+ */
11
+ /**
12
+ * Append a structured debug entry to the in-DOM overlay. No-op unless the
13
+ * runtime gate is on (`window.__PP_DEBUG__ === true` or
14
+ * `localStorage.PP_DEBUG === "1"`). Stringification is lazy: the payload is
15
+ * not serialized when the gate is off, so this is safe to leave in hot paths.
16
+ */
17
+ export declare function debugLog(label: string, payload: unknown): void;
18
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/debug/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0TH;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CA4B9D"}
@@ -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":"AAWA,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,WAAW;AAQjB,IAAI,UAAmB,CAAA;AACvB,IAAI,QAA4B;AAChC,IAAI,SAA6B;AAEjC,SAAS,YAAqB;AAC5B,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,IAAI;AACV,MAAI,EAAE,iBAAiB,KAAM,QAAO;AACpC,MAAI;AACF,WAAO,OAAO,cAAc,QAAQ,UAAU,MAAM;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,OAAwB;AAC7C,QAAM,2BAAW,QAAA;AACjB,MAAI;AACF,UAAM,MAAM,KAAK;AAAA,MACf;AAAA,MACA,CAAC,MAAM,MAAM;AACX,YAAI,OAAO,MAAM,YAAY,MAAM,MAAM;AACvC,cAAI,KAAK,IAAI,CAAW,EAAG,QAAO;AAClC,eAAK,IAAI,CAAW;AAAA,QACtB;AACA,YAAI,OAAO,MAAM;AACf,iBAAO,aAAc,EAAwB,QAAQ,WAAW;AAClE,YAAI,OAAO,MAAM,YAAa,QAAO;AACrC,eAAO;AAAA,MACT;AAAA,MACA;AAAA,IAAA;AAEF,WAAO,OAAO,OAAO,KAAK;AAAA,EAC5B,SAAS,GAAG;AACV,WAAO,qBAAsB,EAAY,OAAO;AAAA,EAClD;AACF;AAEA,SAAS,eAAqB;AAC5B,MAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,KAAK;AACX,QAAM,cAAc;AAAA,GACnB,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAkBR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAWR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,GAKR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,GAKR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAUR,QAAQ;AAAA,GACR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,GAKR,QAAQ;AAAA,GACR,QAAQ;AAAA,GACR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAMR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAQR,QAAQ;AAAA,GACR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,GAKR,QAAQ;AAAA,GACR,QAAQ;AAAA;AAAA;AAAA;AAAA,GAIR,QAAQ;AAAA;AAAA;AAAA,GAGR,QAAQ;AAAA,GACR,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWT,WAAS,KAAK,YAAY,KAAK;AACjC;AAEA,SAAS,WAAW,OAAe,SAAqD;AACtF,QAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,MAAI,OAAO;AACX,MAAI,cAAc;AAClB,MAAI,iBAAiB,SAAS,OAAO;AACrC,SAAO;AACT;AAEA,SAAS,YAAY,KAAwB,KAAa,UAAwB;AAChF,MAAI,cAAc;AAClB,aAAW,MAAM;AACf,QAAI,cAAc;AAAA,EACpB,GAAG,IAAI;AACT;AAEA,SAAS,gBAAgB,MAAc,KAAwB,UAAwB;AACrF,QAAM,WAAW,MAAM;AACrB,QAAI;AACF,YAAM,KAAK,SAAS,cAAc,UAAU;AAC5C,SAAG,QAAQ;AACX,SAAG,MAAM,WAAW;AACpB,SAAG,MAAM,OAAO;AAChB,eAAS,KAAK,YAAY,EAAE;AAC5B,SAAG,OAAA;AACH,eAAS,YAAY,MAAM;AAC3B,eAAS,KAAK,YAAY,EAAE;AAC5B,kBAAY,KAAK,UAAU,QAAQ;AAAA,IACrC,QAAQ;AACN,kBAAY,KAAK,QAAQ,QAAQ;AAAA,IACnC;AAAA,EACF;AACA,MAAI,UAAU,WAAW,WAAW;AAClC,cAAU,UAAU,UAAU,IAAI,EAAE;AAAA,MAClC,MAAM,YAAY,KAAK,UAAU,QAAQ;AAAA,MACzC;AAAA,IAAA;AAAA,EAEJ,OAAO;AACL,aAAA;AAAA,EACF;AACF;AAEA,SAAS,cAAoB;AAC3B,MAAI,CAAC,MAAO;AACZ,QAAM,UAAU,MAAM,cAAc,WAAW;AAC/C,MAAI,QAAS,SAAQ,cAAc,IAAI,QAAQ,MAAM;AACvD;AAEA,SAAS,WAAW,SAAsB,QAA2B;AACnE,MAAI,WAAW;AACf,MAAI,OAAO;AACX,MAAI,OAAO;AACX,SAAO,iBAAiB,aAAa,CAAC,MAAM;AAC1C,QAAK,EAAE,OAAuB,YAAY,SAAU;AACpD,eAAW;AACX,UAAM,OAAO,QAAQ,sBAAA;AACrB,WAAO,EAAE,UAAU,KAAK;AACxB,WAAO,EAAE,UAAU,KAAK;AACxB,MAAE,eAAA;AAAA,EACJ,CAAC;AACD,SAAO,iBAAiB,aAAa,CAAC,MAAM;AAC1C,QAAI,CAAC,SAAU;AACf,YAAQ,MAAM,OAAO,GAAG,EAAE,UAAU,IAAI;AACxC,YAAQ,MAAM,MAAM,GAAG,EAAE,UAAU,IAAI;AAAA,EACzC,CAAC;AACD,SAAO,iBAAiB,WAAW,MAAM;AACvC,eAAW;AAAA,EACb,CAAC;AACH;AAEA,SAAS,cAA2B;AAClC,MAAI,SAAS,SAAS,KAAK,SAAS,KAAK,EAAG,QAAO;AACnD,eAAA;AAEA,UAAQ,SAAS,cAAc,KAAK;AACpC,QAAM,KAAK;AAEX,QAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,SAAO,YAAY;AAEnB,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,YAAY;AAClB,QAAM,cAAc;AACpB,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,YAAY;AAClB,QAAM,cAAc,IAAI,QAAQ,MAAM;AACtC,QAAM,YAAY,KAAK;AACvB,SAAO,YAAY,KAAK;AAExB,QAAM,aAAa,WAAW,YAAY,CAAC,MAAM;AAC/C,MAAE,gBAAA;AACF,UAAM,OAAO,QACV,IAAI,CAAC,OAAO,MAAM,IAAI,KAAK,GAAG,EAAE,EAAE,YAAA,CAAa,IAAI,GAAG,KAAK;AAAA,EAAK,GAAG,IAAI,EAAE,EACzE,KAAK,MAAM;AACd,oBAAgB,MAAM,YAAY,UAAU;AAAA,EAC9C,CAAC;AACD,SAAO,YAAY,UAAU;AAE7B,QAAM,WAAW,WAAW,SAAS,CAAC,MAAM;AAC1C,MAAE,gBAAA;AACF,cAAU,CAAA;AACV,QAAI,eAAe,YAAY;AAC/B,gBAAA;AAAA,EACF,CAAC;AACD,SAAO,YAAY,QAAQ;AAE3B,QAAM,cAAc,WAAW,KAAK,CAAC,MAAM;AACzC,MAAE,gBAAA;AACF,UAAO,UAAU,OAAO,cAAc;AACtC,gBAAY,cAAc,MAAO,UAAU,SAAS,cAAc,IAAI,MAAM;AAAA,EAC9E,CAAC;AACD,SAAO,YAAY,WAAW;AAE9B,aAAW,OAAO,MAAM;AACxB,QAAM,YAAY,MAAM;AAExB,WAAS,SAAS,cAAc,KAAK;AACrC,SAAO,YAAY;AACnB,QAAM,YAAY,MAAM;AAExB,WAAS,KAAK,YAAY,KAAK;AAC/B,SAAO;AACT;AAEA,SAAS,YAAY,OAAoB;AACvC,MAAI,CAAC,OAAQ;AACb,QAAM,MAAM,SAAS,cAAc,SAAS;AAC5C,MAAI,OAAO;AAEX,QAAM,MAAM,SAAS,cAAc,SAAS;AAC5C,QAAM,MAAM,SAAS,cAAc,MAAM;AACzC,MAAI,YAAY;AAChB,MAAI,cAAc,MAAM;AACxB,QAAM,KAAK,SAAS,cAAc,MAAM;AACxC,KAAG,YAAY;AACf,KAAG,cAAc,IAAI,KAAK,MAAM,EAAE,EAAE,cAAc,MAAM,IAAI,EAAE;AAC9D,QAAM,UAAU,WAAW,QAAQ,CAAC,MAAM;AACxC,MAAE,eAAA;AACF,MAAE,gBAAA;AACF,oBAAgB,MAAM,MAAM,SAAS,MAAM;AAAA,EAC7C,CAAC;AACD,MAAI,YAAY,GAAG;AACnB,MAAI,YAAY,EAAE;AAClB,MAAI,YAAY,OAAO;AACvB,MAAI,YAAY,GAAG;AAEnB,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,cAAc,MAAM;AACxB,MAAI,YAAY,GAAG;AAEnB,SAAO,YAAY,GAAG;AACtB,SAAO,YAAY,OAAO;AAE1B,SAAO,OAAO,SAAS,SAAS,WAAW;AACzC,UAAM,QAAQ,OAAO;AACrB,QAAI,MAAO,QAAO,YAAY,KAAK;AAAA,QAC9B;AAAA,EACP;AACF;AAQO,SAAS,SAAS,OAAe,SAAwB;AAC9D,MAAI,CAAC,YAAa;AAClB,MAAI,OAAO,aAAa,YAAa;AAErC,QAAM,QAAe;AAAA,IACnB,IAAI,KAAK,IAAA;AAAA,IACT;AAAA,IACA,MAAM,cAAc,OAAO;AAAA,EAAA;AAG7B,UAAQ,KAAK,KAAK;AAClB,MAAI,QAAQ,SAAS,UAAW,SAAQ,MAAA;AAExC,QAAM,SAAS,MAAM;AACnB,QAAI;AACF,kBAAA;AACA,kBAAY,KAAK;AACjB,kBAAA;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,SAAS,MAAM;AACjB,WAAA;AAAA,EACF,OAAO;AACL,aAAS,iBAAiB,oBAAoB,QAAQ,EAAE,MAAM,MAAM;AAAA,EACtE;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.18",
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.
@@ -2,6 +2,7 @@ import {
2
2
  deref as derefSchema,
3
3
  tryCombinatorBranches,
4
4
  } from "./refs";
5
+ import { debugLog } from "../debug/overlay";
5
6
 
6
7
  /**
7
8
  * Projection utilities for navigating complex data structures
@@ -119,7 +120,10 @@ export function getProjectedSchema(
119
120
 
120
121
  for (const seg of segments) {
121
122
  current = derefSchema(current, schema);
122
- if (!current) return {};
123
+ if (!current) {
124
+ debugLog("[projection] empty (deref miss)", { path, segment: seg, rootSchema: schema });
125
+ return {};
126
+ }
123
127
 
124
128
  const navigate = (
125
129
  node: Record<string, unknown>,
@@ -141,10 +145,15 @@ export function getProjectedSchema(
141
145
  if (next === undefined) {
142
146
  next = tryCombinatorBranches(current, schema, navigate);
143
147
  }
144
- if (!next) return {};
148
+ if (!next) {
149
+ debugLog("[projection] empty (segment unresolved)", { path, segment: seg, current, rootSchema: schema });
150
+ return {};
151
+ }
145
152
  current = next;
146
153
  }
147
154
 
148
155
  const resolved = derefSchema(current, schema);
149
- return resolved ?? {};
156
+ const out = resolved ?? {};
157
+ debugLog("[projection] getProjectedSchema", { path, output: out, rootSchema: schema });
158
+ return out;
150
159
  }
@@ -1,4 +1,5 @@
1
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.
@@ -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,7 +41,10 @@ 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
49
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
50
  const navigate = (node: Record<string, any>): unknown => {
@@ -46,9 +57,14 @@ export function resolveScopeSchema(
46
57
  if (next === undefined) {
47
58
  next = tryCombinatorBranches(current, rootSchema, navigate);
48
59
  }
49
- if (next === undefined) return undefined;
60
+ if (next === undefined) {
61
+ debugLog("[resolveScope] undefined (segment unresolved)", { scope, segment, current, rootSchema });
62
+ return undefined;
63
+ }
50
64
  current = next;
51
65
  }
52
66
 
53
- return deepDeref(current, rootSchema);
67
+ const out = deepDeref(current, rootSchema);
68
+ debugLog("[resolveScope] resolveScopeSchema", { scope, output: out, rootSchema });
69
+ return out;
54
70
  }
@@ -0,0 +1,358 @@
1
+ /**
2
+ * In-DOM debug overlay for `@narrative.io/jsonforms-provider-protocols`.
3
+ *
4
+ * Renders a fixed-position panel with a scrollable list of structured log
5
+ * entries. Used in place of `console.*` because some host apps strip console
6
+ * output in production. No-op unless explicitly enabled at runtime via
7
+ * either `window.__PP_DEBUG__ = true` or `localStorage.PP_DEBUG = "1"`.
8
+ *
9
+ * Internal API. Not part of the package's public surface.
10
+ */
11
+
12
+ const RING_SIZE = 250;
13
+ const PANEL_ID = "__pp_debug_panel__";
14
+ const STYLE_ID = "__pp_debug_styles__";
15
+
16
+ interface Entry {
17
+ ts: number;
18
+ label: string;
19
+ json: string;
20
+ }
21
+
22
+ let entries: Entry[] = [];
23
+ let panel: HTMLElement | null = null;
24
+ let listEl: HTMLElement | null = null;
25
+
26
+ function isEnabled(): boolean {
27
+ if (typeof window === "undefined") return false;
28
+ const w = window as unknown as { __PP_DEBUG__?: boolean };
29
+ if (w.__PP_DEBUG__ === true) return true;
30
+ try {
31
+ return window.localStorage?.getItem("PP_DEBUG") === "1";
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ function safeStringify(value: unknown): string {
38
+ const seen = new WeakSet<object>();
39
+ try {
40
+ const out = JSON.stringify(
41
+ value,
42
+ (_key, v) => {
43
+ if (typeof v === "object" && v !== null) {
44
+ if (seen.has(v as object)) return "[Circular]";
45
+ seen.add(v as object);
46
+ }
47
+ if (typeof v === "function")
48
+ return `[Function ${(v as { name?: string }).name || "anonymous"}]`;
49
+ if (typeof v === "undefined") return "[undefined]";
50
+ return v;
51
+ },
52
+ 2,
53
+ );
54
+ return out ?? String(value);
55
+ } catch (e) {
56
+ return `[unstringifiable: ${(e as Error).message}]`;
57
+ }
58
+ }
59
+
60
+ function ensureStyles(): void {
61
+ if (document.getElementById(STYLE_ID)) return;
62
+ const style = document.createElement("style");
63
+ style.id = STYLE_ID;
64
+ style.textContent = `
65
+ #${PANEL_ID} {
66
+ position: fixed;
67
+ top: 12px;
68
+ left: 12px;
69
+ width: 460px;
70
+ max-height: 70vh;
71
+ z-index: 2147483647;
72
+ background: #0b0f17;
73
+ color: #d6e1ff;
74
+ border: 1px solid #2a3656;
75
+ border-radius: 6px;
76
+ font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
77
+ box-shadow: 0 8px 32px rgba(0,0,0,0.45);
78
+ display: flex;
79
+ flex-direction: column;
80
+ resize: both;
81
+ overflow: hidden;
82
+ }
83
+ #${PANEL_ID} .pp-header {
84
+ display: flex;
85
+ gap: 6px;
86
+ align-items: center;
87
+ padding: 6px 8px;
88
+ background: #131a2c;
89
+ border-bottom: 1px solid #2a3656;
90
+ cursor: move;
91
+ user-select: none;
92
+ flex-shrink: 0;
93
+ }
94
+ #${PANEL_ID} .pp-title {
95
+ flex: 1;
96
+ font-weight: 600;
97
+ color: #9fb4ff;
98
+ }
99
+ #${PANEL_ID} .pp-count {
100
+ color: #6f7fa6;
101
+ font-weight: 400;
102
+ margin-left: 4px;
103
+ }
104
+ #${PANEL_ID} button {
105
+ background: #1f2a47;
106
+ color: #d6e1ff;
107
+ border: 1px solid #2a3656;
108
+ border-radius: 3px;
109
+ padding: 2px 6px;
110
+ cursor: pointer;
111
+ font: inherit;
112
+ line-height: 1.2;
113
+ }
114
+ #${PANEL_ID} button:hover { background: #2a3656; }
115
+ #${PANEL_ID} .pp-list {
116
+ flex: 1;
117
+ overflow: auto;
118
+ padding: 4px 6px;
119
+ }
120
+ #${PANEL_ID}.pp-collapsed { resize: none; max-height: none; }
121
+ #${PANEL_ID}.pp-collapsed .pp-list { display: none; }
122
+ #${PANEL_ID} details {
123
+ margin: 4px 0;
124
+ border: 1px solid #2a3656;
125
+ border-radius: 3px;
126
+ background: #0f1422;
127
+ }
128
+ #${PANEL_ID} summary {
129
+ padding: 4px 6px;
130
+ cursor: pointer;
131
+ display: flex;
132
+ gap: 6px;
133
+ align-items: center;
134
+ list-style: none;
135
+ }
136
+ #${PANEL_ID} summary::-webkit-details-marker { display: none; }
137
+ #${PANEL_ID} summary::before {
138
+ content: "▸";
139
+ color: #6f7fa6;
140
+ width: 10px;
141
+ }
142
+ #${PANEL_ID} details[open] > summary::before { content: "▾"; }
143
+ #${PANEL_ID} .pp-label {
144
+ color: #ffd479;
145
+ font-weight: 600;
146
+ }
147
+ #${PANEL_ID} .pp-ts {
148
+ color: #6f7fa6;
149
+ }
150
+ #${PANEL_ID} summary button { margin-left: auto; }
151
+ #${PANEL_ID} pre {
152
+ margin: 0;
153
+ padding: 6px 8px;
154
+ max-height: 320px;
155
+ overflow: auto;
156
+ background: #050811;
157
+ color: #c9d6ff;
158
+ white-space: pre-wrap;
159
+ word-break: break-word;
160
+ }
161
+ `;
162
+ document.head.appendChild(style);
163
+ }
164
+
165
+ function makeButton(label: string, onClick: (e: MouseEvent) => void): HTMLButtonElement {
166
+ const btn = document.createElement("button");
167
+ btn.type = "button";
168
+ btn.textContent = label;
169
+ btn.addEventListener("click", onClick);
170
+ return btn;
171
+ }
172
+
173
+ function flashButton(btn: HTMLButtonElement, msg: string, original: string): void {
174
+ btn.textContent = msg;
175
+ setTimeout(() => {
176
+ btn.textContent = original;
177
+ }, 1200);
178
+ }
179
+
180
+ function copyToClipboard(text: string, btn: HTMLButtonElement, original: string): void {
181
+ const fallback = () => {
182
+ try {
183
+ const ta = document.createElement("textarea");
184
+ ta.value = text;
185
+ ta.style.position = "fixed";
186
+ ta.style.left = "-9999px";
187
+ document.body.appendChild(ta);
188
+ ta.select();
189
+ document.execCommand("copy");
190
+ document.body.removeChild(ta);
191
+ flashButton(btn, "copied", original);
192
+ } catch {
193
+ flashButton(btn, "fail", original);
194
+ }
195
+ };
196
+ if (navigator.clipboard?.writeText) {
197
+ navigator.clipboard.writeText(text).then(
198
+ () => flashButton(btn, "copied", original),
199
+ fallback,
200
+ );
201
+ } else {
202
+ fallback();
203
+ }
204
+ }
205
+
206
+ function updateCount(): void {
207
+ if (!panel) return;
208
+ const countEl = panel.querySelector(".pp-count");
209
+ if (countEl) countEl.textContent = `(${entries.length})`;
210
+ }
211
+
212
+ function attachDrag(panelEl: HTMLElement, header: HTMLElement): void {
213
+ let dragging = false;
214
+ let offX = 0;
215
+ let offY = 0;
216
+ header.addEventListener("mousedown", (e) => {
217
+ if ((e.target as HTMLElement).tagName === "BUTTON") return;
218
+ dragging = true;
219
+ const rect = panelEl.getBoundingClientRect();
220
+ offX = e.clientX - rect.left;
221
+ offY = e.clientY - rect.top;
222
+ e.preventDefault();
223
+ });
224
+ window.addEventListener("mousemove", (e) => {
225
+ if (!dragging) return;
226
+ panelEl.style.left = `${e.clientX - offX}px`;
227
+ panelEl.style.top = `${e.clientY - offY}px`;
228
+ });
229
+ window.addEventListener("mouseup", () => {
230
+ dragging = false;
231
+ });
232
+ }
233
+
234
+ function ensurePanel(): HTMLElement {
235
+ if (panel && document.body.contains(panel)) return panel;
236
+ ensureStyles();
237
+
238
+ panel = document.createElement("div");
239
+ panel.id = PANEL_ID;
240
+
241
+ const header = document.createElement("div");
242
+ header.className = "pp-header";
243
+
244
+ const title = document.createElement("span");
245
+ title.className = "pp-title";
246
+ title.textContent = "[provider-protocols] debug";
247
+ const count = document.createElement("span");
248
+ count.className = "pp-count";
249
+ count.textContent = `(${entries.length})`;
250
+ title.appendChild(count);
251
+ header.appendChild(title);
252
+
253
+ const copyAllBtn = makeButton("copy all", (e) => {
254
+ e.stopPropagation();
255
+ const blob = entries
256
+ .map((en) => `// ${new Date(en.ts).toISOString()} ${en.label}\n${en.json}`)
257
+ .join("\n\n");
258
+ copyToClipboard(blob, copyAllBtn, "copy all");
259
+ });
260
+ header.appendChild(copyAllBtn);
261
+
262
+ const clearBtn = makeButton("clear", (e) => {
263
+ e.stopPropagation();
264
+ entries = [];
265
+ if (listEl) listEl.innerHTML = "";
266
+ updateCount();
267
+ });
268
+ header.appendChild(clearBtn);
269
+
270
+ const collapseBtn = makeButton("−", (e) => {
271
+ e.stopPropagation();
272
+ panel!.classList.toggle("pp-collapsed");
273
+ collapseBtn.textContent = panel!.classList.contains("pp-collapsed") ? "+" : "−";
274
+ });
275
+ header.appendChild(collapseBtn);
276
+
277
+ attachDrag(panel, header);
278
+ panel.appendChild(header);
279
+
280
+ listEl = document.createElement("div");
281
+ listEl.className = "pp-list";
282
+ panel.appendChild(listEl);
283
+
284
+ document.body.appendChild(panel);
285
+ return panel;
286
+ }
287
+
288
+ function renderEntry(entry: Entry): void {
289
+ if (!listEl) return;
290
+ const det = document.createElement("details");
291
+ det.open = false;
292
+
293
+ const sum = document.createElement("summary");
294
+ const lbl = document.createElement("span");
295
+ lbl.className = "pp-label";
296
+ lbl.textContent = entry.label;
297
+ const ts = document.createElement("span");
298
+ ts.className = "pp-ts";
299
+ ts.textContent = new Date(entry.ts).toISOString().slice(11, 23);
300
+ const copyBtn = makeButton("copy", (e) => {
301
+ e.preventDefault();
302
+ e.stopPropagation();
303
+ copyToClipboard(entry.json, copyBtn, "copy");
304
+ });
305
+ sum.appendChild(lbl);
306
+ sum.appendChild(ts);
307
+ sum.appendChild(copyBtn);
308
+ det.appendChild(sum);
309
+
310
+ const pre = document.createElement("pre");
311
+ pre.textContent = entry.json;
312
+ det.appendChild(pre);
313
+
314
+ listEl.appendChild(det);
315
+ listEl.scrollTop = listEl.scrollHeight;
316
+
317
+ while (listEl.children.length > RING_SIZE) {
318
+ const first = listEl.firstChild;
319
+ if (first) listEl.removeChild(first);
320
+ else break;
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Append a structured debug entry to the in-DOM overlay. No-op unless the
326
+ * runtime gate is on (`window.__PP_DEBUG__ === true` or
327
+ * `localStorage.PP_DEBUG === "1"`). Stringification is lazy: the payload is
328
+ * not serialized when the gate is off, so this is safe to leave in hot paths.
329
+ */
330
+ export function debugLog(label: string, payload: unknown): void {
331
+ if (!isEnabled()) return;
332
+ if (typeof document === "undefined") return;
333
+
334
+ const entry: Entry = {
335
+ ts: Date.now(),
336
+ label,
337
+ json: safeStringify(payload),
338
+ };
339
+
340
+ entries.push(entry);
341
+ if (entries.length > RING_SIZE) entries.shift();
342
+
343
+ const append = () => {
344
+ try {
345
+ ensurePanel();
346
+ renderEntry(entry);
347
+ updateCount();
348
+ } catch {
349
+ // never break the host on debug failure
350
+ }
351
+ };
352
+
353
+ if (document.body) {
354
+ append();
355
+ } else {
356
+ document.addEventListener("DOMContentLoaded", append, { once: true });
357
+ }
358
+ }