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.
This commit is contained in:
Doxa Maps 2026-09-26 12:09:21 +00:00
parent 9888c4bcc9
commit 1228cd1f1a

View file

@ -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
}