What the PR does. Upstream #13968 (ff77cd02 + b2dae8db + 2514ad72, live head 2514ad72) replaces stripJsonc's two regexes with a string-aware scanner and makes parseMenuJsonc return no rows for an array root. It fixes #13250 (a comma before } or ] in a string was eaten), #13492 (an array root became rows "0","1") and #13493 (an inline // emptied the file). b2dae8db also reads JS whitespace outside strings (BOM, NBSP, Unicode spaces, VT, FF) as a space. 2514ad72 only adds a test (comma before } after an escaped quote, reported in the thread). Mirror commit 4d1a1b7d applies it onto baseline quattro-proof 1e433732 (quattro 035ce29f); 15 proof commits follow (tip e491b524). Advisory, not an approval.
Risk rationale: Small parser change. Each fix has differential and observed test evidence, all 127 differences match a requirement, and the fuzz found nothing worse than the base. But 135 compatibility changes are unapproved (prevents ready), the production QV4 runtime and its parse cost are untested, and upgraded users' previously-ignored extension files will start to apply.
Read the full review (5 more paragraphs)
Is the fix verified? Changed-code coverage: 17/17 decision lines, 34/34 decisions. Test results are current, 69/69 passed (run-ae0c976c). All obligation cells of the 6 parser requirements are observed_pass. Each issue reproduces at base 1e433732, not at head; 200k fuzzed inputs: none worse.
Compatibility. Readiness is not_assessed. Next: human_judgement (compat_changes_unapproved), "Approve each of the 135 backward-compatibility changes", by a reviewer (approved policy @ 1e433732). 135 compat_change_unapproved disclosures prevent readiness (136 findings, 1 fixed). All 127 differences are classified intended: 123 data inputs with JS whitespace go from parse error to parsed (BNZG); 4 are the issue reproducers. 41 ids changed at the 035ce29f refresh (two #10367 menu rows; 346 rows, not 344). The 5 test_diff and 3 removed_behavior findings are intended too.
Questions for a human. (1) Does Quickshell's QV4 engine match node on /\s/ and on cost (in node stripJsonc is ~4x slower on the default menu, ~17x on 1.1 MB)? No test runs QV4. (2) User extension files that loaded empty before (inline comment, Unicode space) now load and can override shipped entries.
Traceability. stripJsonc and parseMenuJsonc are traced at function level: E4J2, 3T3F, 66FW, C8W1, BNZG, BMFE (4 added; 3 modified, with PPDW). 23 more are code_changed only via file-level test traces; their 23 not_linked cells have no tagged test (4 more are suppressed). No spec-gap candidates; change effects were not analyzed (effects_unavailable).
Not caused by this PR. KI-MENU-JSONC-CR-LINE-ENDINGS and -STRIP-GAPS stay open. The upstream refresh changes no file this PR touches. No gate fails; 13 checks were skipped. The PR body's verification line names this evaluation.
AI-generated review opinion derived from the evidence package. It is not a verdict, does not change readiness, and must be confirmed by a human reviewer.
30 context notes (info — not a finding)
- AI · confirmed#13250: a comma is dropped only when the next token outside strings is } or ] (SW-REQ-260927-66FW, added)
The comma branch (MenuModel.js:26-38) looks ahead past whitespace and // comments and keeps the comma unless } or ] follows. String contents are copied verbatim (lines 13-19), so
"x, ]y"andmv f{.bak,}survive. I ran it on base 1e433732 and head: base gives labelx ]y, head givesx, ]y. - AI · confirmed#13493: a // comment outside strings is dropped wherever it starts on the line (SW-REQ-260928-C8W1, added)
Lines 23-25 skip to (not past) the next \n, so the tokens on either side stay apart;
1//c\n2is still rejected. I ran it on base 1e433732 and head: base gives 0 rows for{...} // note, head gives 1. The comment ends only at \n, so a CR-only file is unchanged (KI-MENU-JSONC-CR-LINE-ENDINGS stays open). - AI · confirmed#13492: an array root yields no rows (SW-REQ-260928-BMFE, added)
parseMenuJsonc adds
|| Array.isArray(parsed)to the non-object guard (MenuModel.js:100). I ran it: base gives rows0:x,1:y, head gives []. Array.isArray is realm-safe, so the engine's inventory note about instanceof across realms does not apply here. - AI · confirmedb2dae8db: JS whitespace outside strings becomes an ASCII space (SW-REQ-261001-BNZG, added)
Lines 39-42 map every /\s/ character except \n to ' ' (BOM, NBSP, U+1680, U+2000-200A, U+2028/9, U+202F, U+205F, U+3000, VT, FF, and also CR and tab, which JSON already accepts). Characters inside strings are untouched. It came with b2dae8db; MenuModel.js is unchanged at the live head 2514ad72.
- AI · confirmedSW-REQ-260922-E4J2 (JSONC stripping) is reworded from full-line comments to comments outside string literals
The description now says "strips // comments and trailing commas outside string literals". It matches the scanner and the PR's docs/menu.md text.
- AI · confirmedSW-REQ-260922-3T3F (non-object input gives an empty set) changes only its rationale (array root)
Only the rationale changed; the clause text is unchanged.
- AI · confirmedSYS-REQ-260922-PPDW (default + user menu merge) is modified on the mirror
This is the parent of the parser requirements. The edit is our proof work, not upstream code: only its hazard_review rationale (now describing the scanner and the array check) and review stamp changed. Its nominal, boundary and error_handling cells are observed_pass.
- AI opinion — unverified23 requirements are code_changed only through file-level test traces
menumodel-replay.test.mjs, menu-test.sh and report-reproducers.test.mjs carry whole-file Verifies traces (flagged weak_mapping); 2JZT, MQ37, RGCV, W17G and Y58B match only through MenuModel.js at file level, although their own functions did not change. These requirements share the files; their own text did not change. Each shows observed passing tests. Their 23 not_linked obligation cells have no tagged test and are NOT tested per class. Inherited: error_handling from SYS-REQ-260922-47T8 (2JZT, JRW1, MQ37, W17G, Y58B) and its external_call_timeout_bounded (2JZT, JRW1), boundary from SYS-REQ-260922-R8DQ (CYB9, N3RM, PRNV, XW52, 8VJQ), error_handling from SYS-REQ-260927-WC89 (N3RM), atomicity from SYS-REQ-260922-0M8A (EFNR), boundary/error_handling from STK-REQ-260922-XTNR (0M8A both, R8DQ error_handling, V7W6 both) and boundary from STK-REQ-260912-XJ5D (WC89). Own: 0M8A atomicity, R8DQ boundary, WC89 error_handling. Four more cells, external_call_timeout_bounded on MQ37, RGCV, W17G and Y58B, are suppressed: each requirement carries its own obligation_suppressions entry for that class (recorded 2026-09-30, unchanged on this mirror). Suppressed is not tested either. None of these classes concerns the parser change. The same 23 + 4 cells as last round.
- AI opinion — unverifiedPurpose branches are covered and their obligation cells are observed passing
diffCoverage: 17/17 changed decision lines, 34/34 changed decisions, 0 uncovered (mcdc:aggregate+bash+js). testResults: junit, current, 69 passed / 0 failed / 0 skipped (the baseline run ingested 68; the extra test case is the mirror's 'SW-REQ-260928-BMFE input domain' node test; the 3 report tests and the stripJsonc MC/DC test are replaced one for one). The count is the same as for the previous evaluation af2d9e82: 2514ad72 adds one assertion inside the existing menu-test.sh test case, not a new test case. 66FW, C8W1, BMFE, BNZG, E4J2 and 3T3F have every obligation cell observed_pass (boundary, error_handling, malformed_input, input_domain, totality).
- AI opinion — unverifiedMirror composition: upstream product change vs our proof work
4d1a1b7d ("review: apply omacom/omarchy#13968 @2514ad72 onto baseline quattro-proof 1e433732") applies upstream 2514ad72. I checked that its added and removed lines in MenuModel.js, docs/menu.md and menu-test.sh equal the upstream PR's diff against its base 8b4eae6 line for line. In the evaluated tree docs/menu.md is identical to 2514ad72, and MenuModel.js differs only by added comment lines (
// Implements:and two//mcdc:ignore). The next 15 commits (af7af6b3..f43fd837) are our proof work: the 14 commits of the previous evaluation af2d9e82, replayed unchanged, and f43fd837, which moves 51 menu-test.sh line citations in requirements and reviews down by one line for the new assertion. e491b524 is an empty record commit. Against af2d9e82 the evaluated tree differs only by the one assertion line in menu-test.sh (line 72 on the mirror) and those 51 line numbers. MenuModel.js and every other product file are byte-identical, so the package's coverage, behaviour diff and compatibility findings are the same as for af2d9e82. - AI · confirmed2514ad72: a comma before } after an escaped quote in a label is kept (test only)
The new menu-test.sh assertion (line 72 on the mirror, line 54 on the PR branch) parses
{"a": {"label": "say \"hello\", }"}}and expects the labelsay "hello", }. I ran it: on base quattro 035ce29f (and upstream tip ec76c070, same MenuModel.js) the label comes back assay "hello" }and the assertion fails; on the head it passes. Inside a string the scanner copies a backslash together with the character after it (MenuModel.js:15-16), so the escaped quote does not end the string and the comma stays inside it. This follows SW-REQ-260927-66FW; no product code changed. - AI opinion — unverifiedMirror-only: four JSONC KI records marked fixed by this PR; history in time order
KI-MENU-JSONC-ARRAY-ROOT, -COMMA-IN-STRING, -INLINE-COMMENT and -UNICODE-WHITESPACE carry status: fixed (fixed by #13968) on the mirror. Each also keeps the baseline's upstream reference: an 'Upstream:' note on all four and, on COMMA-IN-STRING, an upstream_report record for #13250. I checked that each file's history
at:stamps ascend. This is proof data on the mirror, not part of the upstream PR. The KI evidence on the mirror records known_issue_not_reproduced for the fixed KIs. - AI opinion — unverifiedUpstream refresh to quattro 035ce29f changes no file this PR touches
Between the PR's base 8b4eae6 and 035ce29f, upstream changed only two menu-area files: one property in BarWidget.qml and two data rows in default/omarchy/omarchy-menu.jsonc (install.service.microsoft and remove.service.microsoft, #10367, lines 235 and 317). MenuModel.js, Menu.qml, docs/menu.md, menu-test.sh and the sample extension are byte-identical. The rest of the refresh (the lock screen's fingerprint work #7158 and #7806, the image picker, and the baseline's new lock requirements, lock-node command and lock-js-fingerprintmodel MC/DC target) is outside this PR's diff and its 30 requirements. The two new rows carry escaped quotes,
&&/||and]]inside strings: I ran base and head on the shipped menu and both give the same 346 rows with identical row data. - AI opinion — unverifiedRemoved whole-line-comment \s quirk is preserved (cf-39627352fe66ab9c, fixed)
At base,
\u00A0// notelines were removed whole by ^\s*. At head, the NBSP becomes a space and the comment is dropped, so the row count is the same (I ran it: 1 row at both). menu-test.sh pins this for BOM, NBSP, LSEP, IDEO, VT and FF before a comment. This one is recorded as fixed, not unapproved. - AI opinion — unverifiedDifferential: all 127 recorded differences checked; none unexplained
213 inputs over 5 partitions (data 176, corpus 17, lifecycle 14, scale 3, timing 3). Each of the 123 data differences carries one of VT, FF, NBSP, U+1680, U+2000-200A, U+2028, U+2029, U+202F, U+205F, U+3000 or BOM outside strings, before the first line or before every line of one of three seeds, and goes from strip_parse=ERR (0 rows) to object: 41 each with 0 (sample extension), 2 (#11211 snippet) and 346 (shipped menu) rows. A leading BOM on its own makes no difference, because the harness drops it the way FileView text() does. CR, CRLF, empty, huge and invalid-UTF-8 inputs showed no difference, nor did the lifecycle, scale and timing inputs. The 4 corpus differences are the issue reproducers. Recorded classification: intended with requirement (123 BNZG, 2 C8W1, 1 BMFE, 1 66FW). Separately, I fuzzed 200,000 random JSONC files (labels with , } ] / " \\, NBSP and U+2028, random comments, trailing commas, CRLF): 165,695 same, 34,305 changed to the correct result, 0 changed to a wrong one (run on af2d9e82 against base 1e433732; MenuModel.js is byte-identical at e491b524). That fuzz is outside the package.
- AI opinion — unverified41 differences got new ids at the 035ce29f refresh because the shipped menu gained two upstream rows
The 41 whitespace variants of the shipped-menu seed (default/omarchy/omarchy-menu.jsonc) got new input digests because the seed file now has the two #10367 rows. Their head output is 346 rows instead of 344; the base output is still strip_parse=ERR with 0 rows. Same behaviour class and requirement (SW-REQ-261001-BNZG) as before that refresh, and the engine classifies all 41 intended. The other 86 differences keep their ids and digests. All 127 ids are unchanged since the previous evaluation af2d9e82.
- AI · confirmedIntended difference bd-05efa2fff912da5a: #13493 reproducer (sample extension with inline comment tail): 0 to 3 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-260928-C8W1 specifies. Recorded classification: intended (SW-REQ-260928-C8W1). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other corpus differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-41c905c2c01b5b15: #13493 reproducer (inline comment rows): 0 to 3 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-260928-C8W1 specifies. Recorded classification: intended (SW-REQ-260928-C8W1). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other corpus differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-4453e5b664893137: #13492 array root: phantom rows 0,1 to no rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-260928-BMFE specifies. Recorded classification: intended (SW-REQ-260928-BMFE). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other corpus differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-9495f0dcccf57210: #13250 comma before } inside a label/description is kept
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-260927-66FW specifies. Recorded classification: intended (SW-REQ-260927-66FW). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other corpus differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-a2bcd5d37ee80ee1: BOM before every line of the shipped default menu: 0 to 346 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-261001-BNZG specifies. Recorded classification: intended (SW-REQ-261001-BNZG). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other 122 data differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-a3c3bb2d3fccb3fc: NBSP before every line of the shipped default menu: 0 to 346 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-261001-BNZG specifies. Recorded classification: intended (SW-REQ-261001-BNZG). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other 122 data differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-5eed726fc774fd45: U+2028 before every line of the shipped default menu: 0 to 346 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-261001-BNZG specifies. Recorded classification: intended (SW-REQ-261001-BNZG). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other 122 data differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-f9d067880f260e26: vertical tab before every line of the shipped default menu: 0 to 346 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-261001-BNZG specifies. Recorded classification: intended (SW-REQ-261001-BNZG). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other 122 data differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-df933919303826e2: form feed before every line of the issue #11211 snippet: 0 to 2 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-261001-BNZG specifies. Recorded classification: intended (SW-REQ-261001-BNZG). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other 122 data differences follow the same pattern (see the differential finding).
- AI · confirmedIntended difference bd-cfd90a4600fd0086: U+3000 before every line of the issue #11211 snippet: 0 to 2 rows
Base gives strip_parse=ERR or wrong rows; head gives the result SW-REQ-261001-BNZG specifies. Recorded classification: intended (SW-REQ-261001-BNZG). I reproduced this class of input with node on base 1e433732 vs head MenuModel.js. Representative of its group; the other 122 data differences follow the same pattern (see the differential finding).
- AI opinion — unverifiedKI-MENU-JSONC-CR-LINE-ENDINGS and KI-MENU-JSONC-STRIP-GAPS remain open; the PR does not change them
I ran both: a CR-only file with a // comment (
// c\r{...}) and a /* */ block comment give 0 rows at base and at head. The scanner ends a comment only at \n and has no block-comment state. The PR's docs/menu.md now says block comments are not supported. These KIs pre-date the PR; their evidence on the mirror is still known_issue_reproduced. - AI opinion — unverifiedChange effects not analyzed; scope analysis bounds acknowledged
effects_unavailable: review.effects covers C/C++ only, so callers, gained effects, new mutable state and call-level requirement impact are not in the package for this JS change. The scope approximations say no uncodified connection could be detected, so 'No spec-gap candidates' proves nothing here. By reading: stripJsonc/parseMenuJsonc add no callees, no I/O, and no state beyond local variables.
- AI opinion — unverifiedAll 135 compatibility findings stay unapproved; only a human reviewer can approve them
Each of the 135 compat_change_unapproved disclosures prevents readiness until a reviewer or above approves it under the approved policy @ 1e433732, or the author fixes it. The next decision states the exact count ("Approve each of the 135 backward-compatibility changes"). The finding ids are the same 135 as for af2d9e82; the auditor (buger, who is also this PR's author) records an advisory note on each, as for that evaluation, naming the requirement or issue it follows. The engine shows those notes as informational, not approvals, and they resolve nothing. My reading that each difference is intended is commentary, not an approval either.
- AI opinion — unverifiedThe PR body's verification line names this evaluation; its row count is from the PR's base
The body cites evaluated merge e491b52 on quattro 035ce29 with 69 passed, which is this package. It says the shipped menu parses to 344 rows: that is the count at the PR's base 8b4eae6. On quattro 035ce29f the shipped menu has 346 rows (two #10367 rows) at both base and head, with identical row data (I ran it), so the body's claim of an unchanged shipped menu still holds.