Skip to content

Add Cmd+O open links and attachments palette#180

Open
mickn wants to merge 8 commits into
ankitvgupta:mainfrom
mickn:codex/open-links-attachments
Open

Add Cmd+O open links and attachments palette#180
mickn wants to merge 8 commits into
ankitvgupta:mainfrom
mickn:codex/open-links-attachments

Conversation

@mickn

@mickn mickn commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a Cmd+O palette for the currently focused email's links and attachments
  • extract http/https links from anchors and bare text, excluding mailto links and collapsing duplicate URLs

Validation

  • npm run typecheck
  • ESLint on the touched source and regression test
  • npm run build
  • EXO_DEMO_MODE=true npx playwright test --project=unit tests/unit/open-links-attachments.spec.ts (4 passed)
  • EXO_DEMO_MODE=true npx playwright test --project=e2e tests/e2e/open-links-attachments.spec.ts (8 passed)
  • exact regression using the live email's two Greenhouse URLs separated by <br>, dividers, Source, and the heart

Open in Devin Review

@mickn
mickn marked this pull request as draft July 1, 2026 03:07
devin-ai-integration[bot]

This comment was marked as resolved.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a Cmd+O palette for quickly opening links and attachments from the currently focused email, and refactors the three palettes (Command, Agent, Links/Attachments) to share a new PaletteShell component with common selection and focus plumbing.

  • New OpenLinksAttachmentsPalette: extracts http/https links from anchor elements and bare-text nodes (deduplicating via normalized url.href), merges with downloadable attachments, and supports keyboard-numbered shortcuts (Cmd+1–9), query filtering, and inline attachment preview.
  • PaletteShell extraction: usePaletteSelection, PaletteHeader, PaletteResults, and PaletteFooter are factored out and adopted by AgentCommandPalette and CommandPalette, eliminating duplicated index/scroll/focus logic.
  • Cross-cutting fixes: formatPlatformShortcut replaces hardcoded "Cmd+" strings in several components; APP_SHORTCUT_MODIFIER_KEYS is exported and used to forward both the new Cmd+O and the existing Cmd+J shortcut from the email-body iframe.

Confidence Score: 5/5

Safe to merge; the new palette, link extraction, and keyboard routing are all well-scoped with no data-loss paths.

The implementation is thorough: deduplication, Escape-with-preview, iframe forwarding, and the PaletteShell refactor all behave correctly. The two noted items are narrow UX edge cases that do not break existing functionality.

OpenLinksAttachmentsPalette.tsx — the two edge cases both live here.

Important Files Changed

Filename Overview
src/renderer/components/OpenLinksAttachmentsPalette.tsx New palette component; Escape-with-preview and preview-close correctness are solid, but Cmd+1–9 shortcuts are not suppressed while a preview is showing, and empty-body emails trigger repeated IPC loads.
src/renderer/utils/openables.ts New utility: extracts deduplicated http/https links from email HTML (anchors + bare-text scan) and maps attachments to OpenableItem; trimUrlCandidate is correctly applied only to bare-text matches.
src/renderer/components/PaletteShell.tsx New shared palette primitives extracted cleanly; used by all three palettes.
src/renderer/hooks/useKeyboardShortcuts.ts Cmd+O handler added consistently alongside Cmd+J/K; APP_SHORTCUT_MODIFIER_KEYS export correctly includes 'j' and 'o' to forward those combos from the email-body iframe.
src/renderer/utils/attachments.ts formatFileSize and isPreviewable extracted here; new version adds Math.min cap.
src/renderer/components/EmailDetail.tsx iframe keydown forwarding now uses the shared APP_SHORTCUT_MODIFIER_KEYS set.
src/renderer/components/AgentCommandPalette.tsx Refactored to use PaletteShell primitives; behaviour unchanged.
src/renderer/store/index.ts isOpenLinksAttachmentsOpen and open/closeLinksAttachments added; integrated into getKeyboardMode.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant KS as useKeyboardShortcuts
    participant S as Zustand Store
    participant P as OpenLinksAttachmentsPalette
    participant IPC as window.api.gmail
    participant OL as openables.ts

    U->>KS: Cmd+O keydown
    KS->>S: openLinksAttachments()
    S-->>P: "isOpen=true"
    P->>P: reset query, focus input

    alt storeEmail has body
        P->>OL: buildOpenables(storeEmail)
        OL-->>P: OpenableItem[]
    else body missing / empty
        P->>IPC: getEmail(sourceEmailId)
        IPC-->>P: DashboardEmail (full body)
        P->>OL: buildOpenables(loadedEmail)
        OL-->>P: OpenableItem[]
    end

    P-->>U: render links + attachments list

    alt User selects link
        P->>S: closeLinksAttachments()
        P->>U: window.open(url, _blank)
    else User selects previewable attachment
        P->>IPC: attachments.preview(...)
        IPC-->>P: base64 data
        P-->>U: AttachmentPreviewModal
        U->>P: Escape
        P->>P: setPreviewAttachment(null)
    else User selects downloadable attachment
        P->>IPC: attachments.download(...)
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant KS as useKeyboardShortcuts
    participant S as Zustand Store
    participant P as OpenLinksAttachmentsPalette
    participant IPC as window.api.gmail
    participant OL as openables.ts

    U->>KS: Cmd+O keydown
    KS->>S: openLinksAttachments()
    S-->>P: "isOpen=true"
    P->>P: reset query, focus input

    alt storeEmail has body
        P->>OL: buildOpenables(storeEmail)
        OL-->>P: OpenableItem[]
    else body missing / empty
        P->>IPC: getEmail(sourceEmailId)
        IPC-->>P: DashboardEmail (full body)
        P->>OL: buildOpenables(loadedEmail)
        OL-->>P: OpenableItem[]
    end

    P-->>U: render links + attachments list

    alt User selects link
        P->>S: closeLinksAttachments()
        P->>U: window.open(url, _blank)
    else User selects previewable attachment
        P->>IPC: attachments.preview(...)
        IPC-->>P: base64 data
        P-->>U: AttachmentPreviewModal
        U->>P: Escape
        P->>P: setPreviewAttachment(null)
    else User selects downloadable attachment
        P->>IPC: attachments.download(...)
    end
Loading

Reviews (6): Last reviewed commit: "Merge branch 'main' into codex/open-link..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

@mickn
mickn force-pushed the codex/open-links-attachments branch from 9985c19 to e2d0c20 Compare July 1, 2026 04:35
mickn and others added 3 commits July 1, 2026 00:42
- Keep balanced parens in bare-text URLs (e.g. Wikipedia links) while
  still trimming surrounding punctuation; cover both cases in e2e tests
- Handle Cmd+O before the input-focus/mode bails so it works in the
  same focus states as Cmd+K/J/F
- Replace the hardcoded iframe shortcut allowlist in EmailDetail with a
  shared APP_SHORTCUT_MODIFIER_KEYS set, and forward Cmd+J too
- Extract shared PaletteShell/usePaletteSelection and use them in
  CommandPalette, AgentCommandPalette, and OpenLinksAttachmentsPalette
- Finish the platform shortcut-label migration (settings/help entries,
  ComposeToolbar, AgentPanel, EmailPreviewSidebar, undo toasts) so
  non-Mac UIs no longer mix "Cmd+" and "Ctrl+" labels
- Restore KeyboardHints module-scope constants; drop a dead finally
  guard in the palette load effect

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mickn
mickn marked this pull request as ready for review July 7, 2026 16:21

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread src/renderer/hooks/useKeyboardShortcuts.ts Outdated
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