From 4bce86650d52ca7c264fe8f84f53fa91b02c2e68 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 2 Jul 2026 23:51:01 -0400 Subject: [PATCH] feat(#1867): add UI Considerations section to UI-SPEC template (WIRE-02) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Record resolved UI-state considerations in the UI-SPEC, backward-compatibly (Phase 2, WIRE-02). templates/UI-SPEC.md gains a '## UI Considerations' section (analog of SPEC '## Edge Coverage') after '## Copywriting Contract' — a | Category | Element(s) | Status | Resolution / Reason | table with covered/backstop/unresolved rows in the locked probe-core projectTruths format the shipped plan-phase lift (plan-phase.md:921) reads. Empty/error COPY stays in Copywriting; this section covers shape-rooted STATE and references those rows (de-dup). Tests: docs-fixtures parsed-heading assertion (UI Considerations present + distinct from Copywriting Contract; allow-test-rule, parsed structure); typed backward-compat (projectTruths(undefined/[])===[], old UI-SPEC still plans — Hyrum), format-match, and idempotency (proposeElements determinism). The template-structure test was the RED driver. Install-parity cascade (INVENTORY-MANIFEST + 16 golden fixtures + agent-size-baseline) deferred to Phase 3 SHIP-01, as planned. Claude-Session: https://claude.ai/code/session_01BKt4hgNZwXSeJYJtYAQUSS --- gsd-core/templates/UI-SPEC.md | 25 +++++++++++++++++++ ...consideration-probe-docs-fixtures.test.cjs | 11 ++++++++ tests/ui-consideration-probe.test.cjs | 22 ++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/gsd-core/templates/UI-SPEC.md b/gsd-core/templates/UI-SPEC.md index be2c6e142..4d63aafc9 100644 --- a/gsd-core/templates/UI-SPEC.md +++ b/gsd-core/templates/UI-SPEC.md @@ -79,6 +79,31 @@ Accent reserved for: {explicit list — never "all interactive elements"} --- +## UI Considerations + +> Populated by the ui-phase UI-consideration probe (Step 9.5) and lifted by plan-phase +> (plan-phase.md:921) via the identical rule as SPEC `## Edge Coverage`. Shape-rooted UI *state* +> coverage (empty / loading / error / populated / partial / overflow / zero-one-many / long-text). +> Empty-state and error-state COPY live in `## Copywriting Contract` above — this section covers +> state coverage and REFERENCES those rows rather than restating the copy (de-dup). + +Applicable state considerations resolved: {N covered, M backstop, K unresolved — or "none applicable"} + +| Category | Element(s) | Status | Resolution / Reason | +|----------|------------|--------|---------------------| +| {empty} | {list-collection} | ✅ covered | {concrete truth string — e.g. "Empty results render the documented 'No results' copy"} | +| {long-text} | {static-content} | 🧪 backstop | {held-out/visual UI-state test — lifts as `{ statement, verification: backstop }`} | +| {overflow} | {list-collection} | ⚠ unresolved | {planner treats as assumption} | + + + +--- + ## Registry Safety | Registry | Blocks Used | Safety Gate | diff --git a/tests/ui-consideration-probe-docs-fixtures.test.cjs b/tests/ui-consideration-probe-docs-fixtures.test.cjs index 32448662a..c93414681 100644 --- a/tests/ui-consideration-probe-docs-fixtures.test.cjs +++ b/tests/ui-consideration-probe-docs-fixtures.test.cjs @@ -15,6 +15,7 @@ const path = require('node:path'); const uc = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'ui-consideration-probe.cjs')); const docPath = path.join(__dirname, '..', 'gsd-core', 'references', 'ui-consideration-probe.md'); const domainPath = path.join(__dirname, '..', 'gsd-core', 'references', 'domain-probes.md'); +const templatePath = path.join(__dirname, '..', 'gsd-core', 'templates', 'UI-SPEC.md'); // Extract the first-column ids from the `## Taxonomy` markdown table (skips the `id` header // row and the `|----|` separator; an id is a lowercase-hyphen token). @@ -55,3 +56,13 @@ describe('ui-consideration-probe doc/code parity (ADPT-02)', () => { assert.match(md, /domain-probes\.md/); }); }); + +describe('UI-SPEC template `## UI Considerations` section (WIRE-02 SC3 de-dup)', () => { + // PARSED `##` headings only (never a raw copy substring) — a reformat that preserves the data + // does not fail; a missing/merged section does. Reuses the domainTopics() heading parser. + test('template ## headings include BOTH `UI Considerations` and `Copywriting Contract` as distinct sections', () => { + const headings = domainTopics(fs.readFileSync(templatePath, 'utf8')); + assert.ok(headings.includes('ui considerations'), 'template must gain a ## UI Considerations section'); + assert.ok(headings.includes('copywriting contract'), 'template must retain the distinct ## Copywriting Contract section (de-dup, not a rename)'); + }); +}); diff --git a/tests/ui-consideration-probe.test.cjs b/tests/ui-consideration-probe.test.cjs index b48d045cf..6aa06f9b9 100644 --- a/tests/ui-consideration-probe.test.cjs +++ b/tests/ui-consideration-probe.test.cjs @@ -282,3 +282,25 @@ describe('ui-consideration-probe: partial-cue recall gap (confirm is load-bearin assert.ok(cSet.size > hSet.size, 'the confirmed union must strictly exceed the heuristic set — proving the confirm step recovers missed coverage'); }); }); + +// ══ WIRE-02 (Phase 2, #1867) — the UI-SPEC section round-trips the shipped lift, backward-compat, +// idempotency. Typed returns only (this file carries no allow-test-rule header). The `## UI +// Considerations` section format is LOCKED by the shipped plan-phase lift (plan-phase.md:921) + +// probe-core `projectTruths`; these guards pin that the template documents the SAME format. ═════ +describe('ui-consideration-probe WIRE-02: backward-compat + format-match + idempotency (SC4)', () => { + test('projectTruths(undefined) and projectTruths([]) both === [] — an old UI-SPEC with no section lifts nothing, never throws (Hyrum SC4)', () => { + assert.deepEqual(core.projectTruths(undefined), []); + assert.deepEqual(core.projectTruths([]), []); + }); + test('a mixed covered/backstop/unresolved considerations array projects to the exact plan-phase-lift shape, order preserved (format-match SC3)', () => { + const input = ['Empty state renders the documented "No results" copy.', { statement: 'Overflowing long labels truncate with an ellipsis.', verification: 'backstop' }, 'Loading shows a skeleton for the results table.']; + const out = core.projectTruths(input); + assert.equal(out[0], input[0]); // covered → bare string + assert.deepEqual(out[1], { statement: input[1].statement, verification: 'backstop' }); // backstop → flat scalar + assert.equal(out[2], input[2]); // order preserved + }); + test('proposeElements is deterministic — re-running the probe rewrites byte-stable rows, never duplicated (idempotency SC4)', () => { + const els = [{ id: 'C1', text: 'A table listing all rows of results' }, { id: 'Z', text: 'xyzzy plugh' }]; + assert.deepEqual(uc.proposeElements(els), uc.proposeElements(els)); + }); +});