Commit Graph
17 Commits
Author SHA1 Message Date
SalastilandClaude Sonnet 5.5 285fda248f 1.2.1: Steam identity; CHANGELOG
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-15 17:14:16 -04:00
SalastilandClaude Sonnet 5.5 d9f9f6a127 1.2.0: Twitch; README and CHANGELOG
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-14 21:54:30 -04:00
SalastilandClaude Sonnet 5.5 b877529367 1.1.0: Steam fully ported; the README lists the protocols
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 15:49:22 -04:00
SalastilandClaude Sonnet 5.5 02e5aa364f moho 1.0.0
Moho 1.0. Five protocols in one window - IRC, Discord, Matrix, Kick and Sneedchat - over a daemon that keeps them connected; this is everything since the first release candidate: the rebuilt interface and settings, GIFs and link cards on every service, idle detection, the tray, Kick events as cards, conversations that open where you stopped reading, and every Rust test read.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-11 17:26:09 -04:00
SalastilandClaude Sonnet 5.5 9b14536112 moho 1.0.0-rc.5
Fifth release candidate: GIFs on every service with a picker, idle detection, YouTube and GIF cards on every service including over Tor, the tray's number counted by the window, mentions-only counts that survive a mute, and Discord mutes set on the account.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-10 16:37:23 -04:00
SalastilandClaude Sonnet 5.5 dc94dc3aca moho 1.0.0-rc.4
Fourth release candidate: a rebuilt tray, statuses by service, Do not disturb that silences notifications, and fixes to switching servers, Matrix rate limits and mutes, and Discord's connection.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-09 15:21:54 -04:00
SalastilandClaude Sonnet 5.5 c6206958b4 moho 1.0.0-rc.3
Third release candidate: Discord forums, a redesigned interface and settings, the one-box composer, Discord's notices and read-only channels, and window recovery after a crash.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-08 23:51:16 -04:00
SalastilandClaude Sonnet 5.5 25c362a194 moho 1.0.0-rc.2
Second release candidate: YouTube cards for every kind of video, the formatting strip and right-click menu in the message box, typing anywhere, one # per channel row (#237, #265, #266, #267).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-07 17:47:46 -04:00
SalastilandClaude Opus 5.5 f76e58bc22 moho 1.0.0-rc.1
The first release candidate, to rehearse the release flow (#239).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:29:59 -04:00
SalastilandClaude Opus 5.5 53a6f92ae4 Tests for the window: unit tests, and a layout check against the real app
Vitest, with 70 tests across the pure lib modules - formatting (IRC codes,
BBCode, spoilers, emoji and emotes, media and YouTube detection), media URL
resolution including the file-version fragment, the rail and its folders,
mentions, search filters, nick completion, Kick watching, emote and
interruption state - and the media scheme's boundary in the main process,
with Electron stubbed.

scripts/ui-check.mjs runs the built app against a scratch home and a fake
IRC server, opens the emoji picker, the channel, message and member menus
(right-clicked in the window's far corner) and the mentions inbox at two
window sizes, and fails if any of them leaves the window at 80 ms, 400 ms,
1.5 s or 3.5 s. Checked that it fails: with the context menu's clamping
removed it reports every corner-opened menu outside the window.

npm test and the layout check (under xvfb) run in the Linux release job.
#212

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 18:07:08 -04:00
SalastilandClaude Opus 5.5 f67c753845 Emoji and sticker picker laid out as Discord's; Lottie stickers drawn
One picker with Stickers and Emoji tabs where the service has stickers,
opened on whichever tab its button is for. A search box, then a category
rail down the left - recent, each server or pack by its icon, smilies,
standard emoji - that scrolls on its own with the wheel and lights the
section the list is in. Along the bottom, what the pointer is over and
where it is from. Stickers get a Frequently Used row of their own, kept
per account, and larger cells.

Lottie stickers are played with lottie-web's light player (no expressions,
which would need eval) from the copy the daemon keeps, only once on screen:
in chat, moving if media autoplay is on and on hover otherwise; in the
picker, on hover; in the rail, still. moho-media is allowed for fetch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 14:53:57 -04:00
Salastil 932a07ae82 Take Electron 44
Electron 33 is eleven majors and two years behind, and out of support -
the last three majors get fixes, so every Chromium flaw published since
has stood open in the process that renders text, images and attachments
from four networks. Against that, sandboxing the renderer is the
smaller half of the job. Chromium goes 130 to 152.

One thing broke, and it is a real break rather than a deprecation:
Electron 44 replaced the synchronous clipboard with the W3C shape.
`readImage()`, `writeImage()`, `availableFormats()` are gone; `read()`
returns ClipboardItems addressed by MIME type, asynchronously. Pasting
an image into the composer went through readImage(), so it is ported
here - and it now keeps the format it was given instead of re-encoding
everything to PNG, which the old call could not do.

Nothing else moved. The whole tree typechecks, and on a packaged build:
the window renders, the daemon answers with 309 buffers, the bridge
still has its 28 methods, moho-media images decode, WebCodecs offers
VP8, MediaStreamTrackProcessor and AudioEncoder are present,
RTCPeerConnection negotiates, getDisplayMedia and screenSources answer,
and the renderer is still at Seccomp 2.

What none of that covers is a call. The screen share leans on
WebCodecs, RTP timing and a live peer, and eleven majors of WebRTC have
gone past underneath it - so a real Discord stream and a real Matrix
call want testing before this reaches master.
2026-09-22 22:32:51 -04:00
Salastil e791fcf428 Sandbox the renderer, and ship a deb that can keep it
moho's renderer has been running with no OS sandbox at all. `sandbox:
false` is not merely "no Node in the page" - it opts the renderer out
of Chromium's process isolation entirely, which is a strange place for
the process that draws untrusted text, images and attachments from four
networks to be standing. Measured on a build from before this commit:
Seccomp 0, NoNewPrivs 0, the host's own user namespace. After it:
Seccomp 2, NoNewPrivs 1, a namespace of its own.

Nothing had to move to get it. The preload imports `electron` and
nothing else, which is exactly what a sandboxed preload may do, and
reads its popout flag from process.argv, which stays readable - 28
bridge methods, 309 buffers and 43 of 43 moho-media images, same as
before.

The sandbox needs unprivileged user namespaces, and an AppImage cannot
supply them any other way: its mount is nosuid, so the setuid helper is
unreachable, and an AppArmor profile needs an install step an AppImage
by definition does not have. Hence the deb, which has one: it carries a
profile granting `userns` to /opt/moho/moho and a postinst that loads
it, and on a host old enough to lack the AppArmor version for that,
falls back to the setuid helper instead.

Where neither is possible - Ubuntu 24.04 and after, where the launcher
starts the app with --no-sandbox - it runs anyway and says so, once in
the log and once in the window. Refusing to start would leave somebody
with a chat client that will not open and a dialog pointing at a
different download.

The detection is the command-line switch deliberately. `process
.sandboxed` looks like the right answer and is not: it describes a
renderer's own context and reports true under --no-sandbox just the
same, so it would claim a sandbox that is not there. Checked, rather
than assumed.

electron-builder 26 is what makes all of this available, and it fixes
something older than any of it. The AppImage runtime in 25 is dated
June 2019 and dlopens libfuse.so.2, which neither Ubuntu 22.04 nor
Debian 13 installs by default - the reason Electron AppImages have
wanted `apt install libfuse2` for years. The runtime in 26 is static,
has libfuse3 compiled in, and needs only a fusermount binary, which
fuse3 provides and both distributions ship.

Both targets were checked against the two distributions' real package
indexes rather than assumed: every dependency resolves on Ubuntu 22.04
natively, and on Debian 13 through the t64 packages' Provides.

Also here, because 26 changed or exposed them:

- `desktop` takes its entry under `entry` now; the flat form is ignored
  and the launcher would have lost its name and comment.
- `desktopName` plus `syncDesktopName` gives StartupWMClass=moho in
  moho.desktop, so a running window is linked to its launcher entry.
- The package points at github.com/Moho-Chat/moho rather than Gitea,
  which is the copy somebody holding a deb can actually reach.
- `pack` still builds only the AppImage, so `deploy` stays quick;
  `pack:linux` builds both and is what CI calls.
2026-09-22 22:25:35 -04:00
SalastilandClaude Opus 5 4e9f6ddbb2 A LiveKit client, for the calls held on a media server
One stream up and everybody else's back down, rather than a connection
per person - which is what Element's calls are and, past a handful of
people, the only shape that works.

Deliberately not a second implementation of the call: it hands back the
same people-and-streams the mesh does, so the grid, the speaking ring and
the corner view do not know which kind they are drawing, and mute and
screen share reach whichever is up.

Two things learned by hitting them. The renderer's own CSP refused the
websocket - a signalling connection is neither http nor https - so `wss:`
is allowed now, and plaintext only to this machine, since a server across
a network should not be carrying signalling in the clear. And the stage
now goes up before the joining rather than after it: connecting to a
media server takes a moment, and setting the phase afterwards overwrote
the "connected" the call had already reported.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-06 20:45:59 -04:00
SalastilandClaude Opus 5 9e3fa49321 Watch the stream in the window that has the chat
A Kick channel is a stream as much as a chat, and the video was in a
browser tab beside the window that had the conversation in it.

Not a player of its own. The frame a call draws into is now its own
thing - VideoStage - and a call and a stream are two sources feeding it:
the same pane, the same corner it retreats to, the same minimise that
puts the picture down without ending it, the same sizing. What differs
is the pipe. A call is real-time media that arrives as a MediaStream on
srcObject; a stream is one-to-many HLS off a CDN, which needs Media
Source Extensions and so needs hls.js, and that is the only piece here
a call has no use for.

Receiving only, and one at a time: nothing broadcasts, nobody else is in
it, and starting a stream during a call is refused rather than fought
over - the picture has one place to go.

Offered where the channel is, by right-clicking it, and only while there
is something to watch: an offline channel's playlist is a signed URL to
nothing, and an entry that fails a few seconds after being pressed is
worse than one that isn't there. The same menu is how it stops.

Capped below 1080p60. That rendition is 5.8 Mbps of 60fps decode beside
a chat client, and the ladder underneath it still adapts. A refused
segment is read as an expired token rather than a dead stream, and
answered by loading whichever playback URL the daemon has since sent.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-06 00:58:07 -04:00
Salastil f74b0a93e2 Align grouped messages in comfy mode with their own first line
Comfy mode only rendered the avatar for the first message of a group, so the
follow-ups had nothing holding that column open and slid left into it. A
group's second and third lines therefore sat out of line with its own first
line - every other client keeps a group's text on one left edge. Wrapped lines
never showed the problem, because a wrap happens inside message-content, which
was already positioned correctly.

Grouped rows now render the avatar slot too, just empty. An empty slot would
otherwise still claim its full 28px of height, padding every follow-up line
with about 9px of dead space, so it collapses vertically while keeping its
width.
2026-08-22 11:28:23 -04:00
Salastil 268751e5d0 Initial Commit 2026-08-21 14:04:58 -04:00