Skip to content

docs(harness): Shift+click select + copy for read-only textLayout bodies (plan #753) - #775

Merged
btipling merged 3 commits into
mainfrom
plan/shift-click-select-textlayout
Aug 22, 2026
Merged

docs(harness): Shift+click select + copy for read-only textLayout bodies (plan #753)#775
btipling merged 3 commits into
mainfrom
plan/shift-click-select-textlayout

Conversation

@btipling

Copy link
Copy Markdown
Owner

Plan #753 — Shift+click select + copy for read-only in-canvas text

Implements #753 (phase 2 child of #752; source #749).

Close: Closes #753 · Refs: #749 (source), #752 (parent).

Verification-first outcome (recorded per the plan's design section)

The plan is verification-first: parent #752 merged (PR #762) already installs a
sel_move.shift_click extension branch on the shared TextLayoutWidget
(applyInvincibleShiftClickPatch in native/harness/build.zig), and read-only
transcript bodies paint through that same widget. This PR documents the
verified finding: the parent's shared branch already delivers read-only
anchor + Shift+click extend + Ctrl/Cmd+C copy — no net-new code, no second
shift_click seam.
Grounded (live code, main @ 8f23c1a):

  • Read-only bodies use the shared widgetdvui.textLayout() creates a
    TextLayoutWidget (dvui pin #4f810ef, dvui.zig:3363) and calls
    ret.processEvents() unconditionally, so read-only textLayouts (rich-MD
    bodies via native/harness/src/rich/paint_text.zig, fenced code, thinking /
    preview / skill rows, tool-run detail) receive mouse/key events.
  • Anchor + Shift+click extend already fire — the patched shared widget
    (TextLayoutWidget.zig press handler) turns a Shift+pointer press into
    .shift_click, resolved in selMovePre to selection.moveCursor(hit, true)
    (the stock extend path), zeroing click_num so a follow-on plain click stays
    a caret move + clear. .right/.middle are untouched (no plan: copy link URL via right-click / Alt+Click #647 regression);
    highlight is the existing TEAL text_select fill (palette.zig:139).
  • Plain click clears / sets a new anchor — non-shift press sets .mouse,
    resolved during addText to moveCursor(hit, false) (clears the range).
  • Ctrl/Cmd+C already copies an active read-only range — Ctrl/Cmd+C stays in
    the harness keymap reserved deny-list (keymap.zig .c/ctrl_or_cmd),
    so the harness never marks it handled and it falls through to the focused
    widget's matchBind("copy") (TextLayoutWidget.processEvent) → copy()
    copy_selclipboardTextSet (navigator.clipboard.writeText). A read-only
    textLayout receives focus via the .focus mouse action on hover/click
    (processEventfocusWidget), so the key routes to it. An empty
    range copies nothing (len-0 no-op), which satisfies "only when a range is
    active" with no gate needed. No harness keymap entry is added.
  • No duplicate seamapplyInvincibleShiftClickPatch is idempotent
    ("invincible: dedicated shift-click" marker) and fail-closed (every needle
    must match exactly once or the build panics), so a second shift_click branch
    is a build-time error, not a silent conflict.

What this PR ships

  • docs/harness-limits.md — living-docs hardening (per the plan's Living
    docs plan): a "Read-only body text Shift+click" row in Transcript copy /
    paste
    and a desktop-only Keyboard & focus row, restating that selection
    is within a single textLayout, cross-message / cross-widget stays
    unsupported, plain click clears/sets a new anchor, 📋 remains the reliable
    copy path, and canvas Shift+click is desktop-only. Timeless — no
    phase/issue archaeology.
  • No native/harness/** change (verification-driven; the parent already
    shipped the shared behavior).

Gates

Gate Result
zig build harness -Doptimize=Debug (full Wasm compile; the local build IS the Wasm gate) ✅ exit 0 — shared-widget patch applies cleanly, no duplicate seam
TypeScript gates (typecheck / vitest) ⏭ n/a — docs-only PR, no `app/
Zig test-rich / test-rich-invariants / fmt ⏭ n/a — no .zig changed
build-harness GHA (CI release gate) pending (no native/harness diff → expected green/unchanged)

DoD checklist

  • Read-only textLayout: click anchors, Shift+click extends inside one
    widget (verified via parent's shared branch — not assumed)
  • No cross-widget / cross-message range; plain click clears / sets a new
    anchor (existing .mouse path)
  • Right-click link copy intact; no .right in the selection gate (plan: copy link URL via right-click / Alt+Click #647)
  • No duplicate shift_click seam in build.zig (idempotent + fail-closed
    patch; Wasm Debug build green)
  • Selection uses existing TEAL text_select — no new hex, no EMBER
  • Cloud ops N/A — build-harness is the release gate; no Production mutate
  • Living docs: docs/harness-limits.md updated (timeless)
  • AGENTS.md / README / SECURITY / .env.example — all N/A (no layer/trust/env
    change)
  • Operator browser gates (desktop): click→Shift+click→Ctrl/Cmd+C in a
    transcript body / fenced code / thinking / skill / tool-run detail;
    plain-click clear; different-widget Shift+click does not span; plan: copy link URL via right-click / Alt+Click #647
    right-click regression — operator-gated, out of CI's reach

Note: the interaction itself is browser-operator-gated (the plan's design
section and test table list these as operator gates). In-sandbox verification
is code-grounded reading + the green Wasm compile above.

…ies (plan #753)

Verification-first outcome for plan #753: parent #752 (PR #762) already
installed a shift_click extension branch on the shared TextLayoutWidget, so
read-only transcript text (the same widget) already gets click-anchor +
Shift+click extend + Ctrl/Cmd+C copy with no second seam. This PR is the
living-docs hardening for that verified read-only path; no native/harness
code change.

- docs/harness-limits.md: Read-only body text Shift+click row in Transcript
  copy/paste + a desktop-only Keyboard & focus row; restate within-a-single-
  textLayout and cross-message unsupported; TEAL text_select, Ctrl/Cmd+C
  browser-reserved copy of the active range, plain-click clear, desktop-only.

Fixes #753 (Refs #749, #752)
@btipling btipling self-assigned this Aug 22, 2026
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
invincible Ignored Ignored Aug 22, 2026 8:08am

Request Review

@btipling

Copy link
Copy Markdown
Owner Author

Summary: verification-first phase for plan #753. Parent #752 (PR #762) already installs the sel_move.shift_click extension branch on the shared TextLayoutWidget that read-only bodies paint with, so click-anchor + Shift+click extend + Ctrl/Cmd+C copy are already delivered — this PR is the living-docs hardening (docs/harness-limits.md), no net-new code and no duplicate shift_click seam.

Verified in-sandbox: zig build harness -Doptimize=Debug exit 0 (shared-widget patch applies cleanly). Docs-only diff → no TS/vitest gates. The browser interaction itself is operator-gated (desktop): click → Shift+click → Ctrl/Cmd+C in a body / fenced code / thinking / skill / tool-run detail; plain-click clear; different-widget Shift+click does not span; #647 right-click regression.

@btipling btipling left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review — PR #775

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainplan/shift-click-select-textlayout · 1 file · living-docs only for plan #753 (read-only textLayout Shift+click)
HEAD: ed19e8c1346dc8243dba7018e67fb9039aa273e3
Lenses run: L1, L3, L6, L8, L9 (skip: L2 no secrets/API/workflows; L4 no native/harness/** so no artifact rebuild; L5 no poll/alloc; L7 no clone bind)
AGENTS.md read: yes · docs/feature-divide.md read (docs only; no DOM composer) — divide holds

The shape of a verification-first docs PR is allowed: parent #752 (PR #762, merged 8f23c1a) did install sel_move.shift_click on the shared TextLayoutWidget, dvui.textLayout() does processEvents(), addTextMixed does call tl.addText, Ctrl/Cmd+C is keymap-reserved (not marked handled) so a focused layout can matchBind("copy")copy()clipboardTextSet, and applyInvincibleShiftClickPatch is still the single fail-closed seam. No second shift_click branch. Palette is still text_select. Feature-divide one-liner still yes.

The claim this PR writes into operator docs is that the parent's shared branch already delivers “click → Shift+click → Ctrl/Cmd+C” on transcript rich-MD bodies. That is the surface this child exists for. It does not survive a link.

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 Documented Shift+click-to-select on a markdown link inside a read-only body still opens the URL. TextLayoutWidget sets click_pt / click_event on any pointer-release without drag (TextLayoutWidget.zig ~2104–2108). Parent #752 only gated the click_num += 1 arm, not click_pt. Same-frame addTextClick therefore returns the release. paint_text.zig paintInlines .link maps that through link_click.kind with a Pointer that has no shift field (link_click.zig 15–18: plain left → .open). Assistant body with https://example.com. Click at A in the paragraph, Shift+click the link (the documented extend). dvui.openURL(.{ .url = href, .new_window = false }) navigates the harness tab. Selection may also extend. Composer/queue textEntry has no addTextClick links — this hole is read-only-specific, which is exactly #753. Defender: “parent already shipped the widget; this PR is docs.” Survives: this PR is the one that teaches the gesture on rich-MD bodies. Defender: “📋 is the reliable path.” Then don’t document Shift+click as the body-text select path without carving out links (or pass me.mod.shift() into link_click and no-op .open). Defender: “operator wouldn’t click the link.” Assistant replies are full of URLs; that is the widget. high
Minor L6 Plan tests 1–6 are operator gates and remain unchecked. Test 1 (“click A → Shift+click B in a transcript body”) is the row that would have hit a URL. The PR treats code-reading + a Debug wasm compile as verification. CI stays green (docs-only; no native/harness path filter). Operator follows the new Keyboard / Transcript rows, Shift+clicks a link, leaves /harness. Defender: “plan listed them operator-gated, out of CI.” Honest, and insufficient: the design section required empirical click-A/Shift-click-B, not pin reading. The Major above is the missed test. high
Nit L8 Keyboard & focus row lists “transcript rich-MD bodies, fenced code, thinking/preview/skill rows” and omits tool-run detail; Transcript copy/paste includes it. Operator using only the keyboard table thinks tool-run Shift+click is unsupported. Defender: “see Transcript copy / paste.” The keyboard row is the chord table. high
Nit L1 “an empty range copies nothing” overclaims. TextLayoutWidget.copy() assigns copy_sel = self.selection with no start < end gate; addText / addTextDone still clipboardTextSet a len-0 slice. Plan lock was “only when a range is active.” Click (caret, empty range) then Ctrl/Cmd+C while the layout is focused — clipboard becomes "" (wipe), not a no-op. Defender: “len-0 is a no-op in some browsers.” Not a gate, and not what the sentence says. medium

Residual risk

Parent click_num = 0 + three-way Shift+release gate did land in #762; this attack did not re-open that. Cross-widget / cross-message still unsupported (honest). cache_layout on committed bodies was not runtime-tested; the patched .shift_click bytesNeeded arm is “visible region like mouse,” so a cached layout should still hit-test, but that is unsmoked. Live desktop click→Shift+click→Ctrl/Cmd+C on a non-link body was not run here. Touch correctly stays on 📋.

Merge guidance

CONCERNS: do not merge until the advertised read-only Shift+click path does not navigate on a link — either link_click.Pointer grows a shift bit and .open is suppressed (net-new, still no second shift_click seam), or the living docs explicitly exclude links and keep 📋 / right-click as the URL path. The L6 operator smoke of test 1 on a body with a URL belongs in the same pass. Nits optional.

What was not attacked

Live DO runner / build-harness (no native diff, correctly untriggered); in-browser copy of a non-link fenced-code / thinking / skill / tool-run range; #647 right-click regression on a link that is not Shift+clicked; help-overlay / status / chip textLayouts (out of the documented surface).

@btipling

btipling commented Aug 22, 2026

Copy link
Copy Markdown
Owner Author

Plan to address feedback on PR #775 (Status: IN PROGRESS → DONE)

Last updated: 2026-08-22

Feedback items (most complex → least)

# Severity Review item Status
1 Major L1 — documented read-only Shift+click on a markdown link navigates instead of select-extending 🟢 done (docs carve-out)
2 Minor L6 — plan tests 1–6 remain unchecked operator gates; test 1's URL case missed 🟢 done (folded into Major carve-out + explicit operator-gated note)
3 Nit L8 — Keyboard & focus row omits tool-run detail 🟢 done
4 Nit L1 — "an empty range copies nothing" overclaims (len-0 write wipes clipboard) 🟢 done
5 Nit L8 — [Links] row is a dangling markdown shortcut reference (no [Links]: ref definition → broken/literal) 🟢 done (bold row-name)

#1 — Major L1: Shift+click on a read-only-body link navigates (Major)

  • Review item: documented "Shift+click range-selects … and Ctrl/Cmd+C copies" on transcript rich-MD bodies; a Shift+click on a markdown link inside a read-only body still opens the URL.
  • Grounded problem: native/harness/src/rich/paint_text.zig:96–104 builds link_click.Pointer from the .mouse release with only right/middle/alt/ctrl_cmdno shift. link_click.kind() (src/rich/link_click.zig:15–19) therefore maps a Shift+left release to .open (plain-left bucket), and paint_text.zig:105–109 calls dvui.openURL(.{ .new_window = false }) → the harness tab navigates away. Parent plan: harness — Shift+click extends text selection in composer + queue-row editor (#749) #752 (PR feat(harness): Shift+click extends selection in composer + queue editor (plan #752) #762) only gated the click_num double-click arm and the addText selection extend, not the link path — so the documented surface is exactly the one that breaks.
  • Severity: Major (CONCERNS merge gate) — a safe-looking gesture sends the operator off /harness.
  • Strategies considered:
    • A) Docs carve-out (→ CHOSEN, this PR): explicitly exclude links from the promoted read-only Shift+click gesture in docs/harness-limits.md (Keyboard & focus + Transcript copy / paste), stating a plain or Shift+click on a link still navigates/copies per the [Links] row and never extends the range, with 📋 as the reliable copy path. Honest, keeps this docs-only verification-first PR docs-only; no second shift_click seam, no native diff. The Phase-plan: harness — in-canvas Shift+click range-select on read-only textLayout (child of #752, #749) #753 "already delivered by parent" claim is narrowed to non-link body text.
    • B) Net-new code (shift bit) — deferred for the docs phase: grow link_click.Pointer with a shift field (paint_text.zig populates me.mod.shift()), and have link_click.kind() return a no-op/none when shift is held so Shift+click always select-extends and never opens. The reviewer explicitly allowed either A or B; B is the "make the gesture fully true" follow-up and is the documented residual if an operator wants Shift+click-over-link selection. Not needed to unblock merge (reviewer: "either … or the living docs explicitly exclude links").
  • Open questions: none — reviewer sanctioned both paths; A chosen to preserve the docs-only scope.
  • Fix (when decided): see A. Both doc rows now state links are links-first.
  • Tests: n/a docs-only. Operator smoke (path B) would be a live-browser check, out of CI.

#2 — Minor L6: plan tests 1–6 unchecked operator gates

  • Review item: plan tests 1–6 are operator (desktop-browser) gates and were never run; Test 1 ("click A → Shift+click B in a transcript body") is the row that would have hit the URL defect.
  • Grounded problem: tests are operator-gated (desktop browser, gradient stripping, link-body navigation) and cannot run headless in CI — the plan's own testing table lists them as operator gates.
  • Resolution: the Major's carve-out (below) directly covers Test 1's URL sub-case in the promoted docs gesture, and the PR explicitly records that these remain operator-smoke items (desktop), not CI assertions. Documented in the PR body; no false 'verified' claim retained.

#3 — Nit L8: Keyboard & focus row omits tool-run detail

  • Fixed: docs/harness-limits.md Keyboard & focus "Shift+click (read-only body text)" row now lists "tool-run detail" alongside rich-MD bodies, fenced code, thinking/preview/skill rows — matching the Transcript copy/paste row.

#4 — Nit L1: "an empty range copies nothing" overclaims

  • Grounded problem: TextLayoutWidget.copy() sets copy_sel = self.selection with no start < end gate; addText/addTextDone still write a len-0 slice to the clipboard. So Ctrl/Cmd+C with a caret-only (empty) focused range wipes the clipboard, not a no-op.
  • Fixed: both rows now say an empty/caret-only range is an empty write that can blank the clipboard — select a range before copying or use 📋.

Round 3 — #5 Nit L8: [Links] dangling markdown shortcut reference

  • Review item: docs/harness-limits.md uses [Links] row in the two read-only Shift+click rows, but the file has no [Links]: reference definition (rounds 2 & 3), so GitHub renders it literally/broken instead of jumping to the Rich transcript Links row (~line 233, the modifier table).
  • Grounded problem: verified — [Links] occurs at lines 66 & 207 with no matching link-reference def in the doc; the intended target is the Links row (line 233) in ## Rich transcript (Wasm).
  • Fix: converted both to the doc's bold row-name convention — per the **Links** row — resolving the broken shortcut without a native seam. Docs-only, no gate impact.

Progress log (append-only)

Merge decision

CONCERNS gate satisfied: the advertised read-only Shift+click path no longer navigates on a link — the docs explicitly exclude links (reviewer-sanctioned Path A). Nits #3, #4 resolved; round-3 Nit #5 (dangling [Links] ref) resolved on b6ab66b. #2 = operator-gated smoke, documented as such. → merge-ready from review; hand off to merge-pr on explicit request.

@btipling btipling left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review — PR #775 (round 2)

Verdict: PASS WITH NOTES
Repo: btipling/invincible
Scope: mainplan/shift-click-select-textlayout · 1 file · living-docs only for plan #753 (read-only textLayout Shift+click)
HEAD: 84da4d967a133660bad6d398b240499e02055244
Lenses run: L1, L3, L6, L8, L9 (skip: L2 no secrets/API/workflows; L4 no native/harness/** so no artifact rebuild; L5 no poll/alloc; L7 no clone bind)
AGENTS.md read: yes · docs/feature-divide.md read (docs only; no DOM composer) — divide holds

Round 1 (CONCERNS on ed19e8c) required the advertised read-only Shift+click path not to silently navigate on a markdown link — either suppress .open on shift, or document that links stay links and 📋 / right-click are the URL path. 84da4d9 takes the docs carve-out. Keyboard + Transcript rows now say a plain or Shift+click on a link navigates / copies per the Links row and is not the range-select path; tool-run is on both rows; empty range is an empty clipboard write, not a no-op.

Re-traced the production seam on this SHA: link_click.kind still has no shift bit (link_click.zig 15–18, plain left → .open); TextLayoutWidget still sets click_pt on any click-without-drag; paint_text.zig .link still openURL on .open. That is now the documented product, not a silent hole. No second shift_click seam. Feature-divide one-liner still yes.

No Blocker or Major survived self-refutation on this SHA.

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Nit L8 [Links] row is a dangling markdown shortcut reference — this file has no [Links]: … definition, so GitHub will not jump to the Rich transcript Links row (docs/harness-limits.md ~233). Operator clicks the bracketed word expecting the modifier table (left open / right-or-Alt copy). They get a literal or broken ref. Defender: “search for Links.” The row exists; the link does not. high

Residual risk

Shift+click on a link still runs the shared .shift_click extend and openURL (same window). The docs say it “does not extend the range”; operator-visible result is navigation, so the lie is bounded. link_click.Pointer still ignores shift — a later “select through a URL” change needs a code seam, not another docs row. Plan tests 1–6 remain operator-gated and unchecked; the URL case is now named in the living docs. Empty-range Ctrl/Cmd+C can still blank the clipboard (now disclosed). Live desktop click→Shift+click→Ctrl/Cmd+C on a non-link body was not run here.

Merge guidance

PASS WITH NOTES. Safe to merge from this attack. Round-1 Major is closed by the explicit link carve-out (the option this review allowed). The leftover Nit is a broken in-page pointer, not a drain/navigation surprise. Do not --approve from this skill.

What was not attacked

In-browser copy of a non-link fenced-code / thinking / skill / tool-run range; #647 right-click on a link that is not Shift+clicked; help-overlay / status / chip textLayouts (out of the documented surface); build-harness (no native diff, correctly untriggered). GitHub checks on this SHA are ignored Vercel only.

@btipling btipling left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review — PR #775 (round 3)

Verdict: PASS WITH NOTES
Repo: btipling/invincible
Scope: mainplan/shift-click-select-textlayout · 1 file · living-docs only for plan #753 (read-only textLayout Shift+click)
HEAD: 84da4d967a133660bad6d398b240499e02055244 (unchanged from round 2)
Lenses run: L1, L3, L6, L8, L9 (skip: L2 no secrets/API/workflows; L4 no native/harness/** so no artifact rebuild; L5 no poll/alloc; L7 no clone bind)
AGENTS.md read: yes · docs/feature-divide.md read (docs only; no DOM composer) — divide holds

HEAD did not move since round 2 (84da4d9). Re-traced the production seam, not the prior comments. Round-1 Major remains closed by the docs carve-out: Keyboard + Transcript rows say a plain or Shift+click on a link navigates / copies and is not the range-select path. link_click.kind still has no shift bit (plain left → .open); that is now the documented product. Tool-run is on both rows; empty range is an empty clipboard write. Feature-divide one-liner still yes.

The round-2 Nit is still live (no commit, no follow-up comment after 07:22). No Blocker or Major survived self-refutation on this SHA.

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Nit L8 [Links] row is a dangling markdown shortcut reference — this file has no [Links]: … definition, so GitHub will not jump anywhere. The nearby row in the same Transcript copy / paste table is named Link URL (docs/harness-limits.md ~202); the modifier table (left open / right-or-Alt copy) is Rich transcript Links (~233). Operator clicks the bracketed word expecting the modifier table. They get a literal or broken ref. Defender: “search for Links.” The rows exist; the link does not. Unchanged from round 2. high

Residual risk

Shift+click on a link still runs the shared .shift_click extend and openURL (same window). The docs say it “does not extend the range”; operator-visible result is navigation, so the lie is bounded. link_click.Pointer still ignores shift — a later “select through a URL” change needs a code seam, not another docs row. Plan tests 1–6 remain operator-gated and unchecked; the URL case is now named in the living docs. Empty-range Ctrl/Cmd+C can still blank the clipboard (now disclosed). Live desktop click→Shift+click→Ctrl/Cmd+C on a non-link body was not run here.

Merge guidance

PASS WITH NOTES. Safe to merge from this attack. Round-1 Major is closed by the explicit link carve-out. The leftover Nit is a broken in-page pointer, not a drain/navigation surprise. Do not --approve from this skill.

What was not attacked

In-browser copy of a non-link fenced-code / thinking / skill / tool-run range; #647 right-click on a link that is not Shift+clicked; help-overlay / status / chip textLayouts (out of the documented surface); build-harness (no native diff, correctly untriggered). GitHub checks on this SHA are ignored Vercel only.

@btipling
btipling merged commit 0b6dc5a into main Aug 22, 2026
2 checks passed
@btipling
btipling deleted the plan/shift-click-select-textlayout branch August 22, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant