Skip to content

Fix launch crash and empty-library bugs; add multiple libraries + manifest inference (1.1.0) - #1

Merged
AxialForge merged 8 commits into
mainfrom
claude/shopdeck-file-selection-bug-j00q7y
Aug 1, 2026
Merged

AxialForge merged 8 commits into
mainfrom
claude/shopdeck-file-selection-bug-j00q7y

Conversation

@AxialForge

@AxialForge AxialForge commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Started from the reported bug — "the app won't let me see or select files," a fatal dialog on launch — and fixed the full chain it uncovered, verified on a real Windows + OneDrive setup. Full write-up in FIXES.md.

Bugs fixed

1. Launch crash — UNKNOWN: unknown error, watch (44a3ec6)
The library watcher's recursive fs.watch only guarded its synchronous setup. On Windows, a recursive watch over an OneDrive-synced folder (the default Documents root) or a network share fires an asynchronous error event with no listener, so Node re-throws it and the main process dies before the window renders. Fix: attach an 'error' listener (degrade to manual refresh) + main-process uncaughtException/unhandledRejection guards.

2. Unreachable library hung the UI on "Loading…" (d730fcc)
Every IPC handler funnels through libraryRoot(), whose fs.mkdir threw on an offline share/deleted folder; library:scan rejected and the renderer's reload() had no try/catch. Fix: libraryRoot() never throws, library:scan returns a soft error object, and the renderer shows a dismissable error banner with Retry.

3. Folder shown but 0 modules — OneDrive on-demand files (0aa5aee)
OneDrive "Files On-Demand" placeholders are reparse points that readdir reports as symlinks, so entry.isFile() was false and the scan skipped every module while still listing the directory. Fix: an entryKind() helper that stat-resolves ambiguous entries (metadata only, no download), plus a self-diagnosing empty state (htmlCount + skip reasons).

4. Standalone-tool timelines had no manifest (627ec6a)
Files exported by the standalone tool carry a <title> and a const DATA = {…} but no module-manifest, so they were correctly found but rejected. Fix: ShopDeck now infers a manifest when one is absent — id from the filename, title from <title>, and for tool-swap timelines the part/event-count/date-range/tags from DATA. Inferred modules are flagged inferred: true (card shows "· no manifest"); real manifests stay authoritative. Import no longer rejects manifest-less HTML.

Features

  • Multiple libraries + rename (d730fcc). New pure-Node electron/libraries.js: a library is a named pointer { id, name, root }. Rename edits the app-side label only (the folder on disk is never renamed); remove just unlinks it (files stay); the last library can't be removed; legacy single-root installs migrate automatically. New IPC libraries:list/add/rename/remove/switch/reveal, a top-bar switcher, and a Libraries manager in Settings.
  • On-demand Windows installer build (d8ec34c, c967885). build-windows.yml builds NSIS + portable installers on windows-latest and uploads them as a private artifact — used for the test builds throughout. release.yml (public release on a v* tag) is unchanged.

Version

Bumped to 1.1.0 (3a363df) — minor, since it adds features on top of the fixes.

Tests

test/library.test.mjs + test/libraries.test.mjs — 24 pure-Node tests passing, including OneDrive/symlink discovery and manifest inference (timeline + generic). CI (node --test, Ubuntu) and the Windows installer build are both green. Electron can't be launched in the CI/agent environment (install scripts blocked, per CLAUDE.md); each fix was confirmed on the user's real Windows machine via a fresh installer build, ending with all 9 timelines showing correctly.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83

AxialForge and others added 2 commits July 31, 2026 11:08
The library watcher created a recursive fs.watch over the library root but
only guarded the synchronous setup call. On Windows a recursive watch over an
OneDrive-synced Documents folder (the default root) or a network share emits
an asynchronous 'error' event ("UNKNOWN: unknown error, watch") from the
FSWatcher handle. With no 'error' listener, Node's EventEmitter rethrew it as
an uncaught exception, killing the main process with the fatal "A JavaScript
error occurred in the main process" dialog — leaving the window blank so files
could not be seen or selected.

Attach an 'error' listener that closes the watcher and degrades to manual
refresh, and add main-process uncaughtException/unhandledRejection guards so a
stray async failure no longer takes the whole app down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
…egrade gracefully

Feature — multiple named libraries:
- New pure-Node model electron/libraries.js: a library is a named pointer
  { id, name, root }. Renaming edits the app-side label only; the folder on
  disk is never touched (consistent with the title/tag override design).
  Removing a library just unlinks it — files stay on disk — and the last
  library can't be removed. Legacy single-root installs migrate automatically.
- main.js keeps a normalized { libraries, activeLibraryId } view; the existing
  libraryRoot() now resolves the ACTIVE library, so every downstream IPC
  handler keeps working unchanged. New IPC: libraries:list/add/rename/remove/
  switch/reveal, plus library:chooseRoot repoints the active library.
- preload exposes window.shopdeck.libraries.*.
- Renderer: an active-library switcher in the top bar and a full Libraries
  manager in Settings (use / rename / reveal / remove / add). Rename uses the
  in-app modal (Electron has no window.prompt).
- Unit tests for the model (runs under the existing pure-Node CI).

Fix — sibling of the fs.watch crash (a network-share failure must not brick
the app): every IPC handler funnels through libraryRoot(), whose fs.mkdir
threw when the configured root was an offline share or a deleted folder;
library:scan then rejected and the renderer's reload() (no try/catch) hung on
"Loading…" forever. Now libraryRoot() never throws, scan degrades to a soft
error object, and the renderer shows a dismissable error banner with Retry
and Manage libraries instead of an empty window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
@AxialForge AxialForge changed the title Fix main-process crash from async fs.watch error on launch Fix launch crash, add multiple libraries with rename, and make an unreachable library degrade gracefully Jul 31, 2026
AxialForge and others added 6 commits July 31, 2026 11:31
Builds the NSIS + portable installers on a windows-latest runner and uploads
them as a private, auto-expiring workflow artifact — a way to produce an
installer without cutting a public GitHub Release (that stays with release.yml
on v* tags). Triggers via workflow_dispatch or a winbuild-* tag push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
Tag pushes aren't permitted from the current environment, so add a branch
trigger for this WIP branch to kick off the installer build via CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
…ing empty state

Root cause of "the library shows the folder but no modules": OneDrive
"Files On-Demand" placeholders are reparse points, and
fs.readdir(withFileTypes) reports them as symlinks — dirent.isFile() is
false — so the scan's walk() skipped every .html module while still listing
the (real) directory. Moving the files to another folder on the same volume
keeps the placeholder, so it reproduced off OneDrive too.

- New entryKind() helper: trust the dirent when decisive, otherwise stat to
  resolve the real type (stat reads metadata only; it does not download an
  online-only file). walk() and walkHtmlUnder() use it, so placeholder/symlink
  .html files are discovered. A realpath-keyed seen-set guards against cycles
  now that reparse entries are followed.
- scanLibrary now returns htmlCount + a `skipped` list (file + reason) for
  files that were found but couldn't become modules.
- The library's empty state uses those to explain itself: "Found N HTML files
  here, but none are valid modules … First: <file> — <reason>." instead of a
  bare "Library is empty," so any remaining cause is visible without guesswork.
- Tests: a symlinked module is discovered (reproduces the placeholder dirent),
  and htmlCount/skip-reasons are reported for invalid files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
…load

Files exported by the standalone timeline tool carry no embedded
module-manifest — only a <title> and a `const DATA = {…}` object — so ShopDeck
listed the folder but no modules. Support both kinds of file:

- scanLibrary now synthesises a manifest when a file has none (or an invalid
  one): id from the filename, title from <title>, and — when the embedded
  timeline DATA is present — type tool-swap-timeline enriched with part, event
  count, date range, and operation tags pulled straight from DATA. Such modules
  are flagged inferred:true; files with a real manifest stay authoritative.
- importFiles / importTree no longer reject manifest-less HTML — any
  self-contained HTML can be imported and is inferred on scan.
- The card shows "· no manifest" for inferred modules; the empty-state wording
  no longer claims a manifest is required.
- Tests: inference of a tool-swap timeline from its DATA (part/events/tags/
  dates), generic inference from <title>/filename, and import of manifest-less
  files. Verified against the real exported file (part 40-1318-01, 147 events).
- CLAUDE.md: document that the manifest is now optional (inferred when absent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
Records each of the four bugs (launch crash, unreachable-library hang, empty
OneDrive scan, manifest-less timelines) with symptom / root cause / fix / files
/ commit, plus the multi-library feature and the Windows installer build
workflow, and a summary table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
Minor release: adds multiple libraries + rename and manifest inference for
standalone-tool timelines, on top of the launch-crash, unreachable-library,
and empty-OneDrive-scan fixes. Installers produced from this branch are now
named shopdeck-1.1.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rbmo7LSmxRtFf5zwfvw83
@AxialForge AxialForge changed the title Fix launch crash, add multiple libraries with rename, and make an unreachable library degrade gracefully Fix launch crash and empty-library bugs; add multiple libraries + manifest inference (1.1.0) Aug 1, 2026
@AxialForge
AxialForge marked this pull request as ready for review August 1, 2026 01:31
@AxialForge
AxialForge merged commit 892db5d into main Aug 1, 2026
2 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