<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-client/src/main.js, branch 0.16</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.16</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.16'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-23T17:01:37Z</updated>
<entry>
<title>fix(client): let Copy buttons write the clipboard in the desktop app</title>
<updated>2026-09-23T17:01:37Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-23T17:01:37Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=d87f05f9f131aa7cc92f53355c5fe63be01aa516'/>
<id>urn:sha1:d87f05f9f131aa7cc92f53355c5fe63be01aa516</id>
<content type='text'>
Only fullscreen was granted, so navigator.clipboard.writeText was
refused and every Copy button did nothing. Grant clipboard-sanitized-write;
reading stays refused.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: cite MESHBAY_DESIGN.md and a section instead of the merged notes</title>
<updated>2026-09-19T00:12:47Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-19T00:12:47Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=20a824118c09af15d6c338db4c9480ffe5cbcdb6'/>
<id>urn:sha1:20a824118c09af15d6c338db4c9480ffe5cbcdb6</id>
<content type='text'>
The per-feature design notes were merged into docs/MESHBAY_DESIGN.md and
deleted from the tree on 2026-09-11, but ~230 comments across the three
packages still named them — usually written `docs/musicbay.md §3.2`, as
though the file were still in docs/. A reader had to know §16 existed to
resolve any of them. They now name the section directly.

Every mapping comes from §16, the concordance, which already records where
each old section landed: musicbay -&gt; §9.8, mediacenter -&gt; §9.7 for the
Videos app and §6.5 where the subject is derived data, photos -&gt; §9.9,
auth-confirm -&gt; §3.6, refactoring-search -&gt; §9.11, invite-pairing-v1 -&gt;
§3.4, per-node-identity-v1 -&gt; §3.2, captcha -&gt; §7.7, chat-sender-keys -&gt;
§4.5, apps/refactor-groups -&gt; §9.1–§9.4, desktop-client-v1 per section.
Bare citations of the same documents (`draft-v6 §2.11`, `§4.8`, `§3.4`)
are retargeted too: those collide with real section numbers in the design
document, so leaving them would have been worse than the named form.

Four cases the concordance does not cover, each decided rather than guessed:

  Sub-item references into documents that no longer exist — mediacenter's
  `§3.3 row 4`, `§3.4b/c`, `apps.md §3b` — name rows and sub-items §9.7 and
  §9.2 do not reproduce. The module-level citation stays; the sub-item
  pointer is dropped.

  The V-findings keep their labels but lose the dead `§10.1/` prefix.
  §13.8 lists V1–V13 as per-application open items, which is not what the
  labels mean in these comments, so pointing them at §13.8 would have been
  a false citation.

  `apps.md §5`'s virtualization requirement has no counterpart anywhere in
  the design document. The requirement is stated in the comment itself, so
  the citation is dropped rather than aimed at a section that does not say
  it.

  Comments that attributed a *sentence* to an old note — musicbay's "several
  thousand files" example, its "what got measured" note, its measured
  ~11%/~26% cover-art figures, the "original no root, whole shared tree"
  call — state the fact without attribution now. §9.8 does not contain those
  sentences and citing it for them would have been wrong.

CLAUDE.md's "a reference to a document that no longer exists" row now says
the concordance is for git history and out-of-tree material; the code cites
sections directly.

Verified: 2851 passed, 4 skipped. The 12 errors in the run are the Firefox
leg of test_sticky_header.py's browser harness, which is broken at the
browser level on this machine — headless Firefox (snap) dies with
`[GFX1-]: RenderCompositorSWGL failed mapping default framebuffer`, renders
nothing, and the probe exits `{"error": "no measurement"}` after its full
90s wait. Chrome runs the same 12 assertions in 3.2s and passes. Nothing
here can affect it: every changed line in style.css is inside a comment.
Also checked: ast.parse on every changed .py, `node --check` on every
changed .js, the /* */ balance in style.css, and that no changed line
exceeds the width its file already used.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: drop the references to design notes that live outside the repo</title>
<updated>2026-09-18T23:15:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-18T23:15:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=cc0820de8d8c33560c44bd6a0864de30e33a0109'/>
<id>urn:sha1:cc0820de8d8c33560c44bd6a0864de30e33a0109</id>
<content type='text'>
Seventeen comments across the Windows packaging targets pointed at
C:\Users\admin\devel\light-client.md and msix-installer.md -- absolute
paths on one developer's machine, unreadable to anyone else who clones
this repository and unverifiable by any test here.

The surrounding prose already carried the substance in every case, so
these are removals, not rewrites, with two exceptions where the pointer
was doing real work:

  electron-builder.msix.yml's header told the reader to go read §4 and §8
  first. It now states the fact directly (an AppX/MSIX install never
  elevates, by design, so the package carries none of installer.nsh's
  elevation logic) and says the open items are called out at each
  declaration below -- which they already were, at `capabilities` and
  `customExtensionsPath`.

  The two "msix-installer.md §8" citations become "an open item" / "the
  other open item", beside the description of the item that was already
  there.

Paragraphs the removals left ragged are re-wrapped.

Verified: node suite 1403 pass / 4 skip (71 of them test_packaging_win.py),
both electron-builder configs still parse as YAML, node --check on main.js.
The .ps1 edits are inside &lt;# #&gt; comment headers plus one deleted Write-Host
in a block that keeps two others; no pwsh on this machine to parse them.

Not touched, and much larger: ~230 comments elsewhere in the codebase cite
per-feature design notes (musicbay.md, mediacenter.md, auth-confirm.md and
twenty more) that were merged into docs/MESHBAY_DESIGN.md.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: decode and re-encode video on the GPU where there is one</title>
<updated>2026-09-18T12:37:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-18T12:37:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=d22ce0ba5aebfff5ffaea97982472f876af5da22'/>
<id>urn:sha1:d22ce0ba5aebfff5ffaea97982472f876af5da22</id>
<content type='text'>
A 1080p film is decoded by whoever watches it and re-encoded by the node
when no browser can decode the source. Both were on the CPU, and on an
Atom or Celeron mini-PC neither reaches real time — which is what
`transcode_incompatible_video` exists to refuse. This adds the mechanism
that makes refusing it unnecessary.

Node — `hwaccel.py`: VA-API on Linux, Quick Sync or NVENC on Windows,
established by encoding 1080p and reading the file back with ffprobe.
Nothing is accepted that does not produce the exact profile and level
`stream_init` announces, since the client checks that string before it
trusts a byte: an encoder that wrote another level would make the node's
own codec string a lie, and ffmpeg takes `-level 4.1` and `-level 41`
from h264_qsv without saying which it understood. Three modes per
stream — hardware decode and encode, hardware encode alone, libx264 —
demoted per source codec, because a GPU that decodes HEVC may have no
decoder for MPEG-4 Part 2 and only asking it finds out. A mode that
fails is detected on an empty stdout before `stream_init` goes out, so
the viewer sees one working stream and never an error.

Client — Chromium ships VA-API off on Linux. It is enabled where a
render node and a driver are present, then verified through
`navigator.mediaCapabilities`: a no moves to the next GL backend on the
next launch and an exhausted list drops the switches, so a renamed
feature cannot pass for a feature that is on and `--ignore-gpu-blocklist`
cannot survive on a machine it did not help. Feature lists now merge
rather than overwrite — `appendSwitch` replaces the value, and a second
caller would have silently cancelled the mDNS switch aiortc depends on.

Packaging — the drivers are weak dependencies on all four formats, so a
machine without a GPU installs exactly as before. `dpkg -i` and
`rpm -ivh` ignore weak deps; `packaging/README.md` now says so. Windows
needs no driver: the bundled ffmpeg already carries h264_qsv and
h264_nvenc, and a re-pin that dropped them would cost every low-power
Windows node its hardware encoding silently.

AMD on Windows (AMF) and macOS (VideoToolbox) are named gaps, not
oversights: neither could be tried anywhere in this project, and both
re-encode in software as before.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(cast): carry subtitles to a Chromecast, on the relay's clock</title>
<updated>2026-09-18T07:49:27Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-18T07:47:01Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=c03512aeab576a06f8d5026e5eb484897ec45f99'/>
<id>urn:sha1:c03512aeab576a06f8d5026e5eb484897ec45f99</id>
<content type='text'>
The relay forwards the node's fragments untouched, and those begin at zero
at the seek point. The player never notices because its SourceBuffer is given
`timestampOffset = start`; a receiver has no equivalent, so the cues are
shifted by `-start` before they leave, recomputed at every restart of the
relay. Sent as they are, a subtitle would be out by the whole seek.

The document is served from the relay's own port at /subs.vtt, behind the same
token as the stream and with CORS: a receiver fetches a side-loaded track with
XHR from its own origin, and without the headers it fails as a network error
with nothing on screen to say so. The URL carries a version because a track is
cached by address — changing the cues behind a fixed URL leaves the previous
language showing.

Cues that end before the stream begins are dropped rather than clamped, so a
line from before the seek cannot appear over the first frames after it.

The relay is plain Node, so the tests start it and fetch from it rather than
reading its source.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01UGY17EPph5LsLzePPXhUVc
</content>
</entry>
<entry>
<title>fix(win): a service-mode daemon can be replaced, and the Node page can link one</title>
<updated>2026-09-14T21:51:36Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-14T21:51:36Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e12570f9d1aa2645e6bb223b1417fa0e81957b65'/>
<id>urn:sha1:e12570f9d1aa2645e6bb223b1417fa0e81957b65</id>
<content type='text'>
Two live-reproduced bugs in Windows node start/stop, found sideloading the
0.14.0 build:

- node:start's crash-recovery step killed a service-mode daemon with
  taskkill/CTRL_BREAK, both of which fail with "Access is denied" against a
  process running under the Scheduled Task's own S4U logon session (a
  different session from the Electron app's). The daemon it was meant to
  replace just kept running, unreplaced, and schtasks /run on a task Windows
  still considered Running was then a silent no-op too. Route through
  winServiceTaskEnd() (schtasks /end) first, the way nodeServiceStop/
  nodeServiceRestart already correctly do. service-mode.ps1 also now starts
  the task right after registering it -- Register-ScheduledTask's own
  AtStartup trigger does not run it immediately, so nothing was listening
  until the next reboot.

- The Node page's Start button called node.start() with no arguments, so an
  unlinked node (a fresh install, or one whose hub-side link was lost) could
  never link on Start alone -- only create-group-page.js's own call passed
  {hubUrl, username, token}. Reproduced on a fresh non-service install signed
  in to the real hub: Start hung for ~105s and failed with "could not link",
  pointing at a "Link Node" control that lives on Settings, not the Node
  page (that message is fixed too).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(packaging): three MSIX first-run regressions found by a real sideload</title>
<updated>2026-09-12T13:48:22Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-12T13:48:03Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=9cc2909cb4a360c81b471ceab1d9578a7655a88e'/>
<id>urn:sha1:9cc2909cb4a360c81b471ceab1d9578a7655a88e</id>
<content type='text'>
A second-machine sideload of the MSIX target surfaced three things the
earlier verification round (which only proved the package installs and
runs) had missed:

1. meshbay-node missing from PATH. installer.nsh's customInstall adds
   node-runtime\ to HKCU\Environment at install time -- an unelevated
   per-user write, never blocked by MSIX's no-elevation rule, only by the
   more basic fact that an AppX/MSIX install runs no custom code at all.
   packaging/win/ensure-node-path.ps1 (idempotent, no admin verb) plus
   main.js's winEnsureNodeOnPath() do it from the app itself instead, once
   per launch, shipped to Full and MSIX (not Light, nothing to add there).
   Verified live via the Node inspector protocol: the entry was in
   HKCU\Environment\Path after a launch, absent before.

2. A daemon that crashes on startup failed silently. spawnNodeDetached()
   used stdio: 'ignore', so a real crash reproduced live (a second instance
   colliding with the first on 127.0.0.1:18000) left waitForNode()'s
   generic 60s timeout as the only failure ever shown. spawnNodeDetachedWatched()
   pipes stdio and watches ~2.5s, rejecting immediately with the daemon's
   own stderr on an early exit; a survivor has its streams released and
   runs fully detached exactly as before. First version bounded the
   captured text by line count and a live test showed that cut the actual
   OSError line -- two uvicorn/asyncio tracebacks followed it in the real
   capture -- so it is bounded by characters instead.

3. No hint that a startup-mode choice exists. The install-time radio page
   was the only place this was ever offered, and nothing replaces it now
   that no install-time page can exist at all. SetupWelcome (the existing
   first-run banner) grew a conditional hint, shown only while a bundled
   node is present and neither autostart nor service mode is configured
   yet. Considered and rejected: linking straight to the Node page -- its
   route is gated on a linked hub node key, false on the exact fresh-install
   screen this hint targets, so the link would have been dead on arrival.
   New key setup.node_startup_hint, added to all ten locale catalogues.

test_packaging_win.py gained six tests pinning all three (69 total).
Full plan and verification detail: C:\Users\admin\devel\msix-installer.md
section 13 (out of repo).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: gate the create-group wizard on whether a node is bundled</title>
<updated>2026-09-11T15:51:51Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-11T12:17:55Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=d4935aa2a28fcbab8c3556e3532e53667092701e'/>
<id>urn:sha1:d4935aa2a28fcbab8c3556e3532e53667092701e</id>
<content type='text'>
MeshBay Light has no bundled meshbay-node.exe, so the create-group wizard
(which assumes it can start a local node) needs its own signal, not just
platform.node.available. main.js exposes it over IPC (node:bundled) by
checking the packaged resources directory rather than trusting a build-time
constant; preload.js and platform.js carry it through the usual
contextBridge/wrapper path.

winCanElevateServiceMode() replaces the two prior 'app.isPackaged' checks
for whether the app can offer service-mode elevation -- Light is packaged
but has no service-mode.ps1 to elevate into, so packaged alone was already
the wrong test even before this target existed.

create-group-page.js gates the wizard step that starts a node on the new
capability instead of hiding the whole feature; node-page.js's comment fix
is unrelated cosmetic drift caught in the same pass.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: the PDF preview needs object-src and frame-src, in both policies</title>
<updated>2026-09-09T13:48:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T13:48:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=56776934c2ddfbad4884b252da6f5fb25864b8db'/>
<id>urn:sha1:56776934c2ddfbad4884b252da6f5fb25864b8db</id>
<content type='text'>
A PDF preview showed the "this browser will not display the PDF inline"
fallback everywhere — in the desktop client since its first launch, and in
the browser since the hub started sending a CSP on 2026-09-01. It read as a
missing native feature because before that commit the hub sent no policy at
all, so Chrome had once worked and the application never had.

Two directives govern one feature. `files-app.js` decrypts the file in the
page and hands it to `&lt;object type="application/pdf"&gt;` from a Blob; Chromium
loads that as plugin data (`object-src`, absent and therefore falling back to
`default-src 'none'`) and then renders it in an internal frame (`frame-src`).
Opening either alone changes nothing visible — the second refusal produces the
same fallback. `'self'` covers neither: a same-origin `blob:` URL is not
matched by it in either directive, measured in Chrome 152 against the deployed
page and in Electron 44 against the client's own policy.

`plugins` stays at its default `false`: the built-in viewer is not behind that
flag on Electron 44, verified by rendering one.

Widening `object-src` from `'none'` to `blob:` admits only what page script
minted itself, at a type this code sets — PDFium parsing bytes that came from
a node, which is what any browser does with the same file once downloaded.

Tests: each policy is pinned to carry `blob:` in both directives (each fails
if either token is removed), and the two policies are now held identical
directive by directive apart from the two deliberate differences — the comment
claiming they were the same had already drifted and nothing checked it. The
CSP source parser in test_desktop_shell.py read `//` comment lines as
directives, which is the "parse directives, not text" mistake this file
already records; it skips them now.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XauykfBvRrpy6RYbF6F7Wu
</content>
</entry>
<entry>
<title>feat: MNP 3.0 — a transfer needs a lease</title>
<updated>2026-09-09T12:00:22Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T12:00:22Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=53ea44cb03ef6f8d941f6c8c9446551b0c5cd1ac'/>
<id>urn:sha1:53ea44cb03ef6f8d941f6c8c9446551b0c5cd1ac</id>
<content type='text'>
Stage 4 of ~/next/improve-downloads.md, the flag day. Leases become compulsory
and a 2.x peer is refused at the handshake.

**The bound on leaseless reads (§3.4.1) did not exist, and it is what makes the
rest mean anything.** Browsing a group is never subject to a transfer slot —
that is an operator decision and a requirement: a member must be able to browse
a group at capacity exactly as they browse an idle one. But "not leased" cannot
mean "unbounded", or a client that simply omits `tr` transfers outside every cap
and the caps are decoration. A session may now read two distinct files at once
without a lease: one because a viewer looks at one file, two so that prefetching
the next photo stays possible. A count of files and not a byte budget, because a
RAW photo is 60-80 MB and is browsing while a 40 MB archive is a download, and
no size threshold separates them. Thumbnails, posters and cover art never reach
this check at all — they resolve out of the node's own cache.

It is a fairness control among cooperating clients, in the company of
`max_concurrent_streams`, and is not a defence against a member determined to
saturate a node's disk. That member is a member, and the answer to them is
`member revoke`.

**MNP_VERSION and MNP_MIN_SUPPORTED both move to 3.0**, on both sides. The
messages are additive; the requirement is not. An opt-in switch would leave a
leaseless branch reachable on every node, which is finding C6's lesson — a
transport that accepted a bare JWT — one feature later.

**The desktop client now checks before it connects.** The SPA is served by the
hub and picks up a new client on reload; the application ships its own
interface, so an un-updated one would sign in, list groups, and fail every
connection with `version_too_old` — a refusal in a protocol vocabulary with
nothing anyone can act on. It asks `/v1/hub/version` for `client.minimum` and
says so plainly instead. An unreachable hub is deliberately *not* "too old": a
captive portal or a closed laptop must not make starting the application
impossible.

**Every package is aligned on 0.13.0.** `meshbay-client/package.json` had
drifted to 1.0.0 while the Python packages were on 0.12.0 — invisible until
something compared those numbers, and then load-bearing: an installed client
announcing 1.0.0 sorts above a 0.13.0 minimum and walks through the gate meant
to stop it. That is stated in the code rather than left to be rediscovered; it
is acceptable exactly once, because the operator is updating every client, node
and hub by hand for this flag day. A new test fails if two packages ever
disagree again, and another fails if the hub would refuse the client the tree
builds.

Node suite 1209 passed, hub suite 861 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
</feed>
