Commit Graph
21 Commits
Author SHA1 Message Date
SalastilandClaude Sonnet 5.5 97611c8742 No reference to the maintainer's own server: .gitmodules and PKGBUILD are the public ones on every branch, the nobilis release is its own script and the mirroring is kept outside the repository, and the maintainer is [email protected] everywhere it is named
Closes #335

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-11 00:28:47 -04:00
SalastilandClaude Sonnet 5.5 64ade92ee1 Kick cards sit where a person does: no join mark beside them, starting at the picture's edge with the name where a name is in the log - in comfy and in bubbles, where they had been centred - and with the air above them a message has
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-11 00:07:59 -04:00
SalastilandClaude Sonnet 5.5 336335a8a3 Scrolling a long unread run no longer snaps back: the place being read is found through the bar laid over the top of the log, not read off the bar, so a card or picture settling above does not put the view back where the bar appeared
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-10 13:17:22 -04:00
SalastilandClaude Sonnet 5.5 55248eb583 GIFs: a linked GIF is drawn as the picture - silent, looping, without the player - on every service, a GIF picker with trending, categories and search, and Klipy/Tenor/Giphy pages are never probed (#331)
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-09 17:55:10 -04:00
SalastilandClaude Sonnet 5.5 24106e51f4 Tray: a bubble drawn for the system's colour scheme with the number waiting on it; a menu of what is new, Status, notifications, Settings, Restart moho and Quit; no daemon plumbing (#330)
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-09 11:00:10 -04:00
SalastilandClaude Opus 5.5 9967d8f776 Every version tag is a full release, release candidates included (#239)
v1.0.0-rc.1 was published as a pre-release, which GitHub does not show as
the repository's release. Every version tag now publishes a full release
marked "Latest". The already-published v1.0.0-rc.1 was changed to match,
for moho and nobilis, on GitHub and Gitea.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:58:58 -04:00
SalastilandClaude Opus 5.5 3bc9003711 Build only for version tags (#239)
A push to master and a tag on it were two push events, so every release
was built twice: once as master's check, once to publish. Releases are
versions only now, so master pushes build nothing. A branch can still be
checked by starting the workflow by hand from the Actions tab - a run that
is not for a tag builds and tests everything and publishes nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:44:15 -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 a0e9445991 Version tags publish a release of their own; PKGBUILD reports the version (#239)
- A pushed v* tag runs the same build and publishes a release named after
  it, beside `latest` rather than replacing it: the files renamed
  moho-<version>.AppImage/.deb/.exe, notes of its own, created as a draft
  and published only once its files are attached, marked GitHub's latest.
  The rolling steps (moving `latest`, its notes, the prune, the re-dating)
  run only for branch pushes.
- The Linux job checks first that the tag is v<package.json version>, so a
  tag cannot publish an app that calls itself something else in Settings >
  About.
- PKGBUILD's pkgver leads with the version: 0.1.0.r493.5a8d7f7. A number
  sorts above the r493.5a8d7f7 it replaces, so installs upgrade.

The version and commit were already in Settings > About, for moho and for
nobilis. Not done here, as they are decisions rather than mechanics: the
1.0.0 bump itself, and whether the Gitea release stays.

master's PKGBUILD is kept apart on merges, so the same pkgver change has to
be carried to it by hand.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 10:46:50 -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 c001a252e2 Hold nobilis at zero clippy warnings in the release build
A clippy step after the tests, with the component installed. #245

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 12:41:32 -04:00
SalastilandClaude Opus 5 93829cd638 Key the build cache on the runner image too
Pinning the Linux job to ubuntu-22.04, so the daemon links against a glibc old
enough for the distributions moho claims to support, changed what builds the
artifacts without changing anything the cache key looked at. The next push
restored a target directory compiled on 24.04 under an exact-match key, cargo
took the crates it named to be already built, and the build died on "can't
find crate for tor_bytes" - a dependency whose rlib the restored tree did not
contain.

The Windows job passed the same run. It is still on ubuntu-latest and restored
a cache written on ubuntu-latest, which is how the image turned out to be the
variable rather than anything in the tree: the lockfile, the manifest and the
compiler were identical to the last green build.

Both target caches now carry the image, including the Windows one - it
cross-compiles the binary but still builds host-side build scripts and proc
macros into the same directory. The restore-keys are scoped the same way,
since falling back across images is the identical trap one step down.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-28 17:11:33 -04:00
Salastil 4c6e24be9c Build the daemon where the oldest target can run it
The released daemon could not start on Ubuntu 22.04, and nothing about
the package said so.

nobilis is a native binary and glibc symbol versions only go forwards:
one built against 2.39 refuses to load on anything older. Built here on
Arch it wants GLIBC_2.39, and ubuntu-latest is 24.04, which is also
2.39 - while Ubuntu 22.04 has 2.35. So the AppImage would start, its
window would come up, and the backend would never appear. A packaging
fault wearing a runtime failure's clothes, and the least legible kind
there is.

This is separate from every other Linux problem in this area and older
than all of them: it is not the FUSE runtime, not the sandbox, not the
AppArmor profile. Those were all found and fixed while this one sat
underneath them, which is exactly why it is asserted now rather than
remembered.

So the Linux job builds on ubuntu-22.04, which sets the floor at 2.35 -
satisfied by every newer target, Debian 13 on 2.41 and Ubuntu 24.04 on
2.39 included - and a step after the build reads the symbols back and
fails the run if the floor ever rises again. The pin is the kind of
thing that gets tidied away by someone who reads `ubuntu-22.04` as
staleness rather than intent, so the check is what actually holds the
promise. When the label is retired, the replacement is a
`container: ubuntu:22.04` on a current runner, not the next label along.

The Windows job needs none of this: it cross-builds with mingw and
links no glibc at all.
2026-09-22 22:33:03 -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 e19d15db7c A release dated when it was built, not when it was born
The rolling release has been refreshed on every push to master for days,
and correctly: the two assets it holds have been rebuilt and replaced
each time, and the body names the right commit. Only the date under the
title never moved, because `gh release edit` cannot move it - published_at
is written when a release goes from draft to published, and this one was
published once, in the run that created it.

So the page has read "01:05" through twelve successful builds, which from
outside is indistinguishable from a release that stopped publishing.

Draft and back is the only way to set it. Last in the job, so it dates
the release to when its files were finished.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-12 15:23:27 -04:00
SalastilandClaude Opus 5 5ec71748c1 Stop guessing at how Ubuntu packages wine, and look
Run four failed in a minute on `ln -s '' /usr/local/bin/wine` - installing
wine64 and wine32 had put no wine anywhere that anything could find it.

So I looked the packages up instead of guessing a fourth time. On 24.04 the
wine64 package ships /usr/lib/wine/wine64 and /usr/lib/wine/wineserver64 and
nothing in /usr/bin at all; the wine package ships /usr/bin/wine-stable and
hangs /usr/bin/wine off it through alternatives. Asking for the two halves
directly, which is what I had been doing, installs a wine no caller can
find - and --no-install-recommends made it worse, because the 32-bit half
arrives as a recommend of the metapackage.

Hence: install `wine` with recommends left on, then make the name `wine`
exist whichever way round the image has it, and only then check. Both checks
stay - the version, and the syswow64 ntdll that is the thing rcedit actually
needs - because each of them has now caught a different wrong answer.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-11 20:51:56 -04:00
SalastilandClaude Opus 5 684e1b56e2 The wine that is needed is a 32-bit one
Run three got past apt and spent nine minutes reaching the same step, where
wine loaded rcedit-ia32.exe and died on a missing syswow64 ntdll.

That name is the whole answer: electron-builder writes the icon and version
metadata into the .exe with rcedit-ia32.exe, a 32-bit Windows program, and a
64-bit-only wine cannot run one. So the i386 architecture goes on and
wine32:i386 with it.

The `wine --version` check is not enough and is no longer trusted on its own
- it passed on a wine that could not run rcedit at all. The prefix is built
and the file whose absence failed the pack step is looked for directly, so
the next time this is wrong it says so in a minute rather than in nine.

Worth recording why none of this was visible from here: this desk has a wine
with a 32-bit prefix already, so packing for Windows has always just worked.
Each of the three failures so far has been a thing the local machine quietly
provides.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-11 20:29:53 -04:00
SalastilandClaude Opus 5 634dbd72b8 The package is called one thing and the binary another
Run two: Linux went green in twelve minutes, and Windows stopped after
thirty-three seconds on `wine: command not found`.

The install had worked. Ubuntu 24.04's wine64 package puts the binary at
/usr/bin/wine64 and leaves no `wine` on PATH, which is the name
electron-builder looks for. So: ask for `wine` first, keep wine64 as the
fallback, and bridge the two names with a symlink rather than betting on
which package a future runner image happens to carry.

The `wine --version` check that caught this is the reason it cost thirty-three
seconds instead of the half hour it would have taken to fail at the pack step
- which is exactly what it was put there for, one run earlier.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-11 20:17:47 -04:00
SalastilandClaude Opus 5 bc3ef8b435 Two things the first run found
Both build jobs failed, which is what a first run is for. Neither failure
was in the workflow's own logic - the artifacts, the release, the prune step
were never reached - and `publish` was skipped rather than shipping half a
release, which is the one property the whole arrangement exists to have.

**wine.** Packaging for Windows runs the executable through signtool, which
is a Windows program, and that call wants wine whether or not there is a
certificate to sign with - it skips the signing a moment later for want of
one. This desk has wine installed, so the local build never said so; the
runner said "wine is required" and stopped. Asked for by both package names,
since the split differs between Ubuntu releases, and then checked, so a
silent non-install fails at the apt step rather than half an hour later.

**A hardware test.** `a_missing_device_is_an_error_rather_than_a_substitution`
tests the routing step, which needs a default device to open before there is
anything to route - so on a runner with no sound card it failed one step
earlier, on a different error. Fixed in nobilis (3fa80c7) by marking it
#[ignore], like the hardware test directly below it, and the submodule
pointer moves with this commit.

I said in the plan that every test needing hardware or the network was
already ignored. That was true of 17 of them and not of the eighteenth.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-11 20:04:26 -04:00
SalastilandClaude Opus 5 ec0d558bc3 Build the two downloads somewhere that is not this desk
Every AppImage and every installer so far has been built here and carried to
the release by hand. master is on github now, and names the daemon at a URL
a stranger can fetch, which is the whole prerequisite for a machine that is
not this one doing it instead.

So: a push to master builds the AppImage and the installer on two runners at
once, and the release on the moving `latest` tag ends up holding exactly
those two files. The Gitea release is untouched and stays a manual act.

Every step is an existing npm script rather than a second description of how
to build this. The Windows side cross-compiles with mingw exactly as it does
here, so what CI ships is the same kind of binary that has been shipped and
tested all along - not a new one nobody has run.

The parts worth knowing about, each of which is a decision rather than a
default:

`cargo test` rides inside the Linux job instead of standing as a gate of its
own. A gate would compile all 812 crates a third time in the dev profile and
share nothing; run in the release profile right after the daemon build it
reuses every rlib that build just made. Publishing is still refused when it
fails, structurally: `publish` needs both build jobs, and a needs-dependency
on a failed job never runs.

The target directory is cached, and the key deliberately excludes the commit
sha. What costs half an hour is compiling - aws-lc and sqlite through CMake,
the arti and matrix-sdk trees - not downloading. Keyed on the commit, every
push would write a fresh entry and the repository's cache budget would evict
itself within a day, leaving a cache that is never warm.

The tree is asserted clean before packing. MOHO_BUILD is computed inside the
npm script, so the only way to control what the file is called is to
guarantee the commit it is named after is what was built - and a `-dirty`
suffix on a public download built from a clean checkout would be a lie about
provenance.

Upload then prune, in that order, because the files are named after the
commit: --clobber alone never removes anything and the release would grow a
pair per push forever, while deleting first would leave a window where the
page offers nothing.

Checked before it ever runs: the YAML parses, the prune loop does the right
thing under the errexit shell Actions uses (including the empty-release and
nothing-to-drop cases), the notes composer handles a multi-line subject, a
subject full of backticks, and the no-subject case a dispatched run gives -
and `npm run pack -- --publish never` really does append that flag to
electron-builder rather than to something earlier in the chain.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-11 19:47:47 -04:00