Skip to content

feat(library): Library page with asset thumbnails (T-J1, T-J2) - #441

Merged
lightningpixel merged 9 commits into
devfrom
feat/library-page
Nov 13, 2026
Merged

lightningpixel merged 9 commits into
devfrom
feat/library-page

Conversation

@lightningpixel

Copy link
Copy Markdown
Owner

Summary

Adds a Library page: a thumbnail grid of every workspace asset, plus the thumbnail pipeline behind it (T-J1 + T-J2).

Stacked on #440 (page registry, T-I1). Merge #440 first; this diff then shrinks to the Library work.

Refs #406, #407

Thumbnails (T-J1)

  • Rendered offscreen in the renderer with three.js, using Viewer3D's default look (background, lights, fov, camera direction, ACES, sRGB). This avoids a headless GL setup in Python.
  • A serial render queue (src/shared/thumbnails/) renders one mesh at a time. three.js is lazy-loaded, so the main bundle stays small.
  • Output is a 256×256 WebP, cached in userData/thumbnails/. The cache key is workspace dir + path + mtime/size + render revision, so each file version is rendered at most once. A failed version is not retried until the file changes.
  • Served over a dedicated modly-thumb:// scheme, added to the CSP img-src.
  • When thumbnails get made:
    • whenever the viewer shows a finished mesh (generation, import, smooth/decimate, agent edits, undo/redo);
    • on startup backfill;
    • when the library is listed.
  • Deleting an asset removes its thumbnail. Every library listing also prunes orphaned and outdated thumbnails.
  • Library entries now carry thumbnailUrl.

Library page (T-J2)

  • src/areas/library/LibraryPage.tsx, registered in the page registry (Manage group).
  • Windowed grid: only visible rows are mounted, with no new dependency. List mode reuses the same code.
  • Toolbar:
    • search (/ focuses, Esc clears);
    • source filter with counts;
    • type filter;
    • sort (newest, type, name);
    • grid/list toggle, persisted.
  • Tiles repaint on their own when their thumbnail finishes, through a small zustand store keyed by path. The library is not re-listed.
  • Unopenable assets are dimmed with a lock badge and show the existing openability reason.
  • Double-click or Enter opens an asset in Generate, through a shared openAssetLibraryEntry helper. Arrow keys move the selection.
  • The Generate library picker stays compact and gains an "Open Library →" link.

Known limits

  • Draco- and meshopt-compressed GLBs get no thumbnail: the loader has no decoders yet.
  • Backfill renders in library order, not visible tiles first.
  • After a file changes, its tile shows the old thumbnail until the new render finishes.

Test plan

  • npm run lint
  • npm run test:node: 285 + 218 pass, 0 fail (new tests for the cache, queue, store, filters and grid windowing)
  • npm run build
  • Manually in npm run dev:
    • the Library page shows the workspace (422 assets);
    • a thumbnail renders, saves and displays.
  • Check that thumbnails load in a packaged build (custom scheme + CSP).

…sion

Thumbnails live in a Modly-owned folder keyed by a hash of workspace dir +
path, with the file's mtime and size in the name, so an edited mesh misses
the cache. Library entries carry thumbnailUrl when the current version is
cached, and pruning drops thumbnails of deleted or changed meshes.
…/save IPC

A privileged custom scheme works for both the dev-server and packaged
renderer origins, and only resolves file names from the cache. Deleting a
job also drops its thumbnail.
The renderer draws each mesh with Viewer3D's default rig in a single
offscreen WebGL context, one at a time, whenever a mesh reaches the viewer
(generation, import, edits, library opens) and for library entries still
missing one. three.js is loaded lazily so it stays out of the entry chunk.
…epaint

The render queue reports each thumbnail it ends a request with; the service records it by workspace path in a small zustand store. A tile reads its own key, so a background render repaints that tile without re-listing the library.
…page

selectLibraryEntries reuses the Generate picker's visibility, search and sort rules and adds capability and source-scope filters, as one flat list. gridWindow holds the windowing math (columns, visible rows, reveal on keyboard moves). openAssetLibraryEntry factors the picker's open flow so another page can open an entry in Generate.
The test imports ./pageRegistry without an extension, so the plain node --test pass could not resolve it.
New Library page (group manage) listing workspace assets as square thumbnail tiles or a compact list, with search, source-scope and type filters, sort, and a grid/list toggle. Only the rows near the viewport are mounted, so large workspaces scroll at constant cost. Tiles without a thumbnail show their capability icon and repaint when a background render lands; unopenable entries stay dimmed with their reason. Double-click (or Enter, or the status-bar button) opens the asset in Generate.
The Generate library popover stays a compact picker, now with an Open Library link, and opens entries through the shared openAssetLibraryEntry flow.
@lightningpixel
lightningpixel merged commit 8bb7653 into dev Nov 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant