baldart 4.70.0 → 4.71.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.71.0] - 2026-06-25
9
+
10
+ **Responsive-awareness in UI review — catch a viewport-scoped edit that regresses the *other* viewport.** When an agent works on one viewport (fix the mobile header, tweak the desktop table), nothing today catches it silently shifting the viewport it was *not* editing. The obvious fix — cloning the i18n ESLint gate into a `responsive-gate` that bans raw `matchMedia`/breakpoint literals — was **refuted** by an adversarial pass: that gate is blind to where responsiveness actually lives (Tailwind `md:` classes, CSS, CSS-in-JS — not `window.matchMedia`), so it is mayo-specific opinion, not a distributable mechanism. The real driver is served by **reactivating machinery that already exists and was inert**: `visual-fidelity-verifier` has had a `responsiveness-break` finding all along, but `/e2e-review` only ever screenshotted **one** viewport (1440px), so it could never fire.
11
+
12
+ **MINOR** — adds review capability; **no new config key** (rides on `features.has_e2e_review` + `features.has_design_system`), no schema-change propagation. The mobile viewport is fixed at 375px (the canonical small-screen audit width). Degrades to no-op on consumers without e2e-review. The per-route cost was **measured** on a real verifier run before shipping default-on: ~one extra verifier call per renderable route (~0.3% of a card run — the 375px screenshot is lighter than the 1440px one, and only renderable routes incur it).
13
+
14
+ ### Changed
15
+
16
+ - **`/e2e-review` captures both viewports + runs a mobile responsive-integrity pass** (`framework/.claude/skills/e2e-review/SKILL.md`). Phase 3 now captures `<route-slug>.png` (desktop 1440×900, **unchanged path** — all existing consumers intact) AND `<route-slug>@mobile.png` (375×812), setting the viewport before navigating so SSR/responsive hooks render for that width. Phase 4 adds a second verifier invocation per route with `viewport_role: "mobile"` (step 2b), and the `skip`-level pre-filter no longer suppresses it (the mobile pass needs no mockup). Findings aggregate with `viewport_role` in the dedup key so a mobile `responsiveness-break` stays distinct and the Phase 5 gate / self-heal report show which viewport broke.
17
+ - **`visual-fidelity-verifier` gains a `viewport_role: "mobile"` responsive-integrity mode** (`framework/.claude/agents/visual-fidelity-verifier.md`). When set, it does NOT diff against the (desktop) mockup — the mobile layout reflows by design — and emits ONLY genuine breakage findings (`responsiveness-break`, `unreachable-action`, `layout-break`), conservatively. Reuses the existing taxonomy + output schema; no new finding codes.
18
+ - **`design-system-protocol.md` adds a "Responsive Scope Discipline" section** — the textual SSOT: (1) a viewport-scoped edit MUST NOT alter the other viewport's branch; (2) a structural/heavy component MUST NOT be swapped across viewports via `display:none`/`visibility`/`hidden`/`@media` CSS hiding (mounts both branches, runs both effect/query sets, ships dead DOM). Stated abstractly — concrete primitive names / breakpoints / any project-local gate live in the consumer's own files, never in the framework. Enforced as a **review check** (the CSS-hidden case is invisible to a JS AST rule), not a lint.
19
+ - **`code-reviewer` enforces it** (rule 9) — structural `display:none` swap = HIGH; viewport-scoped edit that mutates the other branch = MEDIUM. **`design-review.md`** checklist points to the same section. This is the surviving, generalized slice of the responsive work; everything stack-specific (a project's `eslint.responsive.config.mjs`, primitive names) stays a consumer-side project file, never in `.framework/`.
20
+
8
21
  ## [4.70.0] - 2026-06-25
9
22
 
10
23
  **`/ds-edit` — the deliberate-edit twin of `/ds-new`, completing the single-element lifecycle.** `/ds-new` creates (its reuse-first step STOPS if the element exists); there was no symmetric guided entry to *change* an existing canonical element. "Update a component" decomposes into three cases — two were already covered: the **mechanical doc-resync** after a code change is automatic (serializer regenerates the HEAD from source, preserving agentic + prose; the BLOCKING `DS_COMPONENT_STALE` gate enforces it), and **agentic re-curation** is `doc-reviewer`'s domain. The gap was the **deliberate change with a decision** — add a variant/prop, a breaking change, or re-governing a closed-set role.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.70.0
1
+ 4.71.0
@@ -115,6 +115,15 @@ Concrete enforcement contract for the review pass:
115
115
  HEAD is NOT `DS_COMPONENT_STALE` merely for that; empty deterministic HEAD
116
116
  fields (non-TS stack, unresolved prop) are advisory, never blocking. The HEAD
117
117
  is an upgrade target — see `component-manifest-schema.md` § Transition leniency.
118
+ 9. **Responsive Scope Discipline** — per [`design-system-protocol.md`](../../agents/design-system-protocol.md)
119
+ § "Responsive Scope Discipline": (a) a structural/heavy component swapped
120
+ across viewports via `display:none` / `visibility:hidden` / `hidden` / `@media`
121
+ CSS hiding (mounts both branches, runs both effect/query sets, ships dead DOM)
122
+ is **HIGH**; (b) a viewport-scoped edit that also mutates the *other* viewport's
123
+ branch (e.g. a "mobile only" change that shifts the desktop layout) is
124
+ **MEDIUM**. Token-level per-breakpoint restyling of a single element is fine —
125
+ only whole-tree `display:none` swaps trip (a). This is a review judgement, not a
126
+ lint (the CSS-hidden case is invisible to a JS AST rule).
118
127
 
119
128
  When `features.has_design_system: false`, the registry reads are skipped but
120
129
  the review still flags hardcoded values and inconsistencies against
@@ -63,6 +63,7 @@ The orchestrator (`/e2e-review`) invokes you with this JSON payload:
63
63
  "card_id": "FEAT-XXXX",
64
64
  "route": "/dashboard",
65
65
  "viewport": { "width": 1440, "height": 900 },
66
+ "viewport_role": "desktop" | "mobile", // optional, default "desktop"
66
67
  "implementation_screenshot_path": "/abs/path/to/impl.png",
67
68
  "mockup_source": {
68
69
  "level": "figma" | "local" | "compliance-only" | "skip",
@@ -81,7 +82,22 @@ The orchestrator (`/e2e-review`) invokes you with this JSON payload:
81
82
  ```
82
83
 
83
84
  If `mockup_source.level == "skip"`, return immediately with
84
- `{ "status": "skipped", "reason": "no_mockup_available", "findings": [] }`.
85
+ `{ "status": "skipped", "reason": "no_mockup_available", "findings": [] }` —
86
+ **unless** `viewport_role == "mobile"`, in which case run the responsive-integrity
87
+ pass below (it needs no mockup).
88
+
89
+ **`viewport_role: "mobile"` — responsive-integrity pass.** When set, you are
90
+ looking at the **mobile render** (a narrow viewport, typically 375px). Mobile
91
+ mockups are rarely provided, and the mobile layout *correctly* reflows away from
92
+ any desktop mockup — so in this mode you **do NOT diff against a mockup** and you
93
+ do **NOT** emit fidelity findings (`element-order`, `spacing-off-scale`,
94
+ `typography-*`, `color-*` relative to a desktop design). Evaluate the mobile
95
+ screenshot on its own and flag **only genuine viewport breakage**:
96
+ `responsiveness-break` (horizontal scroll past the viewport, content cut off,
97
+ elements overlapping/unreadable), `unreachable-action` (primary CTA off-screen or
98
+ behind a fixed overlay), and `layout-break` (a container that genuinely collapsed,
99
+ not an intended stack). Be conservative: a dense-but-legible layout is **not** a
100
+ break — flag only hard failures a user would hit. Return the same output schema.
85
101
 
86
102
  **`resolved_orphans` carve-out (Functional Traceability Gate).** The mockup is a
87
103
  fidelity target, not a requirements oracle: it may contain interactive chrome that
@@ -273,14 +273,27 @@ capture the result + screenshots per route.
273
273
  - Use `page.getByRole()`, `page.getByText()`, `page.getByTestId()` —
274
274
  never CSS selectors that depend on Tailwind class strings.
275
275
  - Use `expect(locator)` with auto-waiting.
276
- - For each route in the scenario, call:
276
+ - For each route in the scenario, capture BOTH viewports (set the viewport
277
+ BEFORE navigating so SSR / responsive hooks render for that width):
278
+ // Desktop — existing path, consumed by Phase 4 / 4b. DO NOT change it.
279
+ await page.setViewportSize({ width: 1440, height: 900 });
277
280
  await page.goto(route);
278
281
  await page.waitForLoadState("networkidle");
279
282
  await page.screenshot({
280
283
  path: ".baldart/e2e-review/<CARD-ID>/screenshots/<route-slug>.png",
281
284
  fullPage: true
282
285
  });
283
- The screenshot path is consumed by the visual-fidelity-verifier in Phase 4 — do not change it.
286
+ // Mobile additive, enables the responsiveness-break check (Phase 4).
287
+ await page.setViewportSize({ width: 375, height: 812 });
288
+ await page.goto(route);
289
+ await page.waitForLoadState("networkidle");
290
+ await page.screenshot({
291
+ path: ".baldart/e2e-review/<CARD-ID>/screenshots/<route-slug>@mobile.png",
292
+ fullPage: true
293
+ });
294
+ Both screenshot paths are consumed by the visual-fidelity-verifier in
295
+ Phase 4 — do not change them. 375px is the canonical small-screen audit
296
+ width (design-system-protocol Reference Tables); it is fixed, not a config key.
284
297
  - For OTP-driven personas (test_plan.test_credentials.auth_method == "otp"),
285
298
  use `await page.pause()` between phone-entry and code-entry. Document
286
299
  the manual step in a test.step block.
@@ -309,7 +322,8 @@ capture the result + screenshots per route.
309
322
  - `status`: `passed` | `failed` | `flaky` | `skipped`
310
323
  - `duration_ms`
311
324
  - `failure_reason` (when failed, the first assertion error + stack hint)
312
- - `screenshot_paths[]` (one per route in the scenario)
325
+ - `screenshot_paths[]` (per route: the desktop `<route-slug>.png` AND the
326
+ mobile `<route-slug>@mobile.png`)
313
327
  5. **Functional findings**: every `failed` or `flaky` scenario becomes a
314
328
  functional finding in the aggregate report (Phase 5). Severity:
315
329
  - Auth / payments / data-mutation scenario failed → `critical`
@@ -336,7 +350,11 @@ For each `route` in `plan.routes[]`:
336
350
  latency / cost saver — most routes pass pixel diff cleanly.
337
351
  - When `mockup_source.level == "compliance-only"`, skip pre-filter (no
338
352
  image to compare against) and proceed directly to step 2.
339
- - When `mockup_source.level == "skip"`, skip this route entirely.
353
+ - When `mockup_source.level == "skip"`, skip the **desktop fidelity diff**
354
+ (steps 2) for this route — but still run the **mobile responsive pass**
355
+ (step 2b) if a `<route-slug>@mobile.png` screenshot was captured, since it
356
+ needs no mockup. Skip the route entirely only when no screenshot exists
357
+ (route unrenderable).
340
358
  2. **Invoke `visual-fidelity-verifier`** with the input contract documented
341
359
  in [`visual-fidelity-verifier.md`](../../agents/visual-fidelity-verifier.md):
342
360
 
@@ -365,12 +383,46 @@ For each `route` in `plan.routes[]`:
365
383
  verifier from flagging a deliberately-removed orphan affordance as
366
384
  `component-missing`. Omit the field (or pass `[]`) when the inventory has no
367
385
  resolved orphans.
386
+ 2b. **Mobile responsive-integrity pass.** Invoke the verifier a **second** time
387
+ for the same route with the mobile screenshot, so its `responsiveness-break`
388
+ check (which never fires on a desktop-only screenshot) can run. This is what
389
+ catches a viewport-scoped edit that regresses the *other* viewport:
390
+
391
+ ```
392
+ subagent_type: "visual-fidelity-verifier"
393
+ prompt: |
394
+ Compare the implementation against the mockup and return JSON per your
395
+ output schema.
396
+
397
+ Input payload:
398
+ {
399
+ "card_id": "FEAT-XXXX",
400
+ "route": "/dashboard",
401
+ "viewport": { "width": 375, "height": 812 },
402
+ "viewport_role": "mobile",
403
+ "implementation_screenshot_path": ".baldart/e2e-review/FEAT-XXXX/screenshots/<route-slug>@mobile.png",
404
+ "mockup_source": { "level": "skip" },
405
+ "tolerance": "<from config>"
406
+ }
407
+ ```
408
+ `viewport_role: "mobile"` puts the verifier in responsive-integrity mode: it
409
+ does NOT diff against the desktop mockup (the mobile layout reflows by design)
410
+ and emits ONLY genuine breakage findings (`responsiveness-break`,
411
+ `unreachable-action`, `layout-break`). Run this pass on every renderable route
412
+ (a desktop screenshot exists); skip it only when the route is explicitly marked
413
+ desktop-only in the plan. Cost is ~one extra verifier call per route — measured
414
+ marginal (~0.3% of a card run) because the 375px screenshot is lighter than the
415
+ 1440px one and only renderable routes incur it.
368
416
  3. **Parse the agent's JSON response** strictly. If the response is malformed
369
417
  or contains prose outside the JSON, log
370
418
  `verifier_protocol_violation` in `gaps[]` and treat the route's findings
371
419
  as empty (do not invent findings from prose).
372
- 4. **Aggregate** visual findings across all routes into a single
373
- `visual_findings[]` array, deduplicating by `(route, category, region)`.
420
+ 4. **Aggregate** visual findings from BOTH passes across all routes into a single
421
+ `visual_findings[]` array, deduplicating by `(route, viewport_role, category,
422
+ region)` — `viewport_role` keeps a mobile `responsiveness-break` distinct from
423
+ any desktop finding on the same route. Tag each mobile-pass finding with
424
+ `viewport_role: "mobile"` so the Phase 5 gate and self-heal report show which
425
+ viewport broke.
374
426
 
375
427
  ---
376
428
 
@@ -73,6 +73,9 @@ recommend `/design-system-init`.
73
73
  - [ ] No horizontal scroll on mobile
74
74
  - [ ] Touch targets minimum 44x44px
75
75
  - [ ] Content hierarchy clear
76
+ - [ ] Responsive Scope Discipline (design-system-protocol.md § "Responsive Scope
77
+ Discipline"): no structural component swapped via `display:none`; a
78
+ viewport-scoped change does not regress the other viewport
76
79
 
77
80
  ### Interaction Design
78
81
 
@@ -545,6 +545,37 @@ implemented (or is about to be) without a traceable function. HIGH severity:
545
545
  it ships dead code and erodes the minimalism contract. The fix is always one of
546
546
  the three outcomes above, never "leave it, it matches the mockup".
547
547
 
548
+ ## Responsive Scope Discipline
549
+
550
+ A UI surface usually renders across viewports (mobile / tablet / desktop). Two
551
+ rules keep responsive work from regressing the viewport you are *not* editing —
552
+ the framework states them abstractly; the concrete primitive names, breakpoint
553
+ values and any project-specific gate live in the consumer's own files (CLAUDE.md,
554
+ overlays, a project-local linter), never here.
555
+
556
+ 1. **Viewport-scoped edits stay scoped.** When a change targets one viewport
557
+ (e.g. "fix the mobile header"), it MUST NOT alter the other viewport's branch.
558
+ Editing the mobile path and silently shifting the desktop layout — or vice
559
+ versa — is a regression even when the targeted viewport looks correct. Verify
560
+ both viewports before declaring the task done (`/e2e-review` renders at 1440px
561
+ and 375px and runs the `responsiveness-break` check at each).
562
+
563
+ 2. **No structural component swapped via `display:none`.** A DOM-structural or
564
+ heavy component (one that mounts a non-trivial subtree, fetches data, or has
565
+ expensive children) MUST NOT be shown/hidden across viewports via
566
+ `display:none` / `visibility:hidden` / a `hidden` attribute / `@media` CSS
567
+ hiding. Mounting both branches and hiding one ships dead DOM, runs *both*
568
+ components' effects and queries, and doubles the work on every render. Render
569
+ only the active branch (a viewport hook / conditional render) instead.
570
+ Token-level responsive styling — a single element restyled per breakpoint
571
+ (padding, font-size, direction) — is fine; it is *swapping whole component
572
+ trees* via `display:none` that this rule rejects.
573
+
574
+ Enforcement is a **review check** (`code-reviewer`, `/design-review`), not a lint:
575
+ the most damaging case (a CSS-hidden heavy subtree) is invisible to a JS AST rule.
576
+ Rule 2 violations are HIGH; a viewport-scoped edit that mutates the other branch
577
+ (rule 1) is MEDIUM.
578
+
548
579
  ## Post-Intervention Coherence Check (BLOCKING completion gate)
549
580
 
550
581
  The BLOCKING pre-work above enforces *reads* before any UI change. This
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.70.0",
3
+ "version": "4.71.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"