summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
Commit message (Collapse)AuthorAgeFilesLines
* fix(hub): show the Files refresh button only on the Search pageChristophe Besson2026-08-301-5/+6
| | | | | | | | | | | | | In a real group the node streams every index change to connected clients as it happens (onIndexSync / onIndexDelta), so a manual re-fetch button there just duplicates the live push. The cross-group Search page has no such connection — its file list is a one-shot cached fetch — so the button belongs there and only there. Gated on a new explicit `showRefresh` prop that only search-page.js passes; `onRefreshIndex` stays in commonProps for ChatPanel's own use after an attachment upload. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* feat(hub): drop the Search page title, add a Files refresh buttonChristophe Besson2026-08-291-1/+18
| | | | | | | | | | | | | | | | | 1. The "Search Files" <h2> is removed from the search page — it duplicated the nav and cost a line of vertical space on mobile. `search.title` is dropped from all ten locales (its only use). 2. A monochrome refresh button sits left of the Files breadcrumb home icon. It calls `onRefreshIndex` (re-fetch the group index from the node — the file list is a view over a cached copy with no other way to pull a fresh one), spins while in flight, and only renders where an `onRefreshIndex` is wired (the real group view, not the cross-group search results). New `refresh` icon; `group.refresh_index` in all ten locales. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* fix(hub): 10s connect timeout in search page, error on unreachable downloadChristophe Besson2026-08-281-3/+8
| | | | | | | | Search-page connections (indexing and pool) now time out after 10s instead of the transport's default 30s, so down nodes are skipped faster. Downloads show a user-facing error when a group is unreachable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(hub): search downloads, photos view, pre-connect optimizationChristophe Besson2026-08-281-11/+16
| | | | | | | | | | | | | | | | | | Downloads from search: FilesPanel accepts a getTransport callback for cross-group file downloads. Select/Download are available in search; delete and zip are hidden (readOnly). Each download connects to the entry's group via the pool on demand. Photos view: PhotosApp reused in search with the same pattern as Videos/Music — synthetic root, per-entry transport refs, group badges on album cards. Lightbox uses per-entry transport for full-resolution image fetches. Zip download hidden in search context. Pre-connecting: connections to all groups start as soon as indexing finishes, regardless of the active view. Thumbnails cache across pool evictions, so they're ready when the user switches views. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(hub): cross-group search with reuse of existing viewsChristophe Besson2026-08-281-5/+11
| | | | | | | | | | | | | Search page fetches indexes from all groups, then renders consolidated entries through the existing FilesPanel, VideoApp, and MusicApp components — no reimplemented views. Groups appear as top-level directories in the file browser; video/music entries use a synthetic root with per-entry transport refs for thumbnails and metadata across groups. Music player lifted to app.js with getConnection(groupId) for cross-group playback. Connection pool (max 3, LRU eviction) manages lazy WebRTC connections. All 10 locales updated with search keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): make "Filter files" search the whole groupChristophe Besson2026-08-281-6/+19
| | | | | | | | | | | | | | | | | 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
* feat: add Photos group appChristophe Besson2026-08-251-82/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A new group application (docs/apps.md's plug-in mechanism), following the plan in docs/photos.md. Unlike Videos/Music: several photo roots per group instead of one (photo_roots is a set, one signed op replaces it whole), a single album-grid view with no third-party matching step, and per-photo info read from the file's own EXIF at index time — no metadata service, no credential, no outbound network call at all. Protocol (meshbay-common, MNP 0.10 -> 0.11, additive): `taken_at`/`camera` on IndexEntry; `photo_roots`/`photo_roots_ack`; `OP_PHOTO_ROOTS`. Node: roster.py stores photo_roots as a group_settings entry (JSON list, same shape as enabled_apps); ops.py/webrtc_server.py validate and sign the whole set in one op, same pattern as apps_enabled; a new PhotoEnricher (indexer/enrich_photo.py) runs Pillow in its own small bounded pool, separate from the video/audio pools, producing a resized thumbnail plus the two EXIF fields — never GPS, checked by a grep-based regression test. Client: photos-app.js — one album card per directory containing images, a per-album photo grid, and a lightbox with next/previous (keyboard and buttons), zoom in/out/fit/100% starting from the actual on-screen fit percentage, and a "zip this album" button reusing files-app.js's own zip mechanism (lifted into file-utils.js's downloadDirectory so both call the same implementation). group-settings.js gets an add/remove multi-root picker, distinct from Videos/Music's single-value one. Bugs found and fixed before this ever shipped, worth keeping the story of: - enrich_photo.py read width/height from the raw image *before* applying EXIF orientation correction, and read DateTimeOriginal off the plain 0th-IFD Exif object — a real camera stores it in the Exif sub-IFD, which Pillow only exposes via get_ifd(Exif). A flat, hand-built EXIF dict round-trips through Pillow either way, which is exactly what would have hidden both bugs; the regression test builds EXIF with piexif instead, matching what real hardware produces. - photos-app.js's album grouping stripped a trailing path segment from entry.path under the assumption it still carried a filename — it doesn't (files-app.js's own convention: e.path is already the containing directory), so every album collapsed one level into its parent. Found live against a real multi-folder library. - transport.js's ADMIN_OP_TYPES allowlist (already the fix for an identical bug on video_root/apps_enabled, see 4783d81) was missing photo_roots: its admin_challenge matched no pending request and was silently dropped, so saving a photo root just timed out after 30s with no error. - daemon.py pruned a thumbnail when its file left the index (root removed or reconfigured) but never forgot the content hash was "already attempted" — the same bytes reappearing under a renamed/relocated root (an operator's real workflow) were then permanently skipped, forever, with nothing to indicate why. Discarding the attempt alongside the cache entry on prune is what makes pruning actually reversible. - packages/meshbay-client's app:// protocol handler served every file with no Cache-Control header, so Chromium was free to serve a stale cached copy indefinitely — none of several `npm run sync-ui` + reload cycles during development actually picked up the new code until the renderer's disk cache was cleared by hand. Now sends Cache-Control: no-store. - the lightbox's zoomed image used flex centering (align-items/ justify-content: center) combined with overflow: auto — a well-known trap where the browser centers overflowing content by shifting it, and the leading half of that overflow (here, the top of a zoomed photo) sits outside what the scrollport can actually reach. Reported live as "unusable". Fixed by switching to top/left alignment once zoomed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiZG4AuSnxHohQMpwTHTyL
* feat(hub): play audio files from Explorer, scoped to the current folderChristophe Besson2026-08-241-2/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Audio files were invisible to file-utils.js's canPreview, so their name never rendered as a clickable link and the toolbar's Play/View buttons never enabled for them - the only way to do anything with an audio file in Files was to download it. - file-utils.js: canPreview now includes 'audio' alongside image/ video/document. - files-app.js: the toolbar's canPlay/canView split treats audio like video (Play, not View) rather than falling into the generic preview path, which was never built for it anyway. - group-page.js: onPreview routes an audio entry to onPlayQueue - the same persistent player Music uses - instead of the preview modal. Deliberately not Music's artist/album grouping: the queue is every audio entry sharing the clicked file's literal containing directory (entry.path), sorted by filename, so previous/next in Explorer stays scoped to what's actually in that folder, tags or no tags. onPlayQueue already replaces whatever queue is playing unconditionally, so starting a track from Files while Music (or another Files folder) is already playing needs no special handling - it's the same "just a new queue" path either way. Client-side only, no protocol/index change. npm run sync-ui re-run. Full suite: 1129 passed, no regressions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBi7ALLGfwcjBXt57yNMcy
* feat(hub): split the group UI into a pluggable "applications" architectureChristophe Besson2026-08-231-0/+620
GroupPage's 6620-line app.js carried Chat and Files wedged in directly, with no way to add another group-level app without touching the shell itself. It is now app.js (routing, non-group pages) plus nine focused files — apps.js (the registry), chat-app.js, files-app.js, video-player.js, group-page.js (the shell), group-settings.js, hub-client.js, icon.js and file-utils.js — with docs/apps.md as the checklist for adding one (Videos/Music/Photos are sketched there, not built). Node side gained the matching enablement mechanism, mirroring member_upload exactly: a roster setting, a signed apps_enabled op enforced by _has_admin_authority, exposed in the handshake ack. Operators toggle applications per group from Settings, which also gained a small reorder: Invite, Pairing, Applications, Shared directories, Uploads, danger zone, Your devices, Members. Two bugs surfaced during the split, both missing an import across the new file boundary and invisible to node --check or a module-load probe since they only throw when the code path actually runs: - group-page.js called onRefreshAuth on a stale-token handshake rejection, but app.js never imported refreshAccessToken from hub-client.js — so a brand new member (including a group's own creator) hit "Not a member of this group" and the retry silently failed, throwing before it could refresh the token. - chat-app.js called getLocale() for message timestamps without importing it from i18n.js. Opening Chat on a group with real messages threw mid- render; uncaught, that appears to wedge Preact's render scheduler, so every button on the page stopped responding until reload. Caught the second class of bug with a proper no-undef audit across all split files (a temporarily installed ESLint 9, since the system one is too old to parse this codebase's syntax) rather than trusting grep. 827 tests pass; 6 new ones cover the apps_enabled policy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016SF6RKNBKg9qejmoMJ9ybA