- Matrix: interactive device verification (SAS emoji flow), recovery-key
backup/restore, membership announcement moderation, member/room avatars,
power-level-aware moderation menu, and an encryption lock indicator on
the composer - verified live with two independent devices.
- Replace the single combined join panel with a dedicated page per
protocol (IrcJoinPanel/MatrixJoinPanel/DiscordJoinPanel), routed by
whichever account the "+" button was clicked for rather than listing
every account in one expandable page.
- Discord: friends list (Online/All, click-to-DM, add-friend, matches
READY's relationships + live PRESENCE_UPDATE), create-a-server, and
join-by-invite alongside the existing DM-by-ID flow.
- Discord: an expired attachment-link fallback ("Open in Discord") for
scrollback media whose signed cdn.discordapp.com URL has lapsed -
Discord's own /attachments/refresh-urls re-sign endpoint hard-rejects
this backend's user-token requests, confirmed live, so this opens the
real message instead of showing a broken embed.
- Discord: detect gateway close code 4004 (revoked token) as a distinct
ConnState::AuthFailed instead of retrying a dead credential forever;
the Accounts pane shows a clear "needs login" state that routes
straight to a fresh QR login, which upserts the same account id in
place (no lost buffers/scrollback).
- Fix the unread-DM pinned-buffer flash never reliably triggering: an
implicit property binding computed via nested closures over two
separate properties wasn't consistently re-evaluated by QML's
dependency tracker; replaced with an explicit recompute function called
at every actual mutation point.
- Fix ChatBarWidget.qml's pill double-padding against BasePill's own
padding.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- New chatd-rs backend/matrix module: m.login.password auth, /sync-based
multi-room connect loop, and full E2EE via matrix-sdk-crypto (device key
upload, Megolm session establishment, encrypt/decrypt) - verified live
against real homeservers with two independent devices exchanging an
encrypted message neither one decrypted itself.
- Reactions, edits, redactions, replies, and media upload/download, mapped
onto the existing Reaction/Embed/ReplyPreview model.
- Frontend wiring: MatrixConnectForm.qml, AccountsPanel/ChatView hookup,
and the real addMatrixAccount RPC handler (replacing the stub).
- Fix messages doubling in the UI: the Matrix backend never called
Runtime::set_own_identity, so every message - including the account's
own - was stored with is_own=false, defeating the frontend's optimistic-
send reconciliation.
- Fix accounts getting stuck showing "connecting" forever: the Connected
state transition was gated on this being the account's very first-ever
sync (next_batch.is_none()), which is never true again once a cursor is
persisted across a restart - a real account could sync successfully
forever and never have its state announced as connected.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- Bundle Sneedchat's 175 site smilies as local assets instead of fetching
live over Tor; render them full-size in the chat buffer and emoji picker,
with animated (gif/webp) smilies looping via a separate AnimatedImage row
since Qt rich-text <img> can only render a static first frame.
- Add Discord message reactions: toggleReaction RPC, per-message reaction
pills sized to match Discord's own client, a shared reaction/emoji-picker
popup anchored and edge-clamped to the message row, and a row-wide hover
toolbar (reactions/reply/more) gated to Discord only.
- Extend the row hover highlight to every protocol, not just Discord, since
it's generic UX polish with nothing protocol-specific about it.
- Restyle the message context menu with per-item icons and Discord-style
colored entries (e.g. red Delete), using anchored icon+text layout instead
of RowLayout/MenuItem auto-sizing for reliable alignment across items.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
qu.ax stopped reliably serving uploaded images back out. postimg.cc has
no documented API, so its anonymous upload flow was reverse-engineered
from a real browser session (HAR capture): the upload POST 403s as
"automated" without Origin/Referer headers, and the response only
returns the result page URL, not a direct image link, so the page is
fetched and scraped for the real (server-normalized) filename.
- http.rs: thread optional extra headers through the request path, add
post_multipart for a multi-field body (postimg.cc needs gallery/
optsize/expire/numfiles/upload_session alongside the file - the old
post_multipart_file only ever sent a bare file)
- mod.rs: new postimg.cc upload/scrape implementation, wraps as
[url=page][img]direct[/img][/url] matching its own "Thumbnail for
forums" BBCode preset; images only (png/jpg/gif/webp/bmp), since
postimg.cc doesn't host video the way qu.ax did
- form.rs: expose attr() for reuse when scraping the result page
- Fixed a live bug where the result page's outer wrapper div repeats
data-image= without the filename attributes, before the real one
Live-verified end-to-end in #fishtank (test messages since deleted).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- Sneedchat: detect a reconnect that silently lands as a guest (server's
own can_send permission flag) and force a session refresh + relogin;
add an idle-read timeout so a dead websocket is actually noticed
- Sneedchat: multipart file upload support in http.rs, wired into
sendMessage's attachment path (qu.ax-hosted images/video)
- Discord-style optimistic message sends: dim pending messages, resolve
to the real server echo, turn red and offer Retry on failure/timeout
- MessageList: fix scroll-to-end/New-divider landing, anchored-scroll
reliability during scrollbar drags, and BBCode [img][/img] tags
merging into one URL when posted back-to-back
- MediaEmbed: real audio output device, working scrub-bar seek/pan,
restart-from-start on a finished video
- DaemonStatusSettings: chatd connection indicator + build (git branch)
badge for DMS's plugin manager pane
- BufferList: scope the "+" join-channel affordance to IRC accounts only
- DisplaySettingsPanel: new toggle for content-sniffing unrecognized
media links
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- Persist the last-viewed channel and collapsed sidebar groups (accounts,
Discord guilds/DMs, pinned) across closing and reopening the chat
window, which recreates these views from scratch rather than just
hiding them.
- Add one line of breathing room between the last message and the
composer bar.
- Fix BBCode-wrapped image links (Sneedchat's own "[url=X][img]X[/img]
[/url]" convention) not embedding: the wrapper wasn't being collapsed
before extraction, so the bare-URL scan swallowed the trailing [/url]
into the "URL" and broke the extension check. Also harden that scan
against any other bracket-adjacent markup, and recognize known
extensionless image hosts (e.g. ShareX-style upload hosts).
- Resolve Kiwi Farms attachment links (which encode their extension in
the slug, not as a real URL suffix) by fetching them through the
account's Tor transport - using the onion address specifically, since
kiwifarms.st's clearnet edge is both unstable and tends to mishandle
Tor exit traffic - caching to /tmp, and patching the message body with
the local path once ready. Required teaching the frontend's URL
detection about file:// links and giving message-body rewrites a way
to update in place without showing a spurious "(edited)" label.
- Fix Sneedchat messages failing to send (while still receiving fine) on
a room whose connection had recently been re-established: the old
connection's delayed cleanup could remove a newer, live sender for the
same room out from under it. Sender removal now checks it's still the
same channel before removing it.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- Move the ~17 top-level QML files into views/, settings/, and forms/
subdirectories, grouped so cross-directory type references stay minimal
(only 3 files need an added `import` line). Root now only holds the
four surfaces plugin.json points at directly.
- Add a `dms ipc call dankChat popout` IPC target to open the floating
window directly, alongside the existing `toggle`, and document both in
the README along with the new directory layout.
- Add one line of breathing room between the last message and the
composer bar via the message ListView's bottomMargin (measured with
FontMetrics rather than a guessed pixel value).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- Render Sneedchat's BBCode (bold/italic/underline/strike/color/quote/
spoiler/code/img/video/list) into the same conventions MessageList's
formatter already understands, and fix multi-line messages collapsing
onto one line under Text.StyledText (raw \n isn't a line break there).
- Bound unbounded memory growth: periodic avatar-cache eviction by size,
periodic scrollback pruning + incremental vacuum, and a per-buffer
in-memory message cap in the frontend.
- Add a Sneedchat settings panel with per-channel enable/disable toggles.
- Make Tor its own settings category instead of a Sneedchat subsection,
since there's a single daemon-wide embedded Tor client, not one per
account. TorManager is now resettable (RwLock instead of OnceCell),
with "regenerate circuit" (fast, reuses cached consensus) and "restart
from scratch" (wipes cache, cold bootstrap) actions.
- Add a per-IRC-account "tunnel through Tor" toggle, off by default,
routing through an external SOCKS5 proxy via the irc crate's native
proxy support (the embedded Arti client has no public hook the irc
crate can use directly, so this needs a real Tor daemon/Tor Browser).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Rust daemon (chatd-rs) speaking IRC, Discord, and Sneedchat (SockChat over
Tor) behind a shared Unix-socket JSON-RPC/event protocol, plus the QML
frontend (accounts, buffers, messages, settings) that drives it. The
earlier libpurple-based C daemon and its vendored/submoduled protocol
plugins have been fully superseded and are not carried into this history.
Co-Authored-By: Claude Sonnet 5 <[email protected]>