feat(ui-flow): attachment download, forward, and viewer IPC routes

Wire cancel-attachment-download, forward-attachment, open-attachment-in-viewer,
and attachment-download-progress polling for Phase 665 media gallery UX.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-02 19:50:52 -04:00
co-authored by Cursor
parent 04ad8a9e62
commit c0a2e03c8f
2 changed files with 48 additions and 0 deletions
+2
View File
@@ -2,6 +2,8 @@
Pearcord desktop UI IPC dispatch and automated flow scenarios for Bare smokes. Mirrors `apps/pearcord` pear-pipe message handling.
**Phase 665 batch 2 (v0.8.640):** IPC `cancel-attachment-download`, `forward-attachment`, `open-attachment-in-viewer`; download progress polling via `attachment-download-progress` event. Bundle: `npm run test:phase665-media-batch2`.
**v0.8.638 (Phase 659):** Added DM poll IPC routes: `vote-dm-poll` (optional `voted` desired-state) -> `dm-poll-voted`, `edit-dm-poll` -> `dm-poll-edited`, `close-dm-poll` -> `dm-poll-closed`, and `delete-dm-poll` -> `dm-poll-deleted`.
**v0.8.638 (Phase 659):** Added DM scheduled-message IPC routes: `create-dm-scheduled-message`, `list-dm-scheduled-messages`, `cancel-dm-scheduled-message`, and `send-now-dm-scheduled-message`.
+46
View File
@@ -1772,13 +1772,29 @@ async function dispatchUiMessage (platform, msg, hooks = {}) {
/* pipe closed */
}
}
let progressTimer = null
try {
progressTimer = setInterval(() => {
const p = platform.attachments?.getFetchProgress?.(attachmentId)
if (!p) return
try {
send({
type: 'attachment-download-progress',
attachmentId,
progress: Number(p.progress) || 0,
status: p.status || 'fetching'
})
} catch {
/* pipe closed */
}
}, 180)
const exported = await Promise.race([
platform.exportAttachmentForDownload(attachmentId),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('download timed out')), downloadTimeoutMs)
})
])
if (progressTimer) clearInterval(progressTimer)
if (!exported?.filePath) throw new Error('attachment file empty')
if (maxInlineBytes > 0 && exported.size > maxInlineBytes) {
try {
@@ -1810,10 +1826,40 @@ async function dispatchUiMessage (platform, msg, hooks = {}) {
sendDownloadError(err?.message || String(err))
}
} catch (err) {
if (progressTimer) clearInterval(progressTimer)
sendDownloadError(err?.message || String(err))
}
return true
}
if (t === 'cancel-attachment-download') {
platform.cancelAttachmentDownload?.(msg.attachmentId)
return true
}
if (t === 'forward-attachment') {
const out = await platform.forwardAttachment({
messageId: msg.messageId,
targetChannelId: msg.targetChannelId,
attachmentIds: msg.attachmentIds || null
})
pushState()
send({ type: 'show-toast', message: 'Attachment forwarded' })
return out
}
if (t === 'open-attachment-in-viewer') {
const exported = await platform.exportAttachmentForDownload(msg.attachmentId)
if (!exported?.filePath) throw new Error('attachment file missing')
const os = require('bare-os')
const child = require('bare-subprocess')
const cmd =
os.platform() === 'darwin'
? 'open'
: os.platform() === 'win32'
? 'explorer'
: 'xdg-open'
child.spawn(cmd, [exported.filePath], { detached: true, stdio: 'ignore' })
send({ type: 'show-toast', message: `Opened ${exported.filename || 'attachment'}` })
return true
}
if (t === 'upsert-guild-emoji') {
await platform.upsertGuildEmoji({
name: msg.name,