Skip to content

fix(telegram): show copyable /start command for pairing#2584

Merged
koala73 merged 5 commits intomainfrom
fix/telegram-pairing-show-command
Mar 31, 2026
Merged

fix(telegram): show copyable /start command for pairing#2584
koala73 merged 5 commits intomainfrom
fix/telegram-pairing-show-command

Conversation

@koala73
Copy link
Copy Markdown
Owner

@koala73 koala73 commented Mar 31, 2026

Problem

Telegram desktop ignores ?start=TOKEN for bots the user has already started. The deep link opens the bot but sends just /start with no payload — claimPairingToken never matches, channel never pairs.

Fix

Show the /start TOKEN command inline with a Copy button. User clicks the bot link to open Telegram, pastes the command and sends. Works on all clients.

Post-Deploy Validation

Send copied command to bot → should receive welcome message.

…able deep link

Telegram desktop ignores ?start=TOKEN for already-started bots — the
deep link opens the conversation but sends /start without the payload.

Replace the "Open Telegram" button UI with a copyable /start TOKEN command
so users can paste it directly into the bot, which works on all clients.

The deep link on the bot username is kept as a convenience to open
Telegram, but pairing no longer depends on the start parameter being
forwarded.
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

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

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Mar 31, 2026 8:01pm

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 31, 2026

Greptile Summary

This PR fixes a known Telegram deep-link limitation: desktop clients that have already started the bot silently strip the ?start=TOKEN payload, so claimPairingToken never fires and the channel never pairs. The fix replaces the standalone "Open Telegram" button with an inline bot link plus a visible /start TOKEN command the user can copy and paste — a reliable fallback that works on all Telegram clients.

Key changes:

  • preferences-content.ts: The "Waiting for pairing…" message is replaced with a <code> element showing the full /start TOKEN command and a copy <button>. A new delegated click handler on .us-notif-tg-copy-btn reads data-cmd (HTML-entity-decoded automatically by the browser) and writes it to the clipboard, toggling the button label to "Copied!" for 2s.
  • settings-window.css: Four new BEM-style class selectors style the inline link, command row flex container, monospace token code block, and the copy button. All use existing CSS custom properties with sensible fallbacks.
  • All dynamic values written via innerHTML are passed through the existing escapeHtml utility — no XSS surface is opened. The data-cmd HTML entity round-trip is correct.
  • One minor UX gap: clipboard.writeText failures are silently swallowed, leaving the button label unchanged.

Confidence Score: 5/5

Safe to merge — the fix is correct, XSS hygiene is maintained, and the one open item is a non-blocking UX polish.

All remaining findings are P2 style/UX suggestions (silent clipboard failure feedback). No correctness, data-integrity, or security issues were found. escapeHtml is applied consistently to all dynamic content in the innerHTML template, and the data-cmd to dataset.cmd HTML-entity round-trip is handled correctly by the browser.

No files require special attention.

Important Files Changed

Filename Overview
src/services/preferences-content.ts Adds inline /start TOKEN display with a Copy button to fix Telegram deep-link pairing. All dynamic values are properly escapeHtml-escaped in the innerHTML template; dataset.cmd correctly returns the HTML-decoded token for the clipboard. One minor UX gap: clipboard failure is swallowed silently.
src/styles/settings-window.css Adds CSS for .us-notif-tg-inline-link, .us-notif-tg-cmd-row, .us-notif-tg-cmd, and .us-notif-tg-copy-btn. Styles are clean, use existing CSS variable tokens with appropriate fallbacks, and handle text overflow on the command code element correctly.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as Settings UI
    participant API as /api/notification-channels
    participant TG as Telegram

    User->>UI: Clicks "Connect Telegram"
    UI->>API: POST create-pairing-token
    API-->>UI: { token, expiresAt }
    UI-->>User: Show inline link + /start TOKEN + Copy button

    alt User clicks Copy button
        User->>UI: Clicks "Copy"
        UI->>UI: navigator.clipboard.writeText("/start TOKEN")
        UI-->>User: Button shows "Copied!" for 2s
        User->>TG: Opens bot link, pastes & sends /start TOKEN
        TG-->>API: Bot receives /start TOKEN payload
        API->>API: claimPairingToken(TOKEN)
        API-->>UI: Channel verified (poll detects)
        UI-->>User: Pairing complete
    else Token expires
        UI->>UI: Countdown reaches 0, poll stops
        UI-->>User: Reload pairing section
    end
Loading

Reviews (1): Last reviewed commit: "fix(telegram): show /start command with ..." | Re-trigger Greptile

Comment on lines +814 to +817
navigator.clipboard.writeText(cmd).then(() => {
btn.textContent = 'Copied!';
setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
}).catch(() => {});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Silent clipboard failure gives no user feedback

When navigator.clipboard.writeText is rejected (e.g. clipboard permission denied, insecure context outside Tauri), the .catch(() => {}) swallows the error completely. The button label never changes from "Copy", so the user sees no reaction and may assume the click didn't register.

Consider surfacing a brief error state so the user knows to copy manually:

Suggested change
navigator.clipboard.writeText(cmd).then(() => {
btn.textContent = 'Copied!';
setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
}).catch(() => {});
navigator.clipboard.writeText(cmd).then(() => {
btn.textContent = 'Copied!';
setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
}).catch(() => {
btn.textContent = 'Failed';
setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
});

Generates an inline SVG QR code (uqr, no canvas required) encoding the
t.me deep link so mobile users can scan instead of copy/paste. Desktop
users still have the copy command + Open Telegram button.

Layout: QR on the left (88×88), copy row + Open Telegram on the right.
- Open Telegram is now the primary action (button first)
- Copy /start command is the visible fallback, always shown
- QR code moves to right side as mobile convenience (not primary path)
- Polling never calls reloadNotifSection on expiry; instead renders an
  in-place expired state with Generate new code button so users don't
  lose context
- Generate new code and Try again re-use the same startTelegramPairing
  handler without backing out
- Countdown shows Waiting... Ns instead of bare Ns
- Clipboard fallback via execCommand when navigator.clipboard unavailable
- Loading state shows Generating code... while token is being created
Two bugs from review:
- navigator.clipboard may be undefined; accessing .writeText on it threw
  synchronously before .catch could run
- document.execCommand('copy') without a selected element copies nothing

Fix: guard with navigator.clipboard?.writeText, and in the fallback create
a temporary off-screen textarea, select its content, then execCommand.
@koala73 koala73 merged commit 9b2c944 into main Mar 31, 2026
7 checks passed
@koala73 koala73 deleted the fix/telegram-pairing-show-command branch March 31, 2026 20:02
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