feat(#2476): custom stickers MVP#2550
Conversation
Add a DevTools-pasteable harness and decision plan for the #2476 custom-stickers feature. The load-bearing assumption (synthetic ClipboardEvent producing a sendable image in Teams compose) has no public prior art and conflicts with the browser-spec behaviour for synthetic paste events; spike validates this before any UI / config work lands. Phase 0 inspects the compose element and its listener shape; Phase 1 tries the cheap synthetic-event path; Phase 2 (clipboard.writeImage + synthetic Ctrl+V via sendInputEvent) is the spec-aligned fallback, stubbed pending Phase 1 outcome. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a feasibility spike for custom sticker support, including a research plan and a DevTools test harness to evaluate programmatic image insertion. The review feedback suggests adding error handling for canvas blob generation and improving the image detection heuristic in the test script to avoid false positives. Additionally, the reviewer noted that the Phase 2 plan should be updated to support macOS keyboard shortcuts and to preserve non-image clipboard data during operations.
📦 PR Snap Build Artifacts✅ Snap builds successful! Download artifacts: 🐧 Linux Snap Packagesx86_64 (110.70 MB) arm64 (107.52 MB) armv7l (101.58 MB) 📝 Note: Other package formats (.deb, .rpm, .AppImage, .dmg, .exe) are built in the main workflow |
📦 PR Build Artifacts✅ Build successful! Download artifacts: 🐧 Linuxx86_64 (447.94 MB) - Contains: .deb, .rpm, .tar.gz, .AppImage arm64 (438.17 MB) - Contains: .deb, .rpm, .tar.gz, .AppImage armv7l (416.01 MB) - Contains: .deb, .rpm, .tar.gz, .AppImage 🍎 macOSx86_64 (129.25 MB) - Contains: .dmg 🪟 Windowsx86_64 (109.61 MB) - Contains: .exe installer 📝 Note: Snap packages (.snap) are built in a separate workflow 🕐 Last updated: 2026-05-20 13:08 UTC |
- harness: guard against canvas.toBlob returning null before reading the buffer, so unexpected failures throw a readable error instead of crashing on a property access. - harness: switch Phase 1's image-detection from absolute count to a before/after src-set delta so chat-history images or stale attachments cannot false-positive the SUCCESS verdict. - SPIKE.md: Phase 2 plan now snapshots all clipboard formats (not just image) before clobbering, and the synthetic paste keystroke uses a platform-aware modifier (meta on darwin, control elsewhere). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 0 confirmed Teams compose is CKEditor 5 (data-tid=ckeditor,
hasCkEditor=true) — not ProseMirror/Slate/Lexical as the research
phase guessed. Phase 1 confirmed synthetic ClipboardEvent('paste')
end-to-end: event.defaultPrevented=true (editor consumed the event),
a blob image was inserted, and a real send through Teams's pipeline
delivered the image with read-receipt.
DESIGN.md captures the MVP scope: floating-button-triggered panel
listing image files from a configured folder, click-to-paste via
the spike-verified synthetic-paste path. Off by default, mirrors
the customBackground shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a floating toggle button (visible only when enabled) that opens
a panel listing image files from a configured folder. Clicking a
sticker focuses the chat compose box and dispatches a synthetic
ClipboardEvent('paste') carrying the image as a File — the path the
spike validated end-to-end against Teams's CKEditor 5 compose.
Off by default. Mirrors the customBackground shape (config flag,
main-process module with ipcMain.handle, renderer-side companion
mounted at document.body).
Default sticker folder is <userData>/stickers/, auto-created on
first run when the feature is enabled. Override via
customStickers.folder. Accepted formats default to PNG / JPG /
JPEG / GIF, configurable via customStickers.formats.
closes #2476
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the async #togglePanel() and #sendSticker() click handlers with .catch() so any rejection (IPC failure, DOM throw) surfaces as a debug log rather than an unhandled promise rejection. The internal try/catch blocks in #refreshStickers and #sendSticker cover the common failure paths; this is defense-in-depth for paths outside those wrappers (focus(), dispatchEvent(), DOM mutations). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bundles app/customStickers/example/example-teams-for-linux.png (a copy of the wrapper icon) and copies it into the user's freshly-created sticker folder on first run, so the panel has something to show before the user has dropped their own files in. - Adds docs-site/docs/development/research/custom-stickers-online-import-research.md capturing the design sketch for a future "import from Telegram pack" feature. Not on the roadmap; recorded so the analysis is not lost. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Docusaurus's MDX parser read the bare <pack> in the UX section as an unclosed JSX tag and failed the Build Docusaurus and Test Build (PR/Branch) CI checks. Wrapping in backticks renders it as inline code and stops MDX from treating it as JSX. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|



Summary
Adds the custom stickers feature requested in #2476. A floating toggle button (visible only when enabled) opens a panel listing image files from a configured folder; clicking a sticker focuses the chat compose box and dispatches a synthetic
ClipboardEvent('paste')carrying the image. Teams's editor (CKEditor 5) consumes the event the same way it handles a real paste — validated end-to-end during the feasibility spike on this same branch.Off by default. Mirrors the
customBackgroundshape (config flag, main-process module withipcMain.handle, renderer-side companion mounted atdocument.body). Default sticker folder is<userData>/stickers/, auto-created on first run when the feature is enabled. Override viacustomStickers.folder. Accepted formats default to PNG / JPG / JPEG / GIF, configurable viacustomStickers.formats.The earlier spike commits remain on this branch as historical context. The harness in
spike/2476-stickers/devtools-paste-test.jsis still useful as a debugging tool if the synthetic-paste path ever regresses.Test plan
npm start, sign in.{ "customStickers": { "enabled": true } }. Restart.<userData>/stickers/(path printed in the log at startup).What changed
app/customStickers/— new main-process module withipcMain.handle('get-sticker-list', ...).app/browser/tools/customStickers.js— renderer tool, floating button + panel + synthetic-paste click handler.app/config/index.js— newcustomStickersconfig object (enabled,folder,formats).app/index.js— instantiates and initialisesCustomStickersnext toCustomBackground.app/security/ipcValidator.js— allowlistsget-sticker-list.app/browser/preload.js— registers the renderer tool and marks it as IPC-needing.docs-site/docs/configuration.md, IPC docs, generator category — documentation.spike/2476-stickers/SPIKE.md— Phase 0 + Phase 1 results recorded.spike/2476-stickers/DESIGN.md— design that came out of brainstorming.closes #2476
🤖 Generated with Claude Code