From 1228cd1f1af67419002b42b6edbe36f2e3d366b7 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 12:09:21 +0000 Subject: [PATCH] check:facet-cascade: read the facet rail by brace depth, so getter titles stop blinding it The gate reads facetRail out of dashboard.config.js with /\{[^{}]*\}/, which matches only objects with no braces inside. 53e2c662 translated the rail titles as getters (get title() { return tStatic(...) || 'People' }), so the four drill sections stopped parsing. The bus was then built with no hierarchy, and the gate reported 21 'orphaned child' failures in a dashboard that cascades correctly (the real one reads the live config object, never this parser). Red on main since 53e2c662 (bisected by swapping the gate's only two inputs: 7f700935 OK, 53e2c662 FAIL). The parser now splits the array into top-level objects by brace depth, skipping quoted strings, and throws if a section has neither a drill nor a facet. So a parse miss is an error, never a false pass or a false fail. Headless: it passes on main's real files. With a poisoned bus (configureHierarchy installs no hierarchy) it FAILS; restored, it passes. --- internal/check-facet-cascade.mjs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/internal/check-facet-cascade.mjs b/internal/check-facet-cascade.mjs index a76f4654f..2e6c72520 100644 --- a/internal/check-facet-cascade.mjs +++ b/internal/check-facet-cascade.mjs @@ -114,11 +114,20 @@ function readFacetRail(src) { if (end < 0) throw new Error('facetRail array is unterminated') const body = src.slice(open, end + 1) const sections = [] - // one entry per `{ … }` object at the top level of that array - const objRe = /\{[^{}]*\}/g - let m - while ((m = objRe.exec(body))) { - const obj = m[0] + // One entry per object at the TOP level of that array, found by brace depth. A `{[^{}]*}` regex + // matched only objects with no braces inside, and 53e2c662 gave every section a getter title + // (`get title() { return tStatic(…) || 'People' }`): the four drill sections stopped parsing, the + // bus was built with no hierarchy, and this gate reported 21 "orphans" in a dashboard that cascades + // correctly. Braces inside quoted strings are skipped, for the same reason. + const objs = [] + for (let i = 1, d = 0, st = -1, q = null; i < body.length - 1; i++) { + const c = body[i] + if (q) { if (c === '\\') i++; else if (c === q) q = null; continue } + if (c === "'" || c === '"' || c === '`') { q = c; continue } + if (c === '{') { if (d === 0) st = i; d++ } + else if (c === '}') { d--; if (d === 0 && st >= 0) { objs.push(body.slice(st, i + 1)); st = -1 } } + } + for (const obj of objs) { const id = /id:\s*'([^']+)'/.exec(obj)?.[1] const drillRaw = /drill:\s*\[([^\]]*)\]/.exec(obj)?.[1] const drill = drillRaw ? [...drillRaw.matchAll(/'([^']+)'/g)].map((x) => x[1]) : undefined @@ -126,6 +135,10 @@ function readFacetRail(src) { sections.push({ id, drill, facet }) } if (!sections.length) throw new Error('no facetRail sections parsed') + // A section with neither a drill nor a facet is one this parser could not read: fail loudly + // rather than test a hierarchy that is not the dashboard's. + const unread = sections.filter((x) => !x.id || (!x.drill && !x.facet)) + if (unread.length) throw new Error(`facetRail: ${unread.length} section(s) could not be read (${unread.map((x) => x.id || '?').join(', ')})`) return sections }