Skip to content

feat(#2476): custom stickers MVP#2550

Draft
IsmaelMartinez wants to merge 8 commits into
mainfrom
feat/2476-custom-stickers-spike
Draft

feat(#2476): custom stickers MVP#2550
IsmaelMartinez wants to merge 8 commits into
mainfrom
feat/2476-custom-stickers-spike

Conversation

@IsmaelMartinez
Copy link
Copy Markdown
Owner

@IsmaelMartinez IsmaelMartinez commented May 17, 2026

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 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.

The earlier spike commits remain on this branch as historical context. The harness in spike/2476-stickers/devtools-paste-test.js is still useful as a debugging tool if the synthetic-paste path ever regresses.

Test plan

  • Pull the branch, npm start, sign in.
  • Add this to your config: { "customStickers": { "enabled": true } }. Restart.
  • A small floating button (slightly-smiling-face emoji) appears bottom-right.
  • Drop a few PNGs / JPEGs / GIFs into <userData>/stickers/ (path printed in the log at startup).
  • Click the button. The panel opens with a 4-column grid of thumbnails.
  • Open a 1:1 chat, click into the compose box.
  • Click a sticker. It lands in compose.
  • Press Enter. The image sends as a real attachment.
  • Empty-folder case: rename the folder away, open the panel — empty-state message shows the resolved path.
  • Compose-not-focused case: click a sticker without clicking into compose first — toast tells you to focus compose.

What changed

  • app/customStickers/ — new main-process module with ipcMain.handle('get-sticker-list', ...).
  • app/browser/tools/customStickers.js — renderer tool, floating button + panel + synthetic-paste click handler.
  • app/config/index.js — new customStickers config object (enabled, folder, formats).
  • app/index.js — instantiates and initialises CustomStickers next to CustomBackground.
  • app/security/ipcValidator.js — allowlists get-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

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>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread spike/2476-stickers/devtools-paste-test.js
Comment thread spike/2476-stickers/devtools-paste-test.js Outdated
Comment thread spike/2476-stickers/SPIKE.md Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

📦 PR Snap Build Artifacts

Snap builds successful! Download artifacts:

🐧 Linux Snap Packages

x86_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

View workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

📦 PR Build Artifacts

Build successful! Download artifacts:

🐧 Linux

x86_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

🍎 macOS

x86_64 (129.25 MB) - Contains: .dmg

🪟 Windows

x86_64 (109.61 MB) - Contains: .exe installer


📝 Note: Snap packages (.snap) are built in a separate workflow

View workflow run

🕐 Last updated: 2026-05-20 13:08 UTC

IsmaelMartinez and others added 3 commits May 17, 2026 10:39
- 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>
@IsmaelMartinez IsmaelMartinez changed the title spike(#2476): custom-stickers paste-feasibility harness feat(#2476): custom stickers MVP May 20, 2026
IsmaelMartinez and others added 4 commits May 20, 2026 11:58
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>
@sonarqubecloud
Copy link
Copy Markdown

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.

[Feat]: Custom stickers support

1 participant