@unoverse-platform/studio 0.1.93 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unoverse-platform/studio",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "type": "module",
5
5
  "description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
6
6
  "scripts": {
@@ -47,7 +47,9 @@ export async function primeTemplating(): Promise<void> {}
47
47
  * Deliberately only a dotted path: anything with a helper or a block is genuinely
48
48
  * text assembly and belongs in Handlebars.
49
49
  */
50
- const WHOLE_VALUE = /^\s*\{\{\s*([a-zA-Z_$][\w$]*(?:\.[\w$]+)*)\s*\}\}\s*$/;
50
+ // A segment may carry `-` or `/`: an org component's box is `<org>/<name>N`, and its
51
+ // paths are written as advertised (StringTemplateResolver, bracketSlashSegments).
52
+ const WHOLE_VALUE = /^\s*\{\{\s*([a-zA-Z_$][\w$/-]*(?:\.[\w$/-]+)*)\s*\}\}\s*$/;
51
53
 
52
54
  function lookup(path: string, ctx: RunContext): unknown {
53
55
  return path.split(".").reduce<any>((a, k) => (a == null ? a : a[k]), ctx as any);
@@ -48,10 +48,51 @@ Handlebars.registerHelper("contains", function (str, substring) {
48
48
  return str.toLowerCase().includes(substring.toLowerCase());
49
49
  });
50
50
 
51
+ /**
52
+ * A NODE ID MAY CARRY A SLASH, and Handlebars reads `/` as a path separator.
53
+ *
54
+ * An org component's box is minted as `<org>/<name>N` (componentRef), and every path
55
+ * the platform advertises for it is written the way an author would write it:
56
+ * `{{signal.some-org/card2.output.state}}`. Handlebars parses that as
57
+ * `signal.some-org.card2…`, finds nothing, and renders "" — no error, no log, an
58
+ * empty string where a page's state should be. Observed live 2026-09-05: a component
59
+ * capture parsed "" as JSON and the name on every extracted row was blank.
60
+ *
61
+ * Handlebars' own answer is the segment literal, `[some-org/card2]`, so every
62
+ * segment that carries a slash is bracketed before compile. Only bare path segments
63
+ * qualify (letters, digits, `_ $ -` and `/`): a quoted string, a URL with its colon, or
64
+ * a segment already bracketed is left exactly as written.
65
+ */
66
+ const SLASH_SEGMENT = /(?<=^|[\s.(|=])([\w$-]+(?:\/[\w$-]+)+)(?=[.\s)}|]|$)/g;
67
+ export function bracketSlashSegments(template: string): string {
68
+ if (!template.includes("/")) return template;
69
+ return template.replace(/\{\{[^}]*\}\}/g, (mustache) => {
70
+ let out = "";
71
+ let quote: string | null = null;
72
+ let run = "";
73
+ const flush = () => {
74
+ out += run.replace(SLASH_SEGMENT, "[$1]");
75
+ run = "";
76
+ };
77
+ for (const ch of mustache) {
78
+ if (quote) {
79
+ out += ch;
80
+ if (ch === quote) quote = null;
81
+ } else if (ch === '"' || ch === "'") {
82
+ flush();
83
+ quote = ch;
84
+ out += ch;
85
+ } else run += ch;
86
+ }
87
+ flush();
88
+ return out;
89
+ });
90
+ }
91
+
51
92
  // Create a memoized template compiler for performance
52
93
  const compileTemplate = memoize((template: string) => {
53
94
  // Disable HTML escaping to prevent issues like "I'll" becoming "I&#x27;ll"
54
- return Handlebars.compile(template, { noEscape: true });
95
+ return Handlebars.compile(bracketSlashSegments(template), { noEscape: true });
55
96
  });
56
97
 
57
98
  export function resolveStringTemplate(template: string, context: TemplateContext, logger: any): any {