---
phase: 20.1.1.1
plan: "06"
type: execute
wave: 3
depends_on: ["20.1.1.1-01", "20.1.1.1-02", "20.1.1.1-03", "20.1.1.1-04", "20.1.1.1-05"]
files_modified:
  - .planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-VALIDATION.md
autonomous: false
requirements: [D-13, D-14]
gap_closure: true
must_haves:
  truths:
    - "Each of D-01 through D-12 is exercised live using BUGSWEEPER endpoints AND captured with a screen-timelapse screenshot."
    - "All mutations made during the session are reverted before the session ends (D-14)."
    - "A VALIDATION.md artifact records endpoint output and screenshot paths per decision."
  artifacts:
    - path: ".planning/phases/20.1.1.1-.../20.1.1.1-VALIDATION.md"
      provides: "Per-decision evidence log: BUGSWEEPER output + timelapse screenshot references"
      contains: "D-01"
  key_links:
    - from: "validation task"
      to: "BUGSWEEPER http://127.0.0.1:9876 endpoints"
      via: "cargo run --features bugsweeper + curl"
      pattern: "/api/ui"
    - from: "validation task"
      to: "screen-timelapse MCP capture"
      via: "mcp__screen-timelapse__* tool calls"
      pattern: "screen-timelapse"
---

<objective>
Validate all twelve fixes (D-01..D-12) from this phase against a live build. Use BUGSWEEPER to exercise state changes programmatically (tab-switch, recipient selection, popover open, note post, property reads) and use the `screen-timelapse` MCP to capture visual evidence (sidebar coexistence, colored pill, hidden+aligned add button, growing note rows). Write a VALIDATION.md listing each decision with its endpoint output snippet and screenshot path. Revert any state mutations before ending the session (per CLAUDE.md BUGSWEEPER session rule, D-14).

Purpose: Prove the fixes work end-to-end in the running app, not just in grep. This is the gate between "planned + built" and "phase closed".

Output: `.planning/phases/20.1.1.1-.../20.1.1.1-VALIDATION.md` listing D-01..D-12 with evidence.
</objective>

<execution_context>
@$HOME/.ccs/instances/bigscreen/get-shit-done/workflows/execute-plan.md
@$HOME/.ccs/instances/bigscreen/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-CONTEXT.md
@.planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-PATTERNS.md
@crates/bugsweeper/GUIDE.md
@CLAUDE.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Build + launch app with BUGSWEEPER; capture baseline state</name>
  <read_first>
    - crates/bugsweeper/GUIDE.md (full guide — building, running, poll health, property list, callback list, session discipline section)
    - .planning/phases/20.1.1.1-.../20.1.1.1-CONTEXT.md (D-13, D-14 validation requirements)
  </read_first>
  <action>
    1) Build the app with BUGSWEEPER enabled:
    ```bash
    cargo build --features bugsweeper 2>&1 | tail -5
    ```

    2) Launch in the background:
    ```bash
    ./target/debug/app.exe &
    ```

    3) Poll health (do NOT fixed-sleep):
    ```bash
    for i in $(seq 1 60); do curl -s http://127.0.0.1:9876/api/debug/health && break; sleep 0.5; done
    ```

    4) Snapshot baseline state for later revert:
    ```bash
    curl -s http://127.0.0.1:9876/api/ui/property/active-mode-index
    curl -s http://127.0.0.1:9876/api/ui/property/search-text
    ```
    Save values. If active-mode-index is already 2, note that for step-5 cleanup.

    5) Verify the cards endpoint returns at least one card for downstream tests:
    ```bash
    curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; print(len(json.load(sys.stdin)))"
    ```
    If it returns 0, the app may still be syncing — poll `/api/ui/property/connection-status` until it reaches 2 (connected) then re-check card count.

    6) Start the screen-timelapse capture via the MCP tool (one call at session start covering the whole validation run).

    Do NOT proceed to Task 2 until health returns ok AND at least one card is loaded.
  </action>
  <acceptance_criteria>
    - `curl -s http://127.0.0.1:9876/api/debug/health` returns `{"ok":true}`.
    - `curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; print(len(json.load(sys.stdin)))"` prints a number greater than 0.
    - screen-timelapse capture session is active.
    - Baseline `active-mode-index` and `search-text` values recorded (note them in scratch notes for Task 5 revert).
  </acceptance_criteria>
  <verify>
    <automated>curl -s http://127.0.0.1:9876/api/debug/health</automated>
  </verify>
  <done>App is running with BUGSWEEPER, health check passes, cards loaded, timelapse recording, baseline state snapshotted.</done>
</task>

<task type="auto">
  <name>Task 2: Validate D-01 through D-03 (sidebar coexistence + tab-restore + name-navigate)</name>
  <read_first>
    - crates/bugsweeper/GUIDE.md (callback list: tab-clicked, tile-clicked)
  </read_first>
  <action>
    For each sub-check, invoke the endpoint(s), capture a timelapse screenshot, and record evidence.

    **D-01 (sidebar coexists with grid):**
    ```bash
    # Switch to Recipients tab (index 2)
    curl -s -X POST -d '{"name":"tab-clicked","args":[2]}' http://127.0.0.1:9876/api/ui/callback

    # Click the first recipient tile — use tile-clicked with the tile name (read first tile from recipient tiles)
    # If recipient-tiles endpoint doesn't exist in BUGSWEEPER, use a cards endpoint to pick a known recipient id
    FIRST_RID=$(curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; cs=json.load(sys.stdin); print(next((c['recipient-id'] for c in cs if c.get('recipient-id')), ''))")
    curl -s -X POST -d "{\"name\":\"tile-clicked\",\"args\":[\"$FIRST_RID\"]}" http://127.0.0.1:9876/api/ui/callback
    ```
    Capture a screenshot via screen-timelapse. Evidence: BOTH the recipient grid AND the sidebar panel must be visible in the screenshot. Record screenshot path.

    **D-02 (tab-restore re-shows sidebar):**
    ```bash
    # Leave Recipients tab (go to tab 0)
    curl -s -X POST -d '{"name":"tab-clicked","args":[0]}' http://127.0.0.1:9876/api/ui/callback
    # Return to Recipients tab
    curl -s -X POST -d '{"name":"tab-clicked","args":[2]}' http://127.0.0.1:9876/api/ui/callback
    ```
    Capture screenshot. Evidence: sidebar is visible again WITHOUT re-clicking a tile. Record path.

    **D-03 (card-name-navigate with coexistence):**
    ```bash
    # Navigate to tab 0 (main dashboard)
    curl -s -X POST -d '{"name":"tab-clicked","args":[0]}' http://127.0.0.1:9876/api/ui/callback
    # If there is a card-name-navigate callback exposed, invoke it. Otherwise use click-on-element at the card-name coordinates.
    # Registry check:
    curl -s http://127.0.0.1:9876/api/ui/registry | python -c "import sys,json; r=json.load(sys.stdin); print([c['name'] for c in r['callbacks'] if 'name-navigate' in c['name'] or 'card-name' in c['name']])"
    # If card-name-navigate exists, invoke:
    curl -s -X POST -d "{\"name\":\"card-name-navigate\",\"args\":[\"$FIRST_RID\"]}" http://127.0.0.1:9876/api/ui/callback
    ```
    If card-name-navigate is not a registered callback (it is an internal Slint callback triggered by card click), this step requires a simulated click via `/api/ui/click` at the recipient-name area. If that is not practical, record the limitation in VALIDATION.md and mark D-03 with a verification fallback using the static trace through main.rs (on_card_name_navigate at lines ~5981-5999) plus a D-02+D-01 combined visual (since D-03 is D-01 + D-02 mechanically).

    Capture screenshot after navigation. Evidence: active-mode-index is 2 AND sidebar is visible AND recipient grid is visible. Record path + property readback:
    ```bash
    curl -s http://127.0.0.1:9876/api/ui/property/active-mode-index
    ```

    Write a temporary `.planning/phases/20.1.1.1-.../scratch/d01-d03-evidence.md` or accumulate in-memory for Task 6 final writeup.
  </action>
  <acceptance_criteria>
    - D-01 screenshot shows both grid and sidebar — recorded.
    - D-02 screenshot shows sidebar re-appearing after tab round-trip — recorded.
    - D-03 screenshot shows tab 2 active with sidebar + grid, OR documented fallback — recorded.
    - `active-mode-index` property reads as 2 after the name-navigate invocation (or documented fallback).
  </acceptance_criteria>
  <verify>
    <automated>curl -s http://127.0.0.1:9876/api/ui/property/active-mode-index</automated>
  </verify>
  <done>D-01, D-02, D-03 each have a BUGSWEEPER-endpoint-confirmed state change and a timelapse screenshot path.</done>
</task>

<task type="auto">
  <name>Task 3: Validate D-04 through D-07 (notes popover behavior)</name>
  <read_first>
    - crates/bugsweeper/GUIDE.md (property `card-count`, callback `card-summary(int)`, callback `card-save-note(int, string)`)
  </read_first>
  <action>
    **D-04 (notes rows grow with content):**
    Use card-save-note to write a long multi-line note on card index 0:
    ```bash
    LONG_NOTE="This is a deliberately long test note used for validating D-04 content-driven row heights. It must wrap across multiple lines so the row grows. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    curl -s -X POST -d "{\"name\":\"card-save-note\",\"args\":[0,\"$LONG_NOTE\"]}" http://127.0.0.1:9876/api/ui/callback
    # Open the notes popover for card 0
    curl -s -X POST -d '{"name":"card-summary","args":[0]}' http://127.0.0.1:9876/api/ui/callback
    ```
    NOTE: card-summary may open the old summary; the relevant callback for the notes-popup is `on-notes-popover-opened` — check registry. If not registered as a POST-able callback, trigger it via `/api/ui/click` on the info-button coordinates. If not possible, take the screenshot after save-note and record that the row visibly wraps (no content bleed).

    Capture screenshot. Evidence: the long note's row is visually taller than a short note's row, with wrapped text fully visible.

    **D-05 (popover stays open after post):**
    While the popover is open, post another note:
    ```bash
    curl -s -X POST -d '{"name":"card-save-note","args":[0,"Post-open test note"]}' http://127.0.0.1:9876/api/ui/callback
    ```
    Capture screenshot immediately after. Evidence: popover is still visible (not closed); the new note is at the top of the list.

    **D-06 (immediate SQLite render):**
    Close the popover (click outside or press Esc — via `/api/ui/click` at empty coords if needed). Then re-open it:
    ```bash
    curl -s -X POST -d '{"name":"card-summary","args":[0]}' http://127.0.0.1:9876/api/ui/callback
    ```
    Capture screenshot within the timelapse. Evidence: notes appear instantly on open (the first frame of the popover shows the note list fully populated — no loading spinner or empty state flicker). A single timelapse frame showing notes visible immediately after open is sufficient.

    **D-07 (dedup: exactly one row per posted note):**
    Read the card's notes via BUGSWEEPER:
    ```bash
    curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; cs=json.load(sys.stdin); c=cs[0]; print(json.dumps(c.get('notes', []), indent=2))"
    ```
    Wait ~5 seconds for the background GH flusher to run, then read again. Evidence: the notes count after flush equals the notes count before flush (no duplication from GH reconciliation). Record both readings.

    Also verify author_display is the user's GH handle, NOT "You":
    ```bash
    curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; cs=json.load(sys.stdin); c=cs[0]; [print(n.get('author-display')) for n in c.get('notes',[])]"
    ```
    Evidence: the most recent posted note's author-display is a GH handle (e.g. "brandon"), not "You". If gh api user was not available at LiveClient init, document the degraded-mode "You" outcome per Plan 04's design.

    Note: if the GH comment flusher requires network and test runs offline, record that D-07's reconciliation half is a "no dup count change within 5 seconds" test only; the full flush cycle happens later.
  </action>
  <acceptance_criteria>
    - D-04: screenshot captures a long wrapped note row that is visibly taller than a short-note row.
    - D-05: screenshot post-note-save shows popover still open.
    - D-06: screenshot captures notes visible on popover re-open (no empty flicker).
    - D-07: BUGSWEEPER cards JSON shows note count stable across pre- and post-flush reads; author-display is a GH handle (or degraded-mode "You" documented).
  </acceptance_criteria>
  <verify>
    <automated>curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; cs=json.load(sys.stdin); print('cards:', len(cs), 'first card notes:', len(cs[0].get('notes',[])) if cs else 0)"</automated>
  </verify>
  <done>D-04, D-05, D-06, D-07 each have a BUGSWEEPER state readback AND a timelapse screenshot path.</done>
</task>

<task type="auto">
  <name>Task 4: Validate D-08 through D-12 (sidebar structure + card + button)</name>
  <read_first>
    - crates/bugsweeper/GUIDE.md (recipient-tile callbacks, element geometry endpoints)
  </read_first>
  <action>
    **D-08 (no X close button):**
    Ensure tab 2 is active with sidebar open (from Task 2). Capture screenshot of the sidebar. Evidence: NO X glyph (U+2715 "✕") appears in the top-right of the sidebar. Visual only — no endpoint can confirm absence directly (but `curl -s http://127.0.0.1:9876/api/ui/registry | grep close-clicked` returns the callback still declared, which is expected per Plan 02 Task 1).

    **D-09 (Purpose is a colored pill):**
    In the same screenshot from D-08, evidence: directly under the recipient name there is a small colored pill (not a labeled "Purpose" field). The pill background color matches the recipient's avatar ring color (the `purpose-color`). Record the color hex by reading the card's purpose-color:
    ```bash
    curl -s http://127.0.0.1:9876/api/ui/cards | python -c "import sys,json; cs=json.load(sys.stdin); print(cs[0].get('purpose-color-hex', 'n/a'))"
    ```
    (If purpose-color-hex is not directly serialized, use the recipient's SQLite row via the query endpoint per BUGSWEEPER guide — `/api/db/query` with `SELECT purpose_color FROM recipients WHERE recipient_key = ...` if supported.)

    **D-10 (field order: Discord, Shopify email, Shopify customer at top):**
    Capture a screenshot of the full sidebar (may need to scroll within it or resize timelapse viewport). Evidence: below the pill, the FIRST three content blocks are Discord username, Shopify email, Shopify customer link — BEFORE Rx OD/OS.

    **D-11 (add button hover-only):**
    Return to tab 0 (main dashboard). Capture a baseline screenshot WITHOUT hovering any card. Evidence: no `+` button visible on any card's product row.

    Then simulate hover — since BUGSWEEPER's `/api/ui/click` simulates click not hover, and true hover is hard to simulate via endpoint, the best available test is:
    - Read the Slint model state that the `+` button is conditionally mounted:
      ```bash
      # No direct property for this; use the registry to confirm the visibility binding exists in code:
      grep -c "card-hover-zone.has-hover" crates/app/ui/card.slint
      ```
    - Visual evidence: non-hover screenshot shows no `+` buttons; this proves the default-hidden path.

    For the hover-visible case, if the user (human) is available during validation, have them hover a card. Alternatively, note the limitation: hover cannot be simulated headlessly. Mark D-11 as "visually confirmed non-hover state; hover state requires human UAT".

    **D-12 (+ button vertically aligned):**
    Piggy-back on D-11: if a hover screenshot is captured (human-assisted), confirm the `+` button center aligns horizontally with the product-square image centers. Record.

    If human is not available, fall back to a code-structure grep:
    ```bash
    grep -A 5 "if card-hover-zone.has-hover" crates/app/ui/card.slint | grep -E "y: 0px|y: 18px|height: 80px"
    ```
    Evidence: the outer Rectangle uses `height: 80px` matching item squares, inner button uses `y: 18px` for centering.
  </action>
  <acceptance_criteria>
    - D-08: screenshot of sidebar shows no X glyph in top-right.
    - D-09: screenshot shows colored pill under name; purpose color retrievable via cards JSON or SQLite query.
    - D-10: screenshot shows Discord → email → Shopify customer blocks before Rx.
    - D-11: non-hover baseline screenshot shows no `+` buttons; hover case documented (human-assisted or grep-confirmed).
    - D-12: alignment confirmed visually (hover screenshot) or by structural grep of `y: 18px` inside `card-hover-zone.has-hover` block.
  </acceptance_criteria>
  <verify>
    <automated>grep -c "card-hover-zone.has-hover" crates/app/ui/card.slint</automated>
  </verify>
  <done>D-08..D-12 each have evidence (screenshot + endpoint/grep); limitations (hover simulation) documented.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 5: Human UAT assist for hover-dependent checks</name>
  <action>Pause execution and present the how-to-verify checklist below to the human user. Wait for their pass/fail report. Do not resume until they respond with "approved" or a failure description.</action>
  <verify><automated>echo "human checkpoint — awaiting user response"</automated></verify>
  <done>Human user has responded with approved or a list of failures. Failures must be logged in VALIDATION.md Task 6.</done>
  <what-built>
    All code changes from plans 01-05 are live in the running app. Non-hover visual state of the `+` button and sidebar coexistence layout have been captured via BUGSWEEPER + screen-timelapse. D-11/D-12 hover behavior requires live mouse hover that cannot be simulated via HTTP endpoints.
  </what-built>
  <how-to-verify>
    1. Keep the current running app window visible.
    2. Navigate to tab 0 (main dashboard). Confirm the `+` button is NOT visible on any card by default.
    3. Hover your mouse over any card. Confirm the `+` button appears only while hovering, and that it is visually aligned with the product squares (same top-edge and same apparent vertical center as the square images).
    4. Move the mouse off the card. Confirm the button disappears.
    5. Open a card's notes popover by clicking the (i) button. Confirm the popover opens.
    6. Type a short note in the composer. Press Ctrl+Enter. Confirm the popover STAYS OPEN and the note appears at the top. (D-05)
    7. Hover a note row. Confirm the absolute date appears on hover. (Parent phase D-05 — regression check.)
    8. Press Esc. Confirm the popover closes.
    9. Go to tab 2 (Recipients). Click a tile. Confirm sidebar opens alongside the grid. Leave to tab 0, return to tab 2. Confirm the sidebar is still visible.
    10. Click the colored pill under the name. Confirm inline-edit TextInput appears in the pill shape.

    Report pass/fail for each numbered check.
  </how-to-verify>
  <resume-signal>Type "approved" or describe any failed check.</resume-signal>
</task>

<task type="auto">
  <name>Task 6: Revert mutations + write VALIDATION.md + shutdown (D-13, D-14)</name>
  <read_first>
    - crates/bugsweeper/GUIDE.md (Session Discipline section)
    - baseline values recorded in Task 1 (scratch notes)
  </read_first>
  <action>
    1) **Revert mutations (D-14):**
    - Restore baseline `active-mode-index` (from Task 1 snapshot):
      ```bash
      curl -s -X PUT -d "{\"value\":$ORIGINAL_MODE}" http://127.0.0.1:9876/api/ui/property/active-mode-index
      ```
    - Restore `search-text`:
      ```bash
      curl -s -X PUT -d '{"value":""}' http://127.0.0.1:9876/api/ui/property/search-text
      ```
    - The test notes added in Task 3 (via `card-save-note`) DO persist to SQLite. Per CLAUDE.md BUGSWEEPER session rule, these must be reverted. Options:
      - If BUGSWEEPER exposes a delete-note endpoint: use it.
      - Otherwise: flag this as a BUGSWEEPER gap. Per CLAUDE.md self-healing rule, if the endpoint is missing, run `/gsd:quick` to add a `DELETE /api/ui/notes/{card_id}/{note_id}` or equivalent endpoint, then resume.
      - If `/gsd:quick` is out of scope for this task: document the residue explicitly in VALIDATION.md ("Test notes on card 0 added during D-04/D-05/D-07 remain in SQLite; manual cleanup required via direct SQL DELETE or feature-gate quick fix follow-up"). This is an honest accounting — do not silently leave the residue undocumented.

    2) **Stop the screen-timelapse capture** via the appropriate MCP tool call. Capture the output directory / file paths.

    3) **Write VALIDATION.md:**
    Create `.planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-VALIDATION.md`. Use the following structure:

    ```markdown
    ---
    phase: 20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n
    validated: <ISO 8601 timestamp>
    status: passed | partial | failed
    decisions_validated: N/12
    human_verification: approved | pending | failed
    mutations_reverted: yes | partial (document residue)
    ---

    # Phase 20.1.1.1 Validation Report

    ## Summary
    <1-2 paragraphs: what was validated, overall outcome, any residue>

    ## Per-Decision Evidence

    ### D-01: Sidebar coexists with recipient grid
    - **Method:** BUGSWEEPER tab-clicked(2) + tile-clicked + screenshot
    - **Endpoint output:** `<trimmed JSON or status>`
    - **Screenshot:** `<path to timelapse frame or saved image>`
    - **Result:** PASS | FAIL
    - **Notes:** <any caveats>

    ### D-02: ... (repeat for D-02..D-12)

    ## Human UAT Items (Task 5)
    <List the 10 numbered checks with pass/fail from human.>

    ## Residue / Follow-Ups
    <Any test notes left in SQLite, any BUGSWEEPER gaps filed for /gsd:quick, any hover checks that could not be simulated.>

    ## Build Health
    - `cargo check --features bugsweeper` exit code: <N>
    - `cargo test --workspace --lib` result: <pass|fail + count>
    ```

    Populate with the actual evidence gathered in Tasks 2-5.

    4) **Shutdown:**
    - Stop the app:
      ```bash
      # Find and kill app.exe process
      # On Windows via Git Bash:
      taskkill //IM app.exe //F 2>/dev/null || true
      ```
    - Verify port 9876 is released:
      ```bash
      curl -s --max-time 2 http://127.0.0.1:9876/api/debug/health || echo "server down (expected)"
      ```

    5) Final check: read back `.planning/phases/20.1.1.1-.../20.1.1.1-VALIDATION.md` to confirm it exists and contains all twelve D-entries.
  </action>
  <acceptance_criteria>
    - `.planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-VALIDATION.md` exists.
    - Grep VALIDATION.md for `### D-01`, `### D-02`, ..., `### D-12` — each returns exactly 1 match (12 total decision sections).
    - Grep VALIDATION.md for `Screenshot:` — returns at least 10 matches (one per decision that has a screenshot).
    - Grep VALIDATION.md for `Result: PASS` OR `Result: FAIL` — returns exactly 12 matches.
    - VALIDATION.md has a `mutations_reverted:` frontmatter entry.
    - `curl -s --max-time 2 http://127.0.0.1:9876/api/debug/health` fails or times out (app stopped).
  </acceptance_criteria>
  <verify>
    <automated>test -f .planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-VALIDATION.md && grep -c "^### D-" .planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-VALIDATION.md</automated>
  </verify>
  <done>VALIDATION.md written with per-decision evidence; mutations reverted (or residue documented); app shutdown; timelapse capture ended.</done>
</task>

</tasks>

<verification>
- 20.1.1.1-VALIDATION.md exists in phase directory with 12 decision sections, screenshots referenced, PASS/FAIL recorded.
- Mutations reverted; residue (if any) explicitly documented.
- Human UAT checkpoint approved.
</verification>

<success_criteria>
- D-01..D-12 each have at minimum: (a) a BUGSWEEPER endpoint output OR a live screenshot, and (b) a PASS/FAIL verdict.
- Session discipline honored per CLAUDE.md: mutations reverted, residue documented.
- D-13 (validation mandate) and D-14 (revert mandate) both met.
</success_criteria>

<output>
After completion, create `.planning/phases/20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n/20.1.1.1-06-SUMMARY.md` that references the VALIDATION.md artifact.
</output>
