Replace the selection popup with an editable copy preview - #885
Conversation
Two React-drawn mock terminals (Claude Code inline and fullscreen, zsh) over a pure model of the copy transforms: line-break judgement, chrome stripping, scope growth, and word nudging. Prototypes/Copy editor compares three concepts (numbered chooser, copy-then-switch receipt, editor). Prototypes/Copy editor 2 carries the chosen one: the editor at full pane width, Auto / Exact / Spaces / No breaks cycled with f, e and Shift+e for scope, arrows to nudge, and a clickable mark on every line break. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The storybook Tool renders as agent-browser-screencast, innerdogfood drops its explicit desktop viewport, and the descriptive comments are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/⬆︎f bare Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pure reading of the terminal buffer for the copy editor: Auto / Exact / Spaces / No breaks renderings with per-break judgement and overrides, decoration stripping, scope expansion to whole words and paragraphs, and word-wise edge nudging. Cells are read through xterm's wide-character continuation cells. Not wired to the UI yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mouse-up over a terminal-handled drag opens the copy editor at full pane width: the clipboard text with a gutter line per clipboard line and a clickable mark on every line break. Formats are Auto (the old Rewrapped, now judging each break on its own: soft wraps and margin-split tokens rejoin, code and list lines keep their breaks), Exact (the old Copy Raw), Spaces, and No breaks, cycled with f / Shift+F. Scopes expand with e / Shift+E to whole words (Full URL / Full path across a split) and the paragraph, drawn dashed by the selection overlay. Arrows nudge the end and Shift+arrows the start by a word. Enter or the copy chord copies; any other key closes the editor and reaches the terminal. The tutorial's Raw / Rewrapped items now check Exact and Auto copies; the homepage and VS Code README describe the editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Cancel-on-change follows the selection: the lifecycle re-snapshots it whenever the store's selection changes, replacing the router's injected setter, the nudge's re-arm, and a dead setTerminalSelectionBaseline. - setSelection writes the copy editor in the same notify (none unless one is given), so an editor never outlives its selection; consumers gate on copyEditor alone. - Span carries its block flag; selectionOfSpan inverts spanOfSelection. - Row text, decoration and paragraph bounds are computed once per row, and one decoration rule serves rows and extracted lines. - The editor renders each format once per scope and reuses it for the preview and the duplicate check; a mark passes its own kind to flip. - Whole-words labels classify edge tokens with the smart-token detector, which also shares the new readLineCells cell reader. - Segments and the Copy button use the design recipes; the preview is memoized against the render tick; tests and stories share one fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…editor A primary mouse drag that reaches the inside program keeps reaching it untouched, but Dormouse now remembers the cells it crossed. On release they become a program-owned selection with no outline and a "Press Cmd+C to copy" hint; the copy chord (Ctrl+C elsewhere) opens the copy editor over them, outline included. Any other key goes to the program and drops the shadow, as do a new press, a content change, and the program turning mouse reporting off. Ctrl+C now intercepts while such a drag waits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OSC 52 is still consumed and still never writes the clipboard. The owner's parser now decodes a write (UTF-8, line endings normalized, other controls but tab removed, dropped rather than truncated past CLIPBOARD_OFFER_LIMIT; a `?` read is never answered) into a clipboard offer, sent to the owning renderer as terminal:clipboardOffer from the sidecar and the VS Code extension host, and routed by PTY id in Tauri. The webview accepts it only into a pane holding a shadowed drag, where the copy editor shows it as a fifth format, "From <program>", last in the f cycle; it is copied only when the user picks it. Replay re-parses without offers. security-local.md's terminal-output rule and FAIL IF lines, security.md, terminal-escapes.md, and transport.md describe the new boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Simplify: - parseOsc52 reuses decodeBase64; CLIPBOARD_OFFER_LIMIT bounds the base64 payload under the incomplete-OSC bound, so PTY read splitting never changes whether an offer is kept. - One clearSelection writes every selection-scoped field; isShadowed names the shadowed state for the overlay and the keys. - The program format's lack of scope lives in the model (choosing it returns to the selection's scope; e does nothing), not three view checks. - "From <program>" uses the WATCHING command key, skipping wrappers and earlier commands, instead of the display string. - A shadowed drag measures its end cell once at release; one pastDragThreshold serves the router's three threshold checks. - renderText shares the break text with render; the copy chord label lives beside hasCopyModifier. Fix, found driving the real app: - Auto judged wraps against at least half the terminal, so `fold -w 50` output in a wide pane never joined; the floor is now an absolute 40 columns (60% of a narrower terminal). - A selection edge resting on a blank no longer grows into the word beside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
relay and one-time get one-line descriptions saying what they serve and how changes reach them; hosted's says its backend needs a restart and Docker. one-time drops the inline notes the other Tools no longer carry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor concept is built (Components/CopyEditor); the prototypes and their parallel model of the copy heuristics would only drift from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The editor holds the buffer it opened with: preview, copy, and nudge all read it, so what is shown is what is copied, and only opening reads the terminal. copy-editor reads the leaf registry, so the copy-selection wrappers that only looked up a terminal are gone. - A scope's rows are read and stripped once (readScope) for all four formats, cached per scope span, and each format renders in one pass. - The Whole words label renders only the grown edge tokens, and nothing when no edge grew. - Any input the terminal receives closes the editor or a shadowed drag at writeUserInput — Pocket's input bar and paste included — replacing the Wall's keydown dismissal list. The editor's bare keys apply in passthrough only; the copy chord works in either mode. - The store owns "the program format needs its offer": a replaced selection demotes it, and setCopyEditor refuses it without one. - stringCopyBuffer, Rendering.lines and the popup-era wording are gone; tests and stories share fixtures (fakeXterm, bufferLine, finalizedSelection) and one useStorySelection harness; the expanded-scope colour lives in design.tsx; the surface merges its border through tv. - Spec: one dismissal list (§4.5), Auto's rule order matches the code, Enter vs Shift+Enter stated, pointers instead of repeats in transport.md and shortcuts.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A drag's edges were the cells under the pointer, inclusive at both ends, so releasing just past a word's last character took the next character too: the end of a selection was usually one character too far, in either drag direction. Each edge is now the cell boundary nearest the pointer, as xterm.js's own selection reads it; the earlier edge takes the cell after its boundary and the later edge the cell before (column order for a block). The router keeps the drag's two boundaries and re-resolves both cells on every move and on an Alt flip, replacing setDragAlt; shadowed program drags resolve the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resize cleared every selection, so the copy editor closed whenever its pane changed size. A finalized linewise selection Dormouse owns now follows its cells: each edge is a cell offset into its logical line, held by an xterm marker on that line's first row, which reflow never inserts or deletes. The new position is coordinate math; it is kept only if the cells there, soft wraps joined and wrap padding skipped, still read as the selected text, and cancels otherwise, as does a trimmed edge. Block, program-owned, alternate-buffer, dragging and copy-confirming selections still cancel. The open editor is rebuilt over a fresh read of the buffer in its format and same-labeled scope; per-break edits drop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A height-only resize reflows nothing, so every break still sits where it did and the edits made to them still apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The copy buffer trimmed trailing blanks off every physical row, so a real space in a soft-wrapped row's last cell was lost: Auto and No breaks joined `dddd ` + `eeee` into `ddddeeee`, and Whole words grew across the space. Rows now trim only where their logical line ends, dropping just a wide character's wrap padding before a soft wrap. A deleted soft wrap puts the trimmed blanks back and keeps the continuation's leading blanks, which the joined-line strip used to drop the same way. Exact and Spaces are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The copy flash's timer is keyed to its own flash, not the selection, so a nudge or a resize during the flash no longer leaves a stuck checkmark, and a resize now follows a selection whose copy is confirming. - The selection's baseline, anchor, render-time cancel check, and resize follow move from terminal-lifecycle.ts into selection-watch.ts (`watchSelection`); the lifecycle only wires xterm's events to it. - `followCopySelection` replaces `refollowCopyEditor` as a copy-editor transition, and writes nothing when a resize moved nothing and the editor's width held. - `followReflow` returns a reading-order Span; `ReflowAnchor` is opaque. - `lineAt` in buffer-cells.ts is the one guarded line reader; the reflow module fetches each row once and shares the wrap-padding check. - Spec 3.4 scopes the rule and cancels any other selection; the tests are table-driven, de-duplicated, and pin the watch's render check directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Geometry for the coming copy editor rewrite; nothing renders differently yet. - TerminalOverlayDims carries the terminal element's viewport corner, read from the rect getTerminalOverlayDims already measures. - copy-editor-placement.ts places the editor in the window: below, above (touch: above first), or the roomier side of the pane at natural size, then squished into the roomier of below and above, then the overlay. A natural spot is held unless a higher one fits with 24px to spare, and every candidate is computed from the new band, so a held spot never covers the selection. GAP_PX and MIN_HEIGHT_PX move here. - rect-tween.ts gains a rect-only tween, and rect-motion.ts drives it from rAF: the first target snaps, later ones restart from the displayed rect, and settled frames land on whole pixels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plumbing for moving the copy editor onto document.body. A React portal still passes events up the React tree, but DOM contains and closest checks no longer see the portaled subtree as part of its pane. - dom.ts maps a [data-portal-anchored] root to an anchor left in place (setPortalAnchor), and anchoredTarget stands that anchor in for any target inside the root. The terminal context's outside press and its Escape, the Wall's context key routing, the Tool context-terminal check, and the agent-browser key forwarder all test it. - MobileTerminalUi ignores a press outside the host's DOM, so Gestures mode never grabs it and it neither acknowledges the Session nor dismisses the keyboard. - LathHost provides lath.subscribeFrames as LayoutFramesContext, for chrome that must follow a pane moving under Lath. - COPY_EDITOR_Z_INDEX = 55: above the ring, below every modal layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor leaves its pane's clipping box for document.body, so it can sit below or above the selection over a neighbouring pane, or beside its own pane, and covers the selection only when nothing else fits. - CopyEditor portals its root to document.body at COPY_EDITOR_Z_INDEX, leaving a hidden anchor in the pane (setPortalAnchor). The root is fixed and hidden until first placed; only the rect-motion driver writes its geometry, and data-copy-editor-side names the spot taken. - copy-editor-measure.ts holds the DOM reads: the natural width from a w-max probe of every format's longest lines, so f and a flipped mark never change it, and the height at a width from a detached copy of the preview, cached per rendering. - Placement runs before paint on a selection, scope, or rendering change, so opening lands without travel. Anything else that moves the pane is coalesced to one frame: the render tick, Lath layout frames, Workspace motion frames, the window and visual viewport, a scroll outside the editor, and a resize of the terminal. The editor no longer re-renders on the tick. - It hides while its Wall is transformed, its pane is hidden, or another pane is zoomed over it, and it stops a right-click as well as a press from reaching the pane. - CopyEditorPlacement stories check each spot in a real Wall: below over a neighbour, above, beside, squished, and the overlay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- mouse-and-clipboard.md §4.5: the body portal and its layer, the desktop and touch ranking, the width rule, re-placing on every selection or scope change, easing every move from the displayed rect, never showing while the Wall is transformed, and presses inside it counting as inside its pane. §7 points the editor's re-placement at layout.md's Position tracking. Budget ratcheted to 5100. - The rationale gains 4.5: why window-wide, why the width cap spans every format, restart rather than retarget, hysteresis, layer 55. - DESIGN.md widens the layout-animation carve-out to every JS per-frame tween and describes the copy editor's travel; the FOCUS_MOTION_MS comment names it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The copy text model judged physical rows, so a soft wrap's continuation row counted as a line of its own. Auto, Paragraph and Whole words now read logical lines: a row and the rows soft wraps continue it onto. - Whole words / Full URL / Full path stopped one cell into a crossed row: the crossing landed on a blank (the next row's indent, or past the row above's end) and the edge-on-a-blank check then ended the growth. A token now crosses a deleted break only with text on both sides, onto the row above's last cell or the row below's first text cell, and a soft wrap's continuation starts at column 0 rather than its line's indent. - A blank row inside a soft-wrapped line read as a blank line: it bounded Paragraph, made Auto keep the breaks around it, and made No breaks drop the blanks it should join exactly. A line's trailing blanks are now trimmed however many rows they wrap onto. - A continuation row's leading position counted as an indent, so it broke the hanging-indent match and pulled the shared indent to 0; its length also counted as a line's, so the fit test kept every break of a reply after a pane narrowed. Auto now judges whole lines, so a reflowed reply reads as the original does. The mid-drag smart extension had the same gap: it read only the pointer's row, so `e` stopped a soft-wrapped URL at the row edge. It now reads across the soft wraps around the pointer, and its hint carries a start and end cell. Exact output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press inside the body-portaled editor focused its buttons on document.body, so four key routes needed anchoredTarget to find the pane again. The editor root now prevents the press's default, except on the preview's own box so its scrollbar still drags, and its buttons leave the tab order. Focus stays in the pane's xterm textarea, so the Wall keyboard, the selection keys, the browser forwarder, and the context's Escape are back to their plain DOM checks. setPortalAnchor marks and unmarks the root itself, and anchoredTarget returns an Element, so its one remaining caller, the context's outside press, needs no cast. The spec states the focus rule, and to stay in its word budget its placement section and rendering notes keep one statement per rule: the editor's subscriptions become a pointer to layout.md's "Position tracking", and the rationale drops the hysteresis mechanism sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor and the ring each wired the same five sources that move a pane without a render: its ResizeObserver, window resize, capture-phase scroll, Lath frames, and Workspace motion frames. subscribePaneMotion now holds them for both, with the anchored menu's narrower scroll rule: only a scroller that contains the pane can move it, and xterm's own scroll already reaches the editor through the render tick. subscribeOverlayViewport holds the visual viewport pair the editor and the anchored menu shared. LayoutFramesContext and the ring's prop take LathWallEngine's subscribeFrames type. The editor no longer reads its Wall's inline transform, which is workspace-motion's private presentation: workspaceInTravel answers it. Its concealment checks run cheapest first, and a concealed editor that is already hidden writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor skips placement on a render tick that moved nothing, keyed on the grid, its box, the viewport, and the measured inputs: the tick is global, so it fired for every terminal's output. Its height measurer resizes one hidden copy of the preview React keeps, instead of deep cloning it for every width. Placement builds the side spots only when neither natural spot fits or a side is held, and one hold() carries all three ways of keeping the previous spot. The width probe cuts each line to what a screen could show, takes its three longest in one pass, and rebuilds only for a new scope or offer, never a flipped mark. rect-motion is created once, owns showing and hiding, and drops dispose and its injected clock; rectsEqual, insetOverlayBounds, portalToBody, and installFakeFrames replace their copies. The editor keeps its side only on its root and its inputs in one ref, and its dismissal listener shares the follow effect's AbortController. DESIGN.md and the code comments point at the spec for restart-not- retarget and the editor's layer instead of restating them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`wrapRun` in buffer-cells is now the one walk over soft wraps: the copy buffer, the smart-token detector, the reflow anchor, and the prompt-line reader all take a logical line's rows from it, each line read once. `textWidth` / `textCells` are the one place wrap padding is dropped. - The copy buffer reads a whole logical line at once, so whether a row keeps its trailing blanks is one backward pass instead of a scan per row, and each row records its line's top and bottom: `line()` no longer walks. `textContinues` and its `translateToString` read are gone. - `readScope` groups rows by logical line and marks continuation rows, so `renderLines` stops asking the buffer. Decoration is judged on a logical line's rows joined, as `line()` judges: a box run, bullet, or row of box drawing a wrap puts mid-line is text, and a box side wrapped onto rows of blanks no longer leaves those blanks behind. - `wrapWidth`'s two walks are one helper, and a paragraph a break's walks read whole shares its width with its other breaks. - A scope's layout per format (lines, shared indent, Auto's decisions) is memoized, so a per-break override render reuses Auto's judgements. Exact renders hash identically before and after over every fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`detectTokenInBuffer` runs on every drag move. It now reads only the cell under the pointer when that cell is blank or unchanged since its last call, and otherwise reads the pointer's row first, going on into a row a soft wrap joins only while the run of non-whitespace reaches that edge. Each UTF-16 unit maps to its cell by number, with positions built only for the token's two ends. `setHintToken` skips a token equal to the one it holds, so a drag no longer rebuilds every pane's snapshot per move. `BufferToken` is the one name for the hint (`TokenHint` and `BufferCell` are gone), built from `DetectedToken` over `GridPos`, and the extension's direction reads through `comparePos`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor's natural width covered only its preview lines, so a narrow pane over a short selection gave a 188px editor whose root clipped the never-wrapping header and footer: one scope button, the selected format scrolled away, no count or Copy button. A chrome probe now lays out the header and footer as wide as any format shows them (one `*`, the Copy check, the widest count), measured only when its labels, hints, or count digits change, so `f` never resizes the editor. Placement takes that width as a floor and a side needs room for it. In a window narrower than the chrome the key hints and legend clip first, keeping the segments, the count, and Copy in view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A side was taken only when the editor fit it whole and only when it fit the whole chrome, key hints included, so a tall selection in a split layout squished into a short strip below or docked over the text with a full-height column free beside the pane. Squish now ranks below, above, and both sides by area, a side taking the window's height, and a side needs only room for the segments, the count, and Copy, measured by a second chrome probe; the key hints and legend clip first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a phone the Copy button was 40x20, a tap just below the editor landed
on the terminal and cleared the selection, and a copy confirmed with a
12px check that shifted the label before everything closed, so a copy
and a miss looked the same. Over plain http the write failed silently.
- Touch gets a full-width Copy row 44px tall.
- Copy swaps in place to Copied or Couldn't copy, every label laid out
in one cell; the copied selection fills, and touch holds the flash
1200 ms with a tap of vibration. A failed write keeps the selection.
- The write falls back to execCommand('copy'), before any await when the
Clipboard API is missing.
- A touch or pen press within 16px outside the editor is swallowed, with
its compatibility mousedown and click.
- MobileTerminalUi skips the later events of a press that began in a
portaled descendant, keyed by pointer id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A copy's flash and its failure notice were two exclusive fields with two token maps and two timers; they are now one `copyOutcome`, 'copied' or 'failed', on one timer, the newer replacing the older. The tutorial reads the copied format from the editor instead of the flash. Copy buttons share the outcome labels. The `execCommand` fallback writes through a one-shot `copy` listener, so focus stays put (no xterm focus report, no committed rename), keeping the hidden textarea only for an engine that fires no `copy`. The theme debugger, the external-link modal, and the update report copy through it. The copy fill's reduced-motion gate is the JS one alone, read once per flash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A near-miss tap was swallowed by three window-capture listeners and a flag carried from pointerdown to click, verified only on Chromium. On touch the root now pads the visible surface with a `TOUCH_SLOP_PX` margin, so the browser targets the editor itself, whose presses are already inert; the placement rect stays the visible editor. MobileTerminalUi drops its portaled-pointer set: a portaled press clears the pending tap instead. One chrome probe replaces the copy that left out the key hints and legend: measured whole, then with `data-essential` hiding its `data-chrome-optional` parts, and memoized. Placement maps each side once to its natural and squished spot, and skips measuring a side when the editor overflows the window at full width. The §4.5 rules state the rule once, the mechanism left to code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying mouseterm with
|
| Latest commit: |
76924a5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://7d7ea8dc.mouseterm.pages.dev |
| Branch Preview URL: | https://copy-paste-2.mouseterm.pages.dev |
Alt-or-latched-block was spelled out at four sites; `dragShape` in the selection store now owns it. Also tightens §5's re-examine rule and the nudge test's case labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # lib/src/components/MobileTerminalUi.tsx # lib/src/lib/platform/fake-adapter.ts # lib/src/lib/platform/vscode-adapter.ts # scripts/spec-word-budgets.json # standalone/src/browser-sidecar-adapter.ts # standalone/src/tauri-adapter.ts
dormouse-bot
left a comment
There was a problem hiding this comment.
Most findings are inline. Two more:
- An empty copy reports a failure. Auto renders
""for a frame-only selection (for example a lone╭──────╮row) or a whitespace-only one, and Exact does the same for whitespace.writeTextToClipboardreturnsfalseon empty text (if (!text) return false;), socopySelectioncallsfailCopyand Copy reads "Couldn't copy" for 1.5 s although no write was attempted. §4.5 ties that message to a failed write. The oldcopyRawjust withheld the flash. - Stale
§4.2references. §4 was renumbered, and §4.2 is now Scopes; the copy chord lives in §4.3. Three places still point at §4.2 for the chord:mouse-and-clipboard.md§8 ("copy keeps the macOS separation instead (§4.2)" and "It gates the copy chord (§4.2)") and thehasCopyModifierdoc comment inlib/src/components/wall/keyboard/chords.ts. All three sit outside the diff hunks, so there are no suggestions for them. I can push the fix if you'd like.
lib/src/ tests were not run locally; this review comes from reading the code.
| void copySelection(sid); | ||
| return consume(); | ||
| } | ||
| if (isCopyChord(e) && isShadowed(mouseState)) { |
There was a problem hiding this comment.
On Linux and Windows this captures Ctrl+C until the shadow is dropped, and nothing times it out. Only input through writeUserInput, a press in the same pane, a content change, or the program ending mouse reporting clears it; clicking another pane or waiting does not.
So after an accidental drag of a few pixels in htop, less --mouse, or a mouse-enabled TUI, the user presses Ctrl+C to quit. The first press opens the editor. A second press copies the shadow's text, which overwrites their clipboard. Only after the flash does Ctrl+C reach the program again. §3.8 specifies this, so it is a design call rather than a slip. Still, losing SIGINT to a drag the user may not know they made seems worth a maintainer's decision. Options include expiring the shadow, dropping it on focus leaving the pane, or opening the editor only when Ctrl+C comes soon after the drag.
There was a problem hiding this comment.
Keeping the specified §3.8 behavior in this change: the copy chord opens an editor over a shadowed program drag, and copies only on a subsequent explicit action. Expiring the shadow or clearing it on focus changes would change that contract and could discard an intentional copy. I have retained the current policy while fixing the implementation defects; the SIGINT tradeoff remains a maintainer design choice.
|
Addressed the implementation findings in 76924a5: preview focus, paragraph boundary expansion, relative indentation from a mid-line start, and nudging an edge past the row's text. Empty copies now return without attempting a write or showing a failure, with regression tests for frame-only and whitespace-only selections. All three copy-chord references now point to §4.3. Validation: 490 targeted library tests, library typechecking, and spec lint with its self-tests pass. The merge of origin/main also passed standalone and VS Code typechecking and 25 standalone adapter tests. The shadowed-drag copy-chord policy is retained as specified in §3.8; the inline reply explains that design choice. |
Replaces the Copy Raw / Rewrapped popup with a copy editor that previews the clipboard text before copying. Users can choose Auto / Exact / Spaces / No breaks, expand the selection to words or paragraphs, nudge either edge, and edit individual line breaks.
The editor uses available space across the window while following its pane and terminal reflow, supports touch controls, and shows copy outcomes. Mouse-reporting program drags and OSC 52 copy offers can also open the editor. Specs, shortcuts, tutorial text, and Storybook coverage are updated.
Validation: library typechecking, spec lint and its self-tests, and 281 targeted selection/copy tests pass. The innerdogfood harness starts and renders the app.