<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-client, branch 0.11</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.11</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.11'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-05T13:26:25Z</updated>
<entry>
<title>feat(client): system tray on Windows, and a clearer tray icon</title>
<updated>2026-09-05T13:26:25Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T13:26:25Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=bd299df3468822e7b545a90bf55ffbc1bac87f58'/>
<id>urn:sha1:bd299df3468822e7b545a90bf55ffbc1bac87f58</id>
<content type='text'>
The tray's Start/Stop already drove nodeService.status/stop/restart, which
had full win32 branches for both startup modes from the Node page work --
so enabling it on Windows is widening two platform gates (the `tray`
capability in preload.js, the window:minimize-to-tray handler in main.js),
not new logic.

Replaced the tray icon: the previous white chevron-in-a-box read as an
envelope at tray size. New icon is a small "M" drawn as mesh nodes and
edges, echoing the app icon's own motif, in the brand blue instead of
plain white so it stays legible on both light and dark taskbars/panels.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(client): tray menu starts and stops the node, and a clearer icon</title>
<updated>2026-09-05T11:32:26Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T22:15:55Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=b3362e6b25bc77dfe94f4c088a865e6fd2be2601'/>
<id>urn:sha1:b3362e6b25bc77dfe94f4c088a865e6fd2be2601</id>
<content type='text'>
The first icon was an arrow dropping into a receptacle, which is the download
glyph -- a vertical stem above a container reads that way whatever the context.
Replaced with a window folding a chevron into itself: no stem, and the frame
says which object is being minimised. Applied to both the nav button and the
panel indicator, which carried the same wrong shape.

The menu now offers Start or Stop for the node daemon, chosen from its actual
state and shown only when there is a daemon to act on: `supported &amp;&amp; installed`,
so a machine with no node installed gets no entry rather than a control that
fails when used. `restart` is the start verb -- systemd's restart starts a
stopped unit, and there is no separate one to call.

The three service handlers become named functions so the tray drives exactly
what the Node page drives, instead of a second copy of the systemctl and Task
Scheduler branches. A read of the state that throws is treated as no control
at all.

The menu is rebuilt on a 5s timer while an indicator exists, and again straight
after an action. libappindicator has no "menu is about to open" event, so a menu
built once would show a stale Start/Stop for the life of the process; `systemctl
--user show` costs a few milliseconds.

Locales: tray.start_node / tray.stop_node in all ten.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
<entry>
<title>feat(client): minimise to a system tray indicator (GNOME)</title>
<updated>2026-09-05T11:32:26Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T16:10:39Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=89ed51e9b7ea38bd4475ce3a234f8c783b3d9b09'/>
<id>urn:sha1:89ed51e9b7ea38bd4475ce3a234f8c783b3d9b09</id>
<content type='text'>
A dedicated monochrome button in the nav, immediately left of the notification
bell, hides the window to a tray indicator. Linux only for now; Windows is
being done on that OS, and the capability is declared per platform so the
button never appears where the desktop shows no indicator -- there it would
hide the window for good.

Hides, never closes: `window-all-closed` quits the app, so closing here would
make "minimise" mean "exit" and drop the session, the transfers and the node
connection. `second-instance` now calls the same restore path, since focusing
a hidden window does nothing visible.

The context menu is not decoration. Under libappindicator -- how GNOME shows a
tray at all, via the AppIndicator extension -- `tray.on('click')` never fires;
the indicator only opens its menu. A tray whose sole affordance was a click
would be inert on the one desktop this targets. The click handler is kept for
desktops that do send it.

Menu labels come from the renderer with the IPC call: the locale files are the
interface's, the main process has no i18n, and a second string table is how two
of them start disagreeing. English fallbacks if none arrive.

The icon lives in src/, not build/: package.json `files` packages only `src/**`
and `ui/**`, so an icon under build/ is present in a dev run and missing from
every installed one. Monochrome, stroked, matching the nav glyph.

Verified on this host: Ubuntu GNOME with ubuntu-appindicators@ubuntu.com and
libayatana-appindicator3 present, so the indicator has somewhere to appear.
Not yet run end to end.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
<entry>
<title>fix(win): graceful shutdown, one startup-mode control, and a stray-\r bug</title>
<updated>2026-09-05T11:06:55Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T11:06:55Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e89a57bb97b5a0d624e8d490b6b8aa38ba140817'/>
<id>urn:sha1:e89a57bb97b5a0d624e8d490b6b8aa38ba140817</id>
<content type='text'>
Windows-only changes, all found by actually running the previous session's
work rather than by review alone:

- CTRL_CLOSE_EVENT/LOGOFF/SHUTDOWN handler (platform.py, ctypes
  SetConsoleCtrlHandler) so closing a console window, signing off, or a
  system shutdown runs the daemon's real _shutdown() instead of Windows
  just ending the process — closing WebRTC sessions and any in-flight
  ffmpeg transcode instead of orphaning it. `taskkill /F` itself stays
  uncatchable (like SIGKILL), so autostart_run() now spawns with
  CREATE_NEW_PROCESS_GROUP instead of DETACHED_PROCESS and autostart_end()
  tries CTRL_BREAK_EVENT against the recorded pid first, falling back to
  the hard kill only if that doesn't stop it in time.

- Replaced the Node page's two independent autostart/service-mode toggles
  with one "start automatically" select (off / at sign-in / as a
  background service). The old pair let both be active at once — starting
  the daemon twice, at boot and at sign-in — and their layout broke
  wrapping inside .node-service's flex row. The new control always removes
  whichever mechanism is active before installing the target; platform.py's
  service_install() does the same on the CLI side. The "background
  service" option disables itself (with a hint pointing at the CLI) when
  running unpackaged, since service-mode.ps1/service.ps1/firewall.ps1 all
  assume an installed build's layout — verified live rather than assumed
  by actually running those scripts unelevated.

- findNodeBinary() no longer bakes a stray \r into resolved paths. Found by
  rebooting after enabling per-user autostart: where.exe listed two
  matches, and stdout.trim().split('\n')[0] only strips the whole string's
  ends, leaving line one's own trailing \r attached — which landed inside
  the Startup .vbs's quoted path and broke it with "Unterminated string
  constant" at boot. Fixed by splitting on \r?\n and trimming every line.

- Dependency audit for the Windows installer (docs/WINDOWS-PORT.md): no
  VC++ Redistributable needed, confirmed by inspecting the built
  node-runtime's actual import table rather than assuming. New
  docs/windows-build.md: a concise clone-to-installer build guide.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(client): a Node-page toggle to switch into/out of service mode</title>
<updated>2026-09-05T07:28:03Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T07:28:03Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=7601991ccb1d75637c055062c38b1852eeef9700'/>
<id>urn:sha1:7601991ccb1d75637c055062c38b1852eeef9700</id>
<content type='text'>
The installer's own mode question is effectively one-shot: customInstall
skips it entirely once the firewall rules already exist, for any reason --
and per-user mode sets those up on its own, with no Scheduled Task involved.
So declining once (or the rules existing from something unrelated, as
happened on a dev machine this session) was a dead end: no reinstall, repair,
or uninstall/reinstall cycle could ever bring the question back, since
uninstall defaults to leaving both alone.

Add the other door in (and out): a checkbox on the Node page, next to the
existing per-user autostart toggle, wired main.js -&gt; preload.js -&gt; platform.js
-&gt; node-page.js. It runs packaging/win/service-mode.ps1 -- the exact script
installer.nsh already runs -- via one Start-Process -Verb RunAs elevation, so
the two paths can never disagree about what service mode means. The elevation
helper writes a tiny param()-based .ps1 to %TEMP% so the target script path
and its arguments bind through real PowerShell parameters instead of nested
string-quoting.

Also fixes a real pre-existing gap found while checking this: 8 of the 10
locale catalogues (all but en/fr) were missing the autostart/service-mode
keys added in an earlier commit this session (b782886) -- test_locales.py's
key-set-parity check uses a for-loop with an inline assert, so it stopped at
the first mismatch (fr) and never actually reached the other eight. Backfilled
all five keys (three pre-existing, two new) in de/es/it/ja/nl/pl/pt-BR/zh-CN.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(packaging): bundle ffmpeg in the Windows installer by default</title>
<updated>2026-09-05T06:43:11Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T06:43:11Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=301c8998bfdcac80ad3302e2d2ebe853e2ea6de1'/>
<id>urn:sha1:301c8998bfdcac80ad3302e2d2ebe853e2ea6de1</id>
<content type='text'>
winget install ffmpeg was considered and rejected as the mechanism: it
needs network access and winget/App Installer present at the exact
moment setup runs, and its failure mode is silent -- video just does
not stream, with nothing pointing back at ffmpeg. Not viable for a
non-technical install.

MeshBay transcodes browser-incompatible video to H.264 (-c:v libx264,
webrtc_server.py) -- a real encode, not remux -- so this needs a genuine
GPL ffmpeg build; no LGPL-only build includes an H.264 encoder, since
libx264 itself is GPL.

packaging/win/fetch-ffmpeg.ps1 (new)
  Downloads, checksum-verifies and stages ffmpeg for the build. Source:
  BtbN/FFmpeg-Builds' Windows x86_64 gpl-shared preset -- shared DLLs
  rather than two independent static binaries, which is what nearly
  tripled this: the "full" static build many devs already have via
  winget is ~220 MB *per executable*. Pinned to one dated release tag
  (immutable once published) and its own sha256, not the "latest" alias
  BtbN repoints on every auto-build -- verified by hand first (downloaded,
  hash matched, ran a real encode+probe with libx264) before pinning.
  ffplay.exe (an SDL2 player, ~17 MB) is dropped; MeshBay never invokes
  it. Cached after the first build. Runs its own smoke test (encode +
  probe a real clip) so a broken fetch fails at build time, not for the
  first user who tries to watch something.

packaging/win/LICENSE-ffmpeg.txt (new)
  GPLv3 notice + where the corresponding source is, required because
  this redistributes a GPL binary even though it is unmodified and only
  ever invoked as a subprocess. Ships alongside ffmpeg.exe in the
  installer.

build-node-runtime.ps1 / build-win.ps1
  Bundling is now the DEFAULT, replacing the old opt-in -FfmpegDir (which
  copied from a local directory and left most builds without ffmpeg at
  all). -SkipFfmpeg opts out for a smaller, streaming-less local-iteration
  build.

  Also fixes a real bug the ffmpeg change exposed rather than caused: the
  final `--help` smoke test did `$help -notmatch "meshbay-node"` against
  $help captured as a PowerShell ARRAY (one element per line) -- -notmatch
  on a collection is a FILTER, not a boolean test, and returns the
  non-matching elements; any non-empty array is truthy in if() regardless
  of content. Once --help wrapped past one line (it now does, with
  autostart/service in the verb list) this threw unconditionally. Fixed
  by joining to one string before matching, and pinned by a new test so
  a future edit cannot silently reintroduce the collection-vs-scalar trap.

Verified: downloaded and hashed the pinned release by hand (matches),
ran a real libx264 encode + ffprobe against the extracted build,
fetch-ffmpeg.ps1 end to end (161 MB staged), a full build-node-runtime.ps1
run (308 MB node-runtime/) and a full installer build (MeshBay-Setup-
1.0.0.exe, 210.8 MB with ffmpeg bundled). Node suite 850 pass / 25 skip.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: opt-in Windows service mode (boot-time, one elevation) + v1.0.0</title>
<updated>2026-09-04T15:29:24Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T15:29:24Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=b78288640d8c13cc0fb3f4ee7c82f3efac33940f'/>
<id>urn:sha1:b78288640d8c13cc0fb3f4ee7c82f3efac33940f</id>
<content type='text'>
The per-user Startup-folder launcher (W3) only ever runs after this user
signs in. A real Windows Service would start earlier, but under
LocalSystem/NetworkService -- accounts with no normal profile, so
%LOCALAPPDATA%\meshbay\ (config, keystore, data) would not exist for it.
Relocating storage to make that work is real surgery, deliberately not
done here.

Instead: a Scheduled Task, created once with admin rights, that runs AS
THIS USER at boot without needing them to sign in first.
`schtasks /create ... /ru &lt;user&gt; /rp ""` with no `/it` registers an S4U
(Service For User) logon -- no password stored anywhere, and unlike
LocalSystem it loads this account's own profile, so config_dir()/
data_dir() need zero changes. The cost: S4U carries no network credential,
which the node never needed -- everything it touches is local disk plus
outbound internet. Creating the task needs admin (a boot trigger touches
system-wide scheduler state, the same reason /sc onlogon needed it);
querying/starting/stopping an existing one does not -- Task Scheduler
grants the owning user that much itself, which is what lets the Node
page's Start/Stop/Restart drive it with no further UAC prompts.

meshbay_node/platform.py
  service_install/_remove/_status/_run/_end -- mirrors autostart_* but
  for the Scheduled Task; TASK_NAME moved here (was decorative before)

meshbay_node/daemon.py
  new `service install|remove|start|stop|status` verb; restart-daemon and
  reset now check for the service task too

packaging/win/service.ps1
  the installer-side equivalent (extraResource); status/run/end never
  self-elevate -- only install/remove do, exactly matching what
  Task Scheduler itself requires

packaging/win/service-mode.ps1
  ONE elevated helper running service.ps1 + firewall.ps1 together, so
  choosing service mode costs exactly one UAC prompt, not two

build/installer.nsh
  the install-time choice: "run as a background service?" (one
  elevation, both jobs) vs the existing per-user + separate firewall
  question. Checked first, unelevated, so re-running setup with
  everything already configured asks nothing. Uninstall offers the
  matching one-elevation cleanup, default No.

src/main.js
  winServiceTaskStatus/Run/End, wired into node:installed,
  node:service-status/-stop/-restart and node:start: when the Scheduled
  Task exists, drive it; otherwise fall back to the existing per-user
  spawn/kill path. This is the hard requirement -- Start/Stop/Restart
  from the Node page must work in either mode.

node-page.js / locales
  a hint explaining why the per-user autostart toggle is absent when
  service mode is active (info.mode from the backend, no new field to
  gate on -- it just isn't sent in that case)

package.json: 0.1.0 -&gt; 1.0.0.

Verified: electron-builder compiles the new NSIS choice logic and ships
all three scripts; service.ps1's S4U install fails cleanly (Access
denied) when run unelevated, and its status/run/end never touch "runas".
Cannot verify the elevated success path myself (no admin in this
session) -- that needs a real UAC click. Node suite 843 pass / 25 skip;
test_packaging_win.py pins the one-elevation property, the S4U flags,
and that main.js actually checks the service task in all three handlers.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(client): Stop/Restart buttons and an autostart toggle on the Node page</title>
<updated>2026-09-04T14:50:32Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T14:50:32Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=13d145253a871ef47ef4344f90566eea21b994ab'/>
<id>urn:sha1:13d145253a871ef47ef4344f90566eea21b994ab</id>
<content type='text'>
Root cause of both reports: node:service-status's `installed` field was
winAutostartInstalled() -- whether the Startup-folder launcher exists --
not whether the daemon can be managed at all. The Node page gates
Stop/Restart on `installed`, so with no autostart configured (the default
-- nothing installs it automatically) those buttons silently never
rendered, leaving only Start. A perfectly manageable daemon looked
unmanageable because a different, unrelated setting was off.

installed now reflects the actual daemon binary (findNodeBinary()), same
definition node:installed already used; autostart moves to its own field
carrying what installed used to mean.

That field also fixes the other half: there was no way to turn autostart
on except the CLI. NodeServicePanel now shows a toggle-switch next to
Start/Stop/Restart, wired through a new platform.node.autostart
(install/remove) that mirrors the existing service.* pattern -- the
preload/main.js bridge (W3) was already there, just never called from the
interface. English and French strings; other locales fall back to English
per the project's own stated policy (test_locales.py's own docstring).

Verified: node --check on every edited file; hub/node suites green
(863 pass, 0 fail — pre-existing test_locales.py encoding failures on
this Windows checkout are unrelated, reproduced identically on the
clean tree).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(client): a failed spawn() of the node no longer crashes the app</title>
<updated>2026-09-04T14:24:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T14:24:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=046d847c1b7ba9fcc8e0f3be6e4ff13dac016479'/>
<id>urn:sha1:046d847c1b7ba9fcc8e0f3be6e4ff13dac016479</id>
<content type='text'>
spawnNodeDetached() and the Linux dev-mode fallback in node:start called
spawn() with no 'error' listener. A spawn failure -- bad path, a stale
PATH entry, antivirus interference -- is delivered on that event
asynchronously; with nothing listening, Node rethrows it as an uncaught
exception and takes the whole Electron main process down with it, instead
of the caller's own waitForNode() timeout turning "never came up" into a
clean message.

Hit directly: ENOENT spawning a stale dev-venv meshbay-node.exe (not the
installed one -- where.exe correctly resolves to the bundled exe now).
Whatever the trigger, a daemon that fails to start must never be able to
take the renderer down with it.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(packaging): re-running setup no longer re-prompts for firewall access</title>
<updated>2026-09-04T14:08:06Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T14:08:06Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=a4aabd1d33770d6199a8cb7bc87f639668617bc9'/>
<id>urn:sha1:a4aabd1d33770d6199a8cb7bc87f639668617bc9</id>
<content type='text'>
Every run of customInstall showed the "Allow MeshBay through Windows
Firewall?" question and, on Yes, a fresh UAC prompt -- an upgrade or repair
install would ask again even with all four rules already in place.

customInstall now checks first: firewall.ps1 check, unelevated
(Get-NetFirewallRule needs no admin, only New/Remove do), exits 0 if every
rule already exists. Only a nonzero result reaches the MessageBox and the
elevated add. A second run of setup on an already-configured machine now
asks nothing.

Verified unelevated: check exits 1 and logs which rules are missing on a
machine with none of them (the fresh-install case); electron-builder
compiles the nsExec::Exec / Pop $0 / ${If} wiring. Node suite 837 pass /
25 skip.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
