Skip to content

fix: clipboard-only Copy as Image with a share-ready snippet; clean selections (no line numbers, no blame interference) - #7

Closed
jkyberneees wants to merge 10 commits into
mainfrom
loop/feature-20261029-1525-snippet-image
Closed

jkyberneees wants to merge 10 commits into
mainfrom
loop/feature-20261029-1525-snippet-image

Conversation

@jkyberneees

@jkyberneees jkyberneees commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Copy as Image: clipboard-only, share-ready snippet; line numbers never join a selection

Follow-up to 1.6.0 from user feedback: "selecting text with the mouse is still selecting line numbers", and "copy as image MUST copy to clipboard only - adding snippet style, ready to share in social apps".

What

  • Line numbers never join a selection. The gutter, blame and truncation labels used only the unprefixed user-select: none, which WebKit ignores. The macOS app is a WKWebView, so dragging selected and highlighted the line numbers. Every user-select: none now also has -webkit-user-select: none, and those elements get a transparent ::selection as a backstop. A CSS test pins this.
  • Clipboard only. The PNG download fallback and the "copied text instead" fallback are gone. Copy as Image either puts an image on the clipboard (web clipboard, or the native pasteboard in the macOS app) or shows "Could not copy image to the clipboard". Too-large selections show "Selection too large for an image" on every path.
  • Share-ready snippet card. The selection sits in a rounded window card with a soft shadow. The card has a title bar with three window dots and the file name, on a padded gradient backdrop derived from the theme accent. It is rendered at 2x, with a minimum content width.
    • Code keeps the editor's syntax colours, theme and fonts.
    • Glyphs are drawn on the editor grid, so there are no font ligatures, and italics stay monospace.
    • Wide CJK and emoji characters take two cells, and combining marks stay on their base character.
  • Same frame everywhere. Rendered-Markdown selections are framed in the same card. In the macOS app, when the WebKit canvas is tainted, the native snapshot comes back to JS as a PNG, gets framed, and is written through the native pasteboard handler. Request ids, cancellation and the selection-highlight ownership are unchanged.
  • Blame label no longer breaks selections. Pressing the mouse on a line made it active, and that, plus the blame lookup ~180 ms later, re-rendered every row and tore out the anchor of an in-progress drag. The active-line highlight and the blame label are now patched in place on the affected row only. The blame label waits until the mouse button is released and stays hidden while a selection exists.
  • Docs (README, DESIGN §15) are updated.

How it was checked

  • Unit tests cover the pure geometry and decisions: frame layout, scale, shadow, font strings, glyph cells and message mapping. The canvas, clipboard and Swift glue was reviewed line by line.
  • The real image was rendered in WKWebView, the macOS app's engine, in both themes. That render caught and fixed serif italics, ligatures, a missing 2x scale, emoji misalignment and square card corners.
  • swiftc -parse passes for packaging/macos/main.swift.

How to test

  1. In the macOS app and in a browser, drag-select across several code lines. The line numbers must not highlight, and pasted text has no numbers.
  2. Drag-select starting on a line with a blame label: the selection holds, and the label appears only after you release and clear the selection.
  3. Select code and press ⌘⇧C, or right-click and choose Copy as Image. Paste into Slack, X or Notes: you get the framed card with the file name, theme colours and crisp 2x text. Nothing is downloaded.
  4. Do the same from a Markdown preview selection.
  5. With the clipboard unavailable (e.g. a non-secure LAN URL in a browser), you get "Could not copy image to the clipboard", and no file or text is copied.

Known limitation

A combining accent is drawn over the next character if a syntax-highlighting token boundary falls exactly between it and its base letter. That is very rare in practice.

🤖 Generated with Claude Code

Comment thread test/client.userselect.css.test.ts Fixed
jkyberneees and others added 3 commits October 29, 2026 16:24
…rations without a regex

The Ubuntu runner timed out at bun's 5 s default on a cold swiftc start, and CodeQL
js/incomplete-sanitization flagged the regex-built property matcher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
6bd41f3 2026-10-29T15:57:22.842Z View logs ↗
  • Build: Failed ❌

View logs ↗
42efcd3 2026-10-29T15:27:46.023Z View logs ↗
  • Build: Failed ❌

View logs ↗
afb4168 2026-10-29T15:30:08.439Z View logs ↗

@jkyberneees jkyberneees changed the title fix: clipboard-only Copy as Image with a share-ready snippet; no line numbers in selections fix: clipboard-only Copy as Image with a share-ready snippet; clean selections (no line numbers, no blame interference) Oct 29, 2026
return this.childNodes.filter((c) => c.nodeType === 1);
}
get textContent(): string {
return this.nodeType === 3 ? this.data : this.childNodes.map((c) => c.textContent).join("") + (this.html ? this.html.replace(/<[^>]*>/g, "") : "");
return this.childNodes.filter((c) => c.nodeType === 1);
}
get textContent(): string {
return this.nodeType === 3 ? this.data : this.childNodes.map((c) => c.textContent).join("") + (this.html ? this.html.replace(/<[^>]*>/g, "") : "");
@jkyberneees

Copy link
Copy Markdown
Contributor Author

Superseded: Copy as Image is being dropped. main will return to the 1.5.0 code, with text copy and selection improvements (no line numbers in selections, the blame label no longer interferes with dragging) in a new PR.

🤖 Generated with Claude Code

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.

2 participants