<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages, branch 0.8</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.8</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.8'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-08-28T07:27:50Z</updated>
<entry>
<title>chore: release 0.8.0</title>
<updated>2026-08-28T07:27:50Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-28T07:27:50Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=c170b9cffd2b8fb4b7ef919ee62c28d2daaa8711'/>
<id>urn:sha1:c170b9cffd2b8fb4b7ef919ee62c28d2daaa8711</id>
<content type='text'>
Bump the three packages to 0.8.0 (released together) and realign
meshbay-common's __version__, which had drifted to 0.7.0 while the
pyproject stayed at 0.6.0. Dependency pins updated to meshbay-common&gt;=0.8.0.

Protocol versions are independent and unchanged: MNP 0.12, MHP 0.1. The
Electron client stays on its own 0.1.0 track (hub MIN_CLIENT_VERSION).

Since 0.6/0.7: public-groups admin switch with full server-side enforcement
and a group Revoke action; chat link previews (node-side URL unfurl, SSRF-
gated); chat composer focus + scroll-to-bottom on tab entry; whole-group
"Filter files" search.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
</content>
</entry>
<entry>
<title>fix(files): make "Filter files" search the whole group</title>
<updated>2026-08-28T07:10:23Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-28T07:10:23Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=4b7d52a28e9a02d55574b2f08b8030ba179caeff'/>
<id>urn:sha1:4b7d52a28e9a02d55574b2f08b8030ba179caeff</id>
<content type='text'>
The filter only ever matched files whose folder was exactly the current
path, so at the top of a group — where every row is a root, never a loose
file — typing in the field did nothing at all.

With text in the field the panel now searches every file in the group by
name or containing folder, wherever it lives, and hides the folder rows
(you are searching, not browsing). Each result shows its folder as a
sub-line; clicking it clears the filter and opens that folder. Empty field
restores the normal folder-by-folder view unchanged.

Also hardened the sort comparators against an entry missing `name`/`type`.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
</content>
</entry>
<entry>
<title>feat(chat): focus the composer when the Chat tab opens</title>
<updated>2026-08-28T06:34:41Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-28T06:34:41Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=45cbe43a21a9d046afebb248b4bd455bf0fd9cd0'/>
<id>urn:sha1:45cbe43a21a9d046afebb248b4bd455bf0fd9cd0</id>
<content type='text'>
ChatPanel remounts on every tab switch, so a mount effect is the tab-entry
hook. `focus({ preventScroll: true })` so it does not fight the panel-sizing
and scroll-to-bottom effects still settling, and so a phone opens the
keyboard without the view jumping.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
</content>
</entry>
<entry>
<title>Merge branch 'feat/chat-link-previews'</title>
<updated>2026-08-28T01:43:29Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-28T01:43:29Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=25ebaed11772056c611c3ce650387d6961e0baa0'/>
<id>urn:sha1:25ebaed11772056c611c3ce650387d6961e0baa0</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(chat): link previews for pasted URLs</title>
<updated>2026-08-28T01:43:19Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-28T01:43:19Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=ce4e10c4b8bd9c66c375c3a5d5c18d8552655775'/>
<id>urn:sha1:ce4e10c4b8bd9c66c375c3a5d5c18d8552655775</id>
<content type='text'>
Paste an http(s) link in a group's chat and it unfurls into an OpenGraph
card — title, description, site name, and image — the way WhatsApp/Signal/
Slack do it.

The fetch is the node's, never the browser's or the hub's. The browser
cannot: a strict img-src/connect-src and CORS block it, and a direct fetch
would leak every reader's IP to the linked host on each render. The hub must
not touch group content (draft-v6 §2.5). The node already fetches third-party
metadata for the Videos and Music apps, over the same authorised path.

Flow mirrors media_meta_req: the client sends `link_preview_req {url}`, the
node replies `link_preview_resp` with the card fields (or `ok: false`), and
any OG image is stored under its blake3 in the existing media_cache thumb
store — the client then fetches it via the normal file_req path, exactly like
a poster. Nothing durable is added: the card text lives in a bounded in-memory
TTL cache on the node (draft-v6 §2.7 — enrichment on demand, the asking device
caches), and MNP goes 0.11 → 0.12 (additive: an older node logs "unknown type"
and the client shows the bare link).

Because the URL is chosen by a *member* and triggers an outbound request from
the operator's machine, `linkpreview.safe_url` is an SSRF gate: http(s) only,
no credentials, and every resolved address must be globally routable — no
loopback, private, link-local, multicast or reserved range, cloud-metadata
included. Redirects are followed by hand so each hop is re-checked. Residual,
documented in the module: DNS rebinding between the check and connect, closed
properly by pinning the checked IP — a follow-up.

Also fixes a long-standing chat annoyance the preview cards made worse:
opening the Chat tab landed a screen or two above the newest message because
the scroll-to-bottom ran before attachment thumbnails and (now) preview cards
had loaded and grown the content. A ResizeObserver keeps the view pinned to
the bottom through late content growth, and does nothing once the reader
scrolls up.

Tests: test_linkpreview.py (the SSRF gate and the OpenGraph parse, incl.
redirect re-validation and image downscaling) and test_link_preview_request.py
(reply shape, the media_cache image round-trip, the result cache).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
</content>
</entry>
<entry>
<title>feat(hub): let a hub admin disable public groups instance-wide</title>
<updated>2026-08-28T00:51:00Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-28T00:51:00Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=b5b4f188a39fc96c4d32e67151e067b1add6dcfc'/>
<id>urn:sha1:b5b4f188a39fc96c4d32e67151e067b1add6dcfc</id>
<content type='text'>
A new General tab in Administration carries one switch, allow_public_groups,
stored in a hub_settings key/value table (runtime-editable, unlike hub.toml).
Default is on; an absent row means on, so an upgrade changes nothing.

Enforcement is server-side on every hub-mediated path, not just the SPA:

  - create_group        refuses visibility=public (403), staff included
  - list_public_groups  the directory returns nothing (local + federated)
  - join_group          open-joining a public group is refused
  - group_online_nodes  a non-member of a public group is handed no node
  - signaling.webrtc_offer  drops the "node hosts an open group" fallback
  - federation.export_directory  advertises nothing to peer hubs

The switch is read live, so flipping it back restores every path. Existing
members of a group that predates the switch keep their membership row and
their access — this is plan A, not a purge. GET /v1/hub/info exposes the
flag (unauthenticated) so the create-group form and the sidebar's "Public
groups" link render correctly.

Also in the admin Groups tab: a Revoke action beside Suspend. Suspend is the
reversible hub flag; Revoke calls POST /v1/admin/revoke, which sets
status=revoked and broadcasts a signed revocation every node enforces
(denylist + dropped live sessions). It is confirm-guarded and names the group.

And a message fix the revoke work surfaced: group_online_nodes, join_group and
webrtc_offer answered "Group is suspended" for any non-active status. They now
report the real state, so a member of a revoked group is told "Group is
revoked" rather than something reversible-sounding.

Tests: test_public_groups_toggle.py (10) covers the switch end to end and the
five enforcement paths; test_revocation.py gains the status-message assertion.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
</content>
</entry>
<entry>
<title>fix(music): stop sharing one folder's cover across unrelated tracks, and merge various-artists compilations into one album</title>
<updated>2026-08-26T16:09:44Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-26T16:09:44Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6fb948045b9c563ce591da289cdac1df6bed360c'/>
<id>urn:sha1:6fb948045b9c563ce591da289cdac1df6bed360c</id>
<content type='text'>
Two real, confirmed bugs in a large flat music library:

- enrich_audio.py's sibling-cover fallback assumed one folder is one
  release. A large flat "chart ranking" folder mixing dozens of unrelated
  artists carried several distinct WMP AlbumArt-cache guids (one per
  original album a track was ripped from), and the fallback picked
  whichever one WMP had copied to Folder.jpg — attaching one unrelated
  release's cover to every other track in the folder. Now refuses to pick
  a cover at all once 2+ distinct guids show up, rather than guess.

- music-app.js's groupMusicEntries grouped by artist first, album second,
  so a various-artists compilation (many genuinely different per-track
  artists, one shared album tag, no album-artist tag at all — a real
  ~20-track soundtrack rip has exactly this shape) could never be
  recognized as one release: every track landed alone in its own artist's
  bucket and got folded into a singleton pile. Now detects an album key
  shared across 2+ distinct artist keys and merges those tracks into one
  compilation card under a "Various" heading instead.

Both verified against real, previously-affected files and live in the
browser: the shared wrong cover is gone, and the compilation renders as
one card with all its tracks in order.
</content>
</entry>
<entry>
<title>fix(video): a TMDB override never stored the metadata its chosen id names</title>
<updated>2026-08-26T15:09:07Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-26T15:09:07Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=59289b82dc8af08c605fd247c90a5c5ec92c6db3'/>
<id>urn:sha1:59289b82dc8af08c605fd247c90a5c5ec92c6db3</id>
<content type='text'>
Found live: "fix match" appeared to work for two shows but not for a movie
whose own automatic search kept landing on the same wrong result. The
override only ever recorded the file-&gt;tmdb_id mapping — never the
metadata that id actually names. _do_media_meta_request's cache check
agrees the mapping is fresh (same media_type) but finds nothing under
that *new* id in tmdb_meta, since nothing had ever fetched it, and falls
through to a brand-new search using the file's own title — reproducing
the exact match the override was meant to replace.

This stayed invisible for the two shows only because their own title
happened to be enough for that fallback search to land on the right
answer anyway, entirely independent of whatever the override recorded —
never because the override was actually being honored. It surfaced on a
movie whose own title search kept landing on the same wrong match
regardless.

Fetches and stores the real metadata for the chosen tmdb_id up front (via
the existing _tmdb_build_meta, which needs only the id — no search result
object required), so a later lookup finds the override itself instead of
falling through to a search blind to it.
</content>
</entry>
<entry>
<title>fix(video): let the toolbar wrap on mobile — no room left for the filter field</title>
<updated>2026-08-26T14:50:34Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-26T14:50:34Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=8b315b99b941f5e08ab9d622c26fb85d4bc64955'/>
<id>urn:sha1:8b315b99b941f5e08ab9d622c26fb85d4bc64955</id>
<content type='text'>
video-app.js's toolbar isn't built from .toolbar-group, so it never got the
existing row-per-group mobile treatment. Mode buttons plus the new
All/Movies/Series control left nothing for the search field on a phone
width. Wraps now, with the filter field taking its own full-width line
like .toolbar-group's search field already does elsewhere.
</content>
</entry>
<entry>
<title>feat(video): add an All/Movies/Series filter to the toolbar</title>
<updated>2026-08-26T14:45:43Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-26T14:45:43Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=f2b90449841373d9b0fdd70449f3e94c9dd0116d'/>
<id>urn:sha1:f2b90449841373d9b0fdd70449f3e94c9dd0116d</id>
<content type='text'>
A segmented control to the left of the search box, defaulting to "All".
Applied before the text filter — a title match within a type nobody asked
to see still isn't shown. Resets to "All" on group change, matching the
text filter's own reset.
</content>
</entry>
</feed>
