From 067ad11a8ed4e2d1de00e5b6d9c182014f3f9b15 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 02:52:11 +0000 Subject: [PATCH] fix(check-design-reaches-app): find the knob file on two-tree checkouts; unreadable knob file is a FAULT, never clean On a two-tree checkout (model under app/) the script read theme/web/custom-variables.scss, theme/web/, themesource/ and deployment/ from the working directory, found nothing, and printed "knobs bound 0 of 0" under a clean verdict (exit 0); the dead-SCSS-bridge pass was silently skipped too ($use-css-variables read 'unknown'). - Model directory from find_model_dir (_common.sh: PROJECT_ROOT, MPR_FILE, app/ probe), with an app/ directory probe when no .mpr is found; design/ and mdlsource/ from the repo root. - Missing knob file, or one declaring no knobs: FAULT, knobs UNMEASURED, exit 2. - Knob file accepted as a third argument as well as CUSTOM_VARS. - Fixture cases 9-10: both layouts (with and without an .mpr), two-tree dead bridge, missing and empty knob file, both overrides. CAPTURE.md records the real two-tree layout. Field run (2026-09-28, Mendix 11.14.0, model under app/, run from the repo root with no arguments): master 0 of 0 knobs, clean; this commit 26 of 30 knobs bound, 157 of 157 tokens, 239 of 269 classes, $use-css-variables: true. CUSTOM_VARS= -> exit 2. Replayed on current master: #162 had added a one-level app/*.mpr probe to the same lines; this supersedes it with find_model_dir. Re-run on a scratch Mendix 11.13 app, both layouts: master prints a clean exit 0 with the knob file missing; this exits 2 with FAULT UNMEASURED. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw --- CHANGELOG.md | 1 + project-bin/check-design-reaches-app.sh | 104 +++++++++++++----- .../fixtures/design-reaches-app/CAPTURE.md | 29 +++++ tests/wave2/test-design-reaches-app.sh | 61 ++++++++++ 4 files changed, 167 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f65597c..9327bb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- fix(project-bin/check-design-reaches-app.sh): **the model directory now comes from `find_model_dir`, and a knob file it cannot read is a fault, never clean.** #162 added a one-level `app/*.mpr` probe; this replaces it with `find_model_dir` in `_common.sh` (`$PROJECT_ROOT`, `$MPR_FILE`, the `app/` probe), plus an `app/theme` / `app/deployment` probe when no `.mpr` is found. The fault is the part #162 did not cover: a missing knob file, or one that declares no knobs, printed "knobs bound 0 of 0" under `clean` with exit 0; it now reports `FAULT … UNMEASURED` and exits 2. The knob file can also be passed as a third argument. Fixture: `tests/wave2/test-design-reaches-app.sh` cases 9–10 (both layouts, with and without an `.mpr`, missing and empty knob file, overrides). Field run: a Stage 5 obligation pass on a two-tree Mendix 11.14 app, 0 of 0 before, 26 of 30 knobs and 157 of 157 tokens after, with no arguments — a two-tree greenfield build - chore(inbox): **triage of `contrib/inbox/`: removed 15 promoted/obsolete files, gave the rest neutral names and scrubbed identifiers.** Gone: the ten machine-diff `*-patches.md` files from 2026-09-14, the 2026-09-27 field-project patch file, the built company-brain idea, and three already-promoted or superseded bug dumps. Kept for now: `2026-09-27-mxcli-upstream-issues.md`, because five drafts under `bug-logs/pending-github-issues/` cite it as their source. Four remaining files were renamed to `field-project-{a,b,c}` names; project and app names, home-style paths and personal names inside the 17 remaining entries were replaced with neutral labels or placeholders (technical content unchanged). — MendixMau - learn(learned-mdl-preflight, learned-detection-gaps, microflow-preflight, testing-shape, learned-mdl-cannot-express, learned-page-patterns, rest-integration-first-time-right, journey-proof, learned-datagrid-customcontent-binding): **the skills side of BUG-142 to BUG-154.** STOP row 12 rewritten shorter for v0.24.0 — `ContentParams` takes a bare attribute or a quoted literal only, and the `toString(Attr)` fix it taught no longer builds; row 14 notes a narrowing `grant` merges, row 23 that `diff` prints false `-`/`~` lines. Detection gaps: the ContentParams row now covers any object-bound `dynamictext` and says check passes `if … then … else`; reserved words cover entities; new CE1870 row. microflow-preflight: CONV013 false positive and doc-comment round-trip resolved from "Not verified"; error handler without `return` is the second merge-overlap shape. testing-shape: `--test-endpoint` writes into the working model until a clean stop; `run --local` starts with scheduled events off (new register row). Also: five cannot-express rows (tab badge, validate-while-typing, AppTitle, always-open nav groups, sidebar toggle / page-load / Marketplace layout) and the DG2 `emptyplaceholder` correction; control-bar attributes, nested list view `Editable` and REPLACE-not-re-run-safe in page patterns; v0.24.0 REST bullets; two OQL journey traps. Triages three inbox notes (ContentParams, REST/JSON/page field notes, scheduled events) — card-disbursement requirements-driven build - learn(bug-logs): **thirteen new ledger entries (BUG-142 to BUG-154) and five retests from mxcli v0.24.0 / Mendix 11.13.0.** New: `ContentParams` builds only from a bare attribute or a quoted literal, and `if … then … else` passes check and fails CE1613 (supersedes the BUG-23 fix); theme seeder and Atlas-map gaps; `create user role` omits `CheckSecurity`/`GUID`; a narrowing `grant` merges; `diff` false modifications; check/exec disagreeing on same-script documents; E007 on a last quoted enum literal; a table of check/lint misfires and check-green/mxbuild-red constructs; DG2 and filter definition gaps; `rest call … body $Var` sends the literal text; published-REST, mapping and Java action gaps; OQL `HAVING` and date aggregates; MDL surface gaps. Retests: BUG-08 (REPLACE still collides, and on reused child names), BUG-77 reproduced with its trigger isolated, BUG-92 and BUG-117 field instances, BUG-122 on a snippet list view; the layout-merge draft gains the error-handler shape. Each from a `check`/exec/mxbuild A/B or a BSON read on a scratch copy — card-disbursement requirements-driven build diff --git a/project-bin/check-design-reaches-app.sh b/project-bin/check-design-reaches-app.sh index e37149c7..24c07f11 100755 --- a/project-bin/check-design-reaches-app.sh +++ b/project-bin/check-design-reaches-app.sh @@ -71,9 +71,21 @@ # styling nothing. # # project-bin/check-design-reaches-app.sh # defaults, from the project root -# project-bin/check-design-reaches-app.sh +# project-bin/check-design-reaches-app.sh [custom-variables.scss] # -# Exit 0 clean, 1 violations, 2 inspected nothing (NOT a pass). +# Exit 0 clean, 1 violations, 2 inspected nothing or a pass could not run (NOT a pass). +# +# ── BOTH LAYOUTS (2026-09-28) ─────────────────────────────────────────────────────────────── +# +# The theme, themesource and deployment trees sit beside the .mpr, NOT at the project root. +# On a single-tree checkout those are the same directory; on a two-tree checkout (repo at the +# root, the app under app/) they are not. This script used to read theme/web/custom-variables.scss +# relative to the working directory, found nothing on a two-tree checkout, and printed +# "knobs bound 0 of 0" under "clean, 1 warning" — a false clean on a Mendix 11.14 app whose +# real figure, with the path given by hand, was 26 of 30 knobs bound and 157 of 157 tokens. +# The model directory is now resolved the way every other project-bin script resolves it +# (find_model_dir in _common.sh: $PROJECT_ROOT, $MPR_FILE, the app/ probe), and a knob file +# that cannot be found, or that declares no knobs, is an instrument fault — exit 2, never clean. # # THIS CHECK REQUIRES A BUILD. That is the point: there is no way to answer "did it reach the # app" from source alone, which is exactly why nothing answered it. With no built stylesheet it @@ -84,38 +96,56 @@ set -uo pipefail DESIGN="${1:-}" BUILT="${2:-}" -# Where the model lives. The design system and mdlsource/ sit at the repo root either way; the -# built sheet, theme/ and themesource/ sit beside the .mpr — the root on a single-tree checkout, -# app/ on a two-tree one (repo at the root, `mxcli new` app under app/, the same one-level probe -# _common.sh's find_mpr makes). Root-relative defaults alone exited 2 on every two-tree project -# (field run, card-disbursement requirements-driven build, 2026-09-25). -MP="" -if ! ls ./*.mpr >/dev/null 2>&1 && ls app/*.mpr >/dev/null 2>&1; then MP="app/"; fi +# ── Where things live ─────────────────────────────────────────────────────────────────────── +# ROOT holds design/ and mdlsource/; MODEL_DIR holds the .mpr and, beside it, theme/, +# themesource/ and deployment/. The script has always run from the project root, so ROOT is +# $PROJECT_ROOT when set, else the working directory — not _common.sh's own tier-2 guess, which +# for the toolkit's shared copy is the toolkit. MODEL_DIR comes from find_model_dir when an .mpr +# can be found; a copy installed beside an older _common.sh, or a tree with no .mpr, falls back +# to probing app/ for the framework's trees. +ROOT="${PROJECT_ROOT:-$(pwd)}" +MODEL_DIR="" +if [ -f "$(dirname "${BASH_SOURCE[0]}")/_common.sh" ]; then + PROJECT_ROOT="$ROOT" + . "$(dirname "${BASH_SOURCE[0]}")/_common.sh" + if type find_model_dir >/dev/null 2>&1; then + MODEL_DIR="$(find_model_dir 2>/dev/null)" || MODEL_DIR="" + fi +fi +if [ -z "$MODEL_DIR" ]; then + MODEL_DIR="$ROOT" + if [ ! -d "$ROOT/theme" ] && [ ! -d "$ROOT/deployment" ] && { [ -d "$ROOT/app/theme" ] || [ -d "$ROOT/app/deployment" ]; }; then + MODEL_DIR="$ROOT/app" + fi +fi +# Paths print relative to where the user stands when they can; absolute otherwise. +rel() { case "$1" in "$(pwd)"/*) printf '%s' "${1#"$(pwd)"/}" ;; *) printf '%s' "$1" ;; esac; } if [ -z "$DESIGN" ]; then - for c in design/ds.css design/design-system.css; do - [ -f "$c" ] && { DESIGN="$c"; break; } + for c in "$ROOT/design/ds.css" "$ROOT/design/design-system.css" "$MODEL_DIR/design/ds.css" "$MODEL_DIR/design/design-system.css"; do + [ -f "$c" ] && { DESIGN="$(rel "$c")"; break; } done fi if [ -z "$BUILT" ]; then - for c in "${MP}deployment/web/theme.compiled.css" "${MP}deployment/web/theme.css"; do - [ -f "$c" ] && { BUILT="$c"; break; } + for c in "$MODEL_DIR/deployment/web/theme.compiled.css" "$MODEL_DIR/deployment/web/theme.css"; do + [ -f "$c" ] && { BUILT="$(rel "$c")"; break; } done fi # The framework's own customization surface. Atlas has called this file the same thing since -# Atlas 2; a project that renamed it passes it as a third argument via CUSTOM_VARS. -CUSTOM_VARS="${CUSTOM_VARS:-${MP}theme/web/custom-variables.scss}" +# Atlas 2; a project that renamed it passes it as the third argument, or via CUSTOM_VARS. +CUSTOM_VARS="${3:-${CUSTOM_VARS:-$(rel "$MODEL_DIR/theme/web/custom-variables.scss")}}" +THEME_WEB="$MODEL_DIR/theme/web" if [ -z "$DESIGN" ] || [ ! -f "$DESIGN" ]; then printf 'check-design-reaches-app: no design system stylesheet found.\n' >&2 - printf ' Searched: design/ds.css, design/design-system.css\n' >&2 + printf ' Searched: design/ds.css, design/design-system.css under %s and %s\n' "$ROOT" "$MODEL_DIR" >&2 printf ' This is NOT a pass. Name it explicitly, or run from the project root.\n' >&2 exit 2 fi if [ -z "$BUILT" ] || [ ! -f "$BUILT" ]; then printf 'check-design-reaches-app: no BUILT stylesheet found.\n' >&2 - printf ' Searched: %sdeployment/web/theme.compiled.css, %sdeployment/web/theme.css\n' "$MP" "$MP" >&2 + printf ' Searched: deployment/web/theme.compiled.css, deployment/web/theme.css under %s\n' "$MODEL_DIR" >&2 printf ' Run a build first. "Did the design system reach the app" cannot be answered from\n' >&2 printf ' source, and answering it from source is how this defect shipped.\n' >&2 printf ' This is NOT a pass.\n' >&2 @@ -124,9 +154,11 @@ fi VIOLATIONS=0 WARNINGS=0 +FAULTS=0 report() { printf 'FAIL %s\n %s\n' "$1" "$2"; VIOLATIONS=$((VIOLATIONS + 1)); } warn() { printf 'WARN %s\n %s\n' "$1" "$2"; WARNINGS=$((WARNINGS + 1)); } +fault() { printf 'FAULT %s\n %s\n' "$1" "$2"; FAULTS=$((FAULTS + 1)); } # ── Pass 0: read the design system ────────────────────────────────────────────────────────── # Tokens: custom properties declared in a :root block. Values too, because a knob may be bound @@ -261,19 +293,19 @@ done # Only dead where the theme actually runs on custom properties. Where it does not, assigning a # framework SCSS variable is the CORRECT bridge and must not be reported. CSS_VARS_MODE="unknown" -if grep -rqE '^[[:space:]]*\$use-css-variables[[:space:]]*:[[:space:]]*true' "${MP}theme/web/" 2>/dev/null; then +if grep -rqE '^[[:space:]]*\$use-css-variables[[:space:]]*:[[:space:]]*true' "$THEME_WEB/" 2>/dev/null; then CSS_VARS_MODE="true" -elif grep -rqE '^[[:space:]]*\$use-css-variables[[:space:]]*:[[:space:]]*false' "${MP}theme/web/" 2>/dev/null; then +elif grep -rqE '^[[:space:]]*\$use-css-variables[[:space:]]*:[[:space:]]*false' "$THEME_WEB/" 2>/dev/null; then CSS_VARS_MODE="false" fi if [ "$CSS_VARS_MODE" = "true" ]; then - for f in "${MP}"theme/web/*.scss "${MP}"themesource/*/web/*.scss; do + for f in "$THEME_WEB"/*.scss "$MODEL_DIR"/themesource/*/web/*.scss; do [ -f "$f" ] || continue - case "${f#"$MP"}" in themesource/atlas_*|themesource/atlas_*/*) continue ;; esac + case "$f" in */themesource/atlas_*) continue ;; esac while IFS= read -r hit; do [ -z "$hit" ] && continue ln="${hit%%:*}" - report "$f:$ln assigns a framework SCSS variable while the theme runs \$use-css-variables: true" \ + report "$(rel "$f"):$ln assigns a framework SCSS variable while the theme runs \$use-css-variables: true" \ 'That assignment compiles to nothing. Set the matching CSS custom property in the framework customization file instead.' done </dev/null) @@ -285,8 +317,10 @@ fi MODEL_CLASS_TOTAL=0 MODEL_CLASS_MISSING=0 MODEL_MISSING_SAMPLE="" -if [ -d mdlsource ]; then - MODEL_CLASSES="$(grep -rhoE "Class:[[:space:]]*'[^']+'" mdlsource 2>/dev/null \ +MDLSOURCE="" +for c in "$ROOT/mdlsource" "$MODEL_DIR/mdlsource"; do [ -d "$c" ] && { MDLSOURCE="$c"; break; }; done +if [ -n "$MDLSOURCE" ]; then + MODEL_CLASSES="$(grep -rhoE "Class:[[:space:]]*'[^']+'" "$MDLSOURCE" 2>/dev/null \ | sed "s/.*'\\(.*\\)'/\\1/" | tr ' ' '\n' | grep -E '^[A-Za-z_-][A-Za-z0-9_-]*$' | sort -u)" for cls in $MODEL_CLASSES; do MODEL_CLASS_TOTAL=$((MODEL_CLASS_TOTAL + 1)) @@ -300,9 +334,18 @@ if [ -d mdlsource ]; then fi # ── Verdicts ──────────────────────────────────────────────────────────────────────────────── -if [ "$KNOB_TOTAL" -eq 0 ]; then - warn "no framework knobs found in $CUSTOM_VARS" \ - 'Pass 1 could not run. Set CUSTOM_VARS to the framework customization file, or say in the review why this project has none.' +# A knob file that is missing, or that declares nothing, means Pass 1 read nothing. "0 of 0 +# knobs bound" under a clean verdict is the false green this used to print on every two-tree +# checkout, so it is a FAULT: exit 2, the pass is UNMEASURED, never clean. +KNOBS_LINE="$KNOB_BOUND of $KNOB_TOTAL framework knobs point at a design-system token" +if [ ! -f "$CUSTOM_VARS" ]; then + fault "framework customization file not found: $CUSTOM_VARS" \ + "Pass 1 (knobs bound) did not run. Model directory: $MODEL_DIR. Pass the file as the third argument or CUSTOM_VARS=; on a two-tree checkout check that the .mpr is under app/." + KNOBS_LINE="UNMEASURED — $CUSTOM_VARS not found" +elif [ "$KNOB_TOTAL" -eq 0 ]; then + fault "no framework knobs declared in $CUSTOM_VARS" \ + 'Pass 1 (knobs bound) read nothing: no uncommented --name: declarations. Point CUSTOM_VARS at the real framework customization file, or say in the review why this project has none.' + KNOBS_LINE="UNMEASURED — no knobs declared in $CUSTOM_VARS" elif [ "$KNOB_BOUND" -eq 0 ]; then report "0 of $KNOB_TOTAL framework knobs are bound to a design-system token" \ "The framework is still wearing its own defaults. Unbound:${UNBOUND_SAMPLE:- (all)}" @@ -334,7 +377,8 @@ fi printf '\n' printf 'design system %s\n' "$DESIGN" printf 'built stylesheet %s\n' "$BUILT" -printf 'knobs bound %s of %s framework knobs point at a design-system token\n' "$KNOB_BOUND" "$KNOB_TOTAL" +printf 'knob file %s\n' "$CUSTOM_VARS" +printf 'knobs bound %s\n' "$KNOBS_LINE" printf 'tokens arrived %s of %s\n' "$TOKENS_ARRIVED" "$TOKEN_COUNT" printf 'tokens valued %s of %s carry the design value in a bare :root\n' "$TOKENS_VALUED" "$TOKEN_COUNT" printf 'classes arrived %s of %s\n' "$CLASSES_ARRIVED" "$CLASS_COUNT" @@ -347,6 +391,10 @@ if [ "$VIOLATIONS" -gt 0 ]; then printf '—-the tokens were never the problem, which is why every other check stayed green.\n' exit 1 fi +if [ "$FAULTS" -gt 0 ]; then + printf '\ncheck-design-reaches-app: %s pass(es) could not run — UNMEASURED, NOT a pass.\n' "$FAULTS" + exit 2 +fi if [ "$WARNINGS" -gt 0 ]; then printf '\ncheck-design-reaches-app: clean, %s warning(s) — read them.\n' "$WARNINGS" exit 0 diff --git a/tests/wave2/fixtures/design-reaches-app/CAPTURE.md b/tests/wave2/fixtures/design-reaches-app/CAPTURE.md index 77e098fd..aff0ab50 100644 --- a/tests/wave2/fixtures/design-reaches-app/CAPTURE.md +++ b/tests/wave2/fixtures/design-reaches-app/CAPTURE.md @@ -38,3 +38,32 @@ no selector altered, no structure simplified. The 0-of-35 and 0-of-28 rows are the whole reason this instrument exists: both were true while `mx check`, `mxcli lint`, the MDL suite and two e2e journeys were green. + +## The two-tree layout (case 9), captured 2026-09-28 + +Case 9 builds its project in the shape of a real two-tree checkout, a Mendix 11.14.0 app +(mxbuild 11.14.0, Atlas 3, `$use-css-variables: true`) with the model under `app/`. The +paths, verbatim from that checkout: + +| Path (relative to the repo root) | Holds | +|---|---| +| `design/ds.css` | the design system: tokens and classes | +| `mdlsource/` | the MDL scripts the WARN pass reads | +| `app/.mpr` + `app/mprcontents/` | the model | +| `app/theme/web/custom-variables.scss` | the framework knobs (664 lines, 35 uncommented declarations) | +| `app/themesource//web/` | theme modules | +| `app/deployment/web/theme.compiled.css` | the built sheet (1.2 MB) | + +Measured there on 2026-09-28, with the built sheet passed by hand and nothing else: + +| | Before the fix (knob file read from the root) | After the fix (no arguments at all) | +|---|---|---| +| knobs bound | **0 of 0** | **26 of 30** | +| tokens arrived | 157 of 157 | 157 of 157 | +| classes arrived | 239 of 269 | 239 of 269 | +| `$use-css-variables` | unknown (dead-bridge pass silently skipped) | true | +| verdict | `clean, 3 warning(s)`, exit 0 | `clean, 2 warning(s)`, exit 0 | + +The 35 declarations include some that name no knob the built bare `:root` defines; those +drop out of the denominator. The "before" verdict is the defect: a pass that read nothing, +reported as clean. diff --git a/tests/wave2/test-design-reaches-app.sh b/tests/wave2/test-design-reaches-app.sh index c089aeef..ce416cec 100755 --- a/tests/wave2/test-design-reaches-app.sh +++ b/tests/wave2/test-design-reaches-app.sh @@ -27,6 +27,10 @@ # an instrument that green-lights a project with no compiled stylesheet is worse than # absent. Exit 2, and 2 must not be confused with 0. # +# 5. BOTH LAYOUTS, AND NO SILENT ZERO. On a two-tree checkout the knob file sits under app/; +# a knob file the instrument cannot find or read reports UNMEASURED and exits 2, never +# "0 of 0 knobs bound" under a clean verdict (field run 2026-09-28). +# # Usage: bash test-design-reaches-app.sh [path-to-check-design-reaches-app.sh] set -u @@ -141,5 +145,62 @@ mkdir -p "$d/themesource/atlas_core/web" printf '$brand-primary: var(--brand);\n' > "$d/themesource/atlas_core/web/main.scss" check "a vendored atlas_core file is not reported" "$(code "$d")" "0" +# ── 9. both layouts: the app under app/ ───────────────────────────────────────────────────── +# The theme, themesource and deployment trees sit beside the .mpr. On a two-tree checkout +# (design/ and mdlsource/ at the repo root, the app under app/) the instrument used to read +# theme/web/custom-variables.scss from the root, find nothing, and print "0 of 0 knobs bound" +# under a clean verdict. Field run 2026-09-28: 0 of 0 by default, 26 of 30 with the path given +# by hand. The layout below is the real one's, path for path (see CAPTURE.md). +two_tree() { + d="$(project "$1" "$2" "$3")" + mkdir -p "$d/app" "$d/mdlsource" + mv "$d/theme" "$d/deployment" "$d/themesource" "$d/app/" + [ "$4" = "mpr" ] && : > "$d/app/App.mpr" + printf '%s' "$d" +} +d="$(two_tree tt built-fixed.css true mpr)" +out="$(run "$d")" +check "two-tree (.mpr under app/) exits 0" "$(code "$d")" "0" +has "two-tree finds the knob file under app/" "$out" "knob file app/theme/web/custom-variables.scss" +has "two-tree measures the knobs" "$out" "knobs bound 4 of 4" +has "two-tree finds the built sheet under app/" "$out" "built stylesheet app/deployment/web/theme.compiled.css" +has "two-tree reads \$use-css-variables" "$out" "css-vars mode \$use-css-variables: true" +hasnt "two-tree never says 0 of 0" "$out" "knobs bound 0 of 0" + +d="$(two_tree tt-broken built-broken.css true mpr)" +check "two-tree shipped state still exits 1" "$(code "$d")" "1" +has "two-tree reports 0 of 4 knobs" "$(run "$d")" "0 of 4 framework knobs are bound" + +# No .mpr (an installed copy beside an older _common.sh, or a bare tree): probe app/ anyway. +d="$(two_tree tt-nompr built-fixed.css true "")" +has "two-tree without an .mpr still finds app/" "$(run "$d")" "knobs bound 4 of 4" + +d="$(two_tree tt-bridge built-fixed.css true mpr)" +printf '$brand-primary: var(--brand);\n' > "$d/app/themesource/moc/web/main.scss" +out="$(run "$d")" +check "two-tree dead SCSS bridge exits 1" "$(code "$d")" "1" +has "two-tree names the bridge file under app/" "$out" "app/themesource/moc/web/main.scss:1" + +# ── 10. a knob file it cannot read is a fault, never clean ────────────────────────────────── +d="$(project noknobs built-fixed.css true)" +rm -f "$d/theme/web/custom-variables.scss" +out="$(run "$d")" +check "a missing knob file exits 2" "$(code "$d")" "2" +has "says the knob pass is UNMEASURED" "$out" "UNMEASURED" +has "names the missing file" "$out" "custom-variables.scss not found" +hasnt "does not call it clean" "$out" "check-design-reaches-app: clean" +hasnt "does not print 0 of 0" "$out" "knobs bound 0 of 0" + +d="$(project emptyknobs built-fixed.css true)" +printf ':root {\n// --brand-primary: #264ae5;\n}\n$use-css-variables: true;\n' > "$d/theme/web/custom-variables.scss" +check "a knob file declaring no knobs exits 2" "$(code "$d")" "2" +has "says no knobs were declared" "$(run "$d")" "no framework knobs declared" + +# A knob file given by hand is still honoured, as the third argument and as CUSTOM_VARS. +d="$(project override built-fixed.css true)" +mkdir -p "$d/elsewhere"; mv "$d/theme/web/custom-variables.scss" "$d/elsewhere/cv.scss" +has "third argument names the knob file" "$( cd "$d" && bash "$SUT" design/ds.css deployment/web/theme.compiled.css elsewhere/cv.scss 2>&1 )" "knobs bound 4 of 4" +has "CUSTOM_VARS names the knob file" "$( cd "$d" && CUSTOM_VARS=elsewhere/cv.scss bash "$SUT" 2>&1 )" "knobs bound 4 of 4" + printf '\n%s passed, %s failed\n' "$PASS" "$FAIL" [ "$FAIL" -eq 0 ] || exit 1