Commit Graph
18 Commits
Author SHA1 Message Date
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 a6fd981b6f Twitch: no third-party emote services; channel emotes greyed in the picker
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-14 19:52:54 -04:00
SalastilandClaude Sonnet 5.5 5356ddba55 Twitch: emotes and badges drawn, moderation and whispers in the window, README
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-14 09:18:31 -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
Salastil f046e28a37 Revise README for protocol updates and clarity
Updated README to reflect changes in protocol support and features.
2026-10-11 20:19:55 -04:00
Salastil 6d4545f5ec Add showcase screenshot to README
Added a showcase screenshot to the README.
2026-10-11 20:11:14 -04:00
SalastilandClaude Sonnet 5.5 d99ad2ba68 Documentation brought up to date: the settings pages, how a conversation opens, presence and the tray, link cards and Kick events, Discord mutes set on the account, the tests and the tidy step, and the daemon described as it is rather than as it was meant to be
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-11 00:30:08 -04:00
SalastilandClaude Opus 5.5 d9a9d0c722 Versions only: tag-built releases with a changelog, Gitea mirrored, an update notice (#239)
- The rolling "Latest build" is retired. release.yml (was rolling-release)
  still builds and tests every push to master, as the check that it is
  releasable, and publishes only for a v* tag. A pre-release suffix
  (v1.0.0-rc.1) publishes as a pre-release, never GitHub's "Latest".
- A release page is the version's section of CHANGELOG.md - major
  features first, then fixes, patches and changes, one line each linked to
  its commit - and the downloads table. A version with no section is not
  published; a release candidate uses its release's section unless it has
  its own. Daemon binaries are kept from a tag's run for nobilis's release.
- CHANGELOG.md, with the 1.0.0 section drafted: the major features, and
  the fixes since the first public build, every link checked against its
  repository.
- scripts/changelog-deps.mjs lists direct dependency changes between two
  releases, linked to the commit that made each, net of anything the range
  put back as it found it.
- scripts/mirror-release.sh, run by hand once GitHub has published: moho's
  release copied to Gitea with its files, and nobilis's release made on both
  hosts - tagged at moho's pointer, its notes the major features and the
  nobilis lines, with the two daemon binaries.
- A newer moho is announced at the top of the mentions inbox, counted in
  its badge, until dismissed (per version) - checked on connect and daily
  through the daemon, so through Tor or the proxy when anything is routed.
  Settings > General > Updates turns it off.
- README: downloads from the latest release, and the versioning scheme.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:22:11 -04:00
SalastilandClaude Opus 5.5 5a8d7f7ad8 Windows: unsigned by decision; the installer warns about Smart App Control (#264)
moho will not be code-signed. Windows 11's Smart App Control refuses to
start an unsigned program at all - moho installs, then never opens, with
no message - while the installer itself is let through. So the installer
now reads Smart App Control's state and, when it is on, says before
installing that moho will not start, where to turn it off, and that
Windows will not let it back on without a reset; OK installs anyway,
Cancel leaves nothing behind.

The README and the rolling release's notes say the same, with the
SmartScreen "More info > Run anyway" / Unblock step.

Checked on the Windows VM (Smart App Control enforcing): the warning comes
before the first page, and Cancel quits with nothing installed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 01:52:02 -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 8d0eb09702 README and ARCHITECTURE: the client as it is now
Discord calls, streams and the no-captcha decision; Kick's player; Matrix
cross-signing and forwarding; Sneedchat on the open internet; the right
socket and cache paths; XMPP and Slack out. #238

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 00:14:13 -04:00
SalastilandClaude Opus 5 f965642a55 A camera that can be turned on in a call already running
The missing half of #189. A call placed as a video call already opened a
camera; a call placed as a voice call could never grow one, and the
button was hidden unless the call was on a media server - so on the two
shapes of Matrix call that do not use one, there was no camera at all.

Added and removed rather than enabled and disabled. A disabled track
keeps sending - a black rectangle at whatever frame rate the encoder
feels like - so the far end sees a dark square where a face was and
cannot tell that from a camera that has frozen. Removing the track ends
it properly, and every other client then draws the person's name. The
loopback harness below measures exactly this difference: with the old
enabled=false the far end still counts one live video track after the
camera is switched off, and with the track removed it counts none.

One gesture for all three shapes of call - a media server, a mesh
between several people, a single connection between two - because the
button is one button and the person pressing it is asking one thing.
Which shape this is belongs to the call, not to the control. On a mesh
the camera is still opened once and lent to every leg, since five
connections asking the machine for the same camera get one capture and
four refusals.

Renegotiations are now serialised. Turning a camera on and sharing a
screen a second later are two changes to one connection, and starting
the second offer while the first is unanswered leaves a peer connection
neither end can recover from. A remote track that is taken away is
dropped from the stream it arrived on, so a tile stops drawing the last
frame it received - Chromium reports that as a mute before it reports it
as an end, and on a removed track the mute is often all there is.

cameraOn is asked of the call rather than assumed, in both branches:
that block runs on every phase change, and a fixed answer put the button
back to "off" the moment a call connected.

The call UI is unchanged in shape and deliberately not Matrix-shaped -
Discord's camera (#180) and screen share (#14) get the same row of
buttons when they arrive.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-13 18:35:29 -04:00
SalastilandClaude Opus 5 c0ff53e833 A front page that reads like an invitation
The README opened with "a unified desktop chat client" and a paragraph about
the UI only ever seeing a service-agnostic account/buffer/message model. That
is a true and useful sentence to a maintainer and the wrong first sentence for
somebody deciding whether to download this: 223 lines in, the wire protocol
and the on-disk layout came before anything about what it is like to use.

So the README is now a page: what moho is, what it does, where to get it, and
the four commands to build and run it. Everything else moved to
ARCHITECTURE.md rather than being deleted - the daemon split, the JSON-RPC
surface, what each backend actually implements, where state lives - because
none of it was wrong, it was only in the wrong place.

Three of the old build instructions were wrong, which is the other reason to
rewrite rather than trim. `npm run pack:appimage` is not a script. There is no
pacman target, so `pacman -U dist/moho-0.1.0.pacman` installs nothing; the
PKGBUILD is the Arch path. And builds have been named after their commit since
the AppImages started accumulating, so `moho-0.1.0.AppImage` is a filename
that no longer occurs. Every command the new page names is checked against
package.json.

The prerequisites are named now, too: cmake, pkg-config and the ALSA headers.
Nobody knew to write those down until CI met a machine that did not already
have them.

Carried forward with a correction: the IRC section claimed twelve
capabilities. It negotiates twenty-four, four of them drafts, plus WHOX - the
ticket work landed after that paragraph was last touched.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-11 21:14:52 -04:00
SalastilandClaude Opus 5 899ac140bb Say what this client actually speaks
The README's protocol section described moho as it stood before most of this
year's work. Discord was "message edit/delete/reactions/replies sync" - no
voice calls, no slash commands, no polls, no invites, no threads, no search,
no captcha window. IRC listed no capabilities at all, and neither typing nor
the notify list. Matrix had no group calls, no spaces, no knocking, no
stickers. Kick had no polls or predictions. Sneedchat did not mention the
captcha on the login form it now answers.

Rewritten against a fresh inventory of each backend rather than from memory.
IRC's paragraph names its twelve IRCv3 capabilities outright, because on that
protocol the capability list is the honest description - and it is also the
one place this client is measurably behind, which is easier to see when the
list is written down.

Discord's says plainly that there is no video or screen share, since a README
that only lists what works is a README somebody has to test to trust.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 15:45:27 -04:00
Salastil db1fe22819 The README describes the thing that exists
Fifteen mentions of `chatd`, a daemon that has been called nobilis since
it moved to its own repository; a config directory two names out of date;
and no mention of Kick at all, which is a protocol this client speaks.

While correcting those, the parts that had quietly drifted: IRC does more
than SASL PLAIN now, Sneedchat's rooms are read from the site rather than
edited in Settings, Matrix has threads and receipts and a directory
search, and the build is `npm run daemon` rather than a bare cargo
invocation. The wire section says requests are answered concurrently, and
points at methods.rs for the full method list rather than listing ninety
of them somewhere they can go stale again.

Two things it now admits that it did not: Matrix verification is between
your own sessions only, and there is no cross-signing.
2026-09-03 16:08:14 -04:00
Salastil 4f6cd9c794 IRC networks by their own logos, and an account form that asks less
Issue #12: the networks now carry their own marks, taken from each
network's own site and bundled rather than fetched - an IRC network is
not a CDN, a rail of tiles should not be a rail of requests, and half of
these sites are plain HTTP. Libera, OFTC, Rizon, QuakeNet, Undernet,
hackint, DALnet and CoreIRC have one; EFnet, Snoonet and Abjects publish
none and keep their initials, which is a better answer than a
wrong-looking copy of somebody's artwork. The marks belong to the
networks that own them and identify those networks here and nothing else.

CoreIRC and Abjects are in the list now - the two this client's own users
are on. A network being small is exactly why nobody else's client offers
it ready-made, and typing "irc.coreirc.net" correctly is the step the
list exists to remove.

The form itself: the network is picked from those marks rather than from
a dropdown of names, and the server and port fields appear only for
"Other" - a chosen network has its address already, and a field showing
it invites editing something that is right. Sign-in is asked here now,
as SASL or NickServ, because a registered nick that never identifies is
a nick lost to the ghost of the last session, and that happens on the
first connection rather than after anybody visits a settings pane.

Autojoin left the form entirely. Which channels an account rejoins is a
property of the channels, so it is set on one: right-click a channel,
"Join automatically". It was a comma-separated field typed once and never
seen again.

And Sneedchat's rooms moved out of the account pane into the join panel,
now that the list comes from the site. That page is for joining things; a
room being ticked is exactly that. The account pane is for what an
account is.
2026-09-03 12:36:36 -04:00
Salastil 9b07c4604b Depend on nobilis as a submodule, and fix four rendering bugs
The daemon moves to its own repository as nobilis, so both frontends - this
one and dms-chat - depend on it as a peer instead of one of them vendoring it.
The vendored chatd tree and the Rust workspace are gone; `npm run daemon`
builds the submodule, and packaging pulls the binary out of it.

The Sneedchat smiley images go with it, next to the table that names them.
Previously nobilis held the filenames while this repository held the files,
so the two could drift apart with nothing to catch it.

Fixes found while testing against live accounts:

- The sidebar did not truncate. A flex item's automatic minimum size is its
  content's min-content width, so prose-titled Matrix rooms made the buffer
  list wider than its pane and painted over the message list.
- Discord's QR never rendered: the event field is `qrCodePath`, not `path`.
  The Sneedchat and Matrix login progress text was blank for the same reason -
  all four status events carry `detail`, not `status`.
- Every Matrix message rendered as a grey system line with no author or
  avatar. IRC, Discord and Sneedchat tag an ordinary message "chat" but Matrix
  tags it "message"; only the first was treated as something a person said.
- Context menus and the emoji picker swallowed their own clicks. Both
  dismissed on a capture-phase mousedown, which unmounts the item before the
  click can land on it - so pin, mute, hide and emoji selection all did
  nothing. Presses inside the popup are now ignored.
2026-08-21 19:25:34 -04:00
Salastil 268751e5d0 Initial Commit 2026-08-21 14:04:58 -04:00