Skip to content

Folder/ZIP bundle grouping, multi-model preview, and Send to Slicer - #65

Closed
thrtnastrx wants to merge 1 commit into
TechJeeper:mainfrom
thrtnastrx:feature/bundle-grouping
Closed

Folder/ZIP bundle grouping, multi-model preview, and Send to Slicer#65
thrtnastrx wants to merge 1 commit into
TechJeeper:mainfrom
thrtnastrx:feature/bundle-grouping

Conversation

@thrtnastrx

Copy link
Copy Markdown
Contributor

Summary

This PR improves how multi-part projects (folders and ZIP archives) appear in the library and how they flow into preview and slicing.

  • Bundle grouping — Models sharing the same parent folder or ZIP archive (2+ STL/3MF files) collapse into one grouped row in List, Preview, and Detailed views. Singles stay ungrouped.
  • Bundle 3D preview — Click a bundle to open one preview with every part on a grid (color-tinted, up to 32 parts).
  • Bundle details — Double-click a bundle for path, stats, and a sortable file table; chevron still expands/collapses in the grid.
  • Send to Slicer — New button in the 3D preview dialog for single models and full bundles; supports multiple configured slicers.
  • New slicer instance — macOS uses open -n; Prusa-family binaries get --single-instance=0 so sends work when a slicer is already open.
  • Database — Adds bundleKey, bundleLabel, bundleKind on models with startup migration/backfill.
  • Testsnpm run test:bundle for bundle-keys.js.

Documentation

  • CHANGELOG.md — full feature list and migration notes
  • GUIDE.md — bundle groups section and updated slicer docs
  • README.md — feature bullets
  • In-app quick start (guide.js) — bundle and slicer bullets

Test plan

  • npm run test:bundle passes
  • Scan a directory with multi-file subfolders — bundles appear with correct labels
  • Scan a ZIP with multiple models (ZIP scanning enabled) — zip bundle groups correctly
  • Click bundle → combined 3D preview loads all parts
  • Double-click bundle → bundle details panel
  • Configure slicer(s) in Settings → Send to Slicer from preview opens model(s)
  • Send again while slicer is open → new instance/window with model loaded
  • Right-click Open in Slicer still works for single files

Notes for maintainer

  • Bundle grouping is heuristic (folder path / zip container); no new manual grouping UI required.
  • ZIP entry paths use existing zipPath::entry format; slicer send extracts via existing extractModelFromZip.

Made with Cursor

…ration.

Group related STLs by folder or zip archive, preview all bundle parts in one 3D
layout, send models to configured slicers from preview (new instance on macOS),
and document behavior in CHANGELOG, GUIDE, and README.

Co-authored-by: Cursor <cursoragent@cursor.com>
TechJeeper added a commit that referenced this pull request Jul 19, 2026
Conflict-resolved integration of folder/ZIP bundle grouping from #65 into main 2.1.7.
@TechJeeper

Copy link
Copy Markdown
Owner

Thanks for this contribution!

#65 could not be merged as-is (mergeable_state: dirty against current main / 2.1.7). Conflicts overlapped with work already on main (ZIP/parent grouping UI, rating/favorite columns, 3MF simplified preview notes).

Conflict-resolved merge landed via #69 (merge/pr-65-resolved), which keeps:

  • your folder/ZIP bundle grouping, multi-model preview, Send to Slicer, and bundleKey DB fields
  • existing main features (rating/favorite, New badge helpers, parent-model nesting, 3MF simplified notes)

Closing this PR as completed via #69.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

I'll take a look. This PR (#65) is closed and was superseded by #69 (merge/pr-65-resolved). Since the real, merged code lives in #69, let me review the key logic that landed there — particularly the bundle-keys.js derivation logic, which is the shared heuristic that everything else depends on.

Let me start by examining the repository to understand what actually merged.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

The checkout is the PR #65 head (a4982f9). This is an Electron desktop app — not a web app I can drive with a URL, and there's no easy way to run the full GUI. But the bundle-keys.js logic is pure, testable Node.js, and it's the foundation everything else depends on. Let me focus my review there where I can actually run the code and catch real bugs.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Now let me actually run this logic against a range of real-world inputs to find bugs. There's a subtle one I want to verify: filePath.includes('::') for zip detection vs url:: — but also the Windows path handling for path.dirname (the code runs path.dirname on a raw path before normalizing separators, which on Linux won't split Windows backslash paths).

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Confirmed a real bug (and note the extra escaping is just my shell — the actual input is a normal Windows path C:\Models\project\part1.stl). Two findings so far:

  1. Windows folder paths don't group at all. For a plain Windows folder path like C:\Models\project\part1.stl, path.dirname() runs on POSIX path in the app's runtime, which doesn't recognize \ as a separator, so it returns . → empty bundle. Two sibling STLs in a Windows folder won't group. The zip branch normalizes before deriving the label, but the folder branch calls path.dirname on the raw (un-normalized) path. This is a genuine cross-platform bug — Printventory is primarily a Windows app.

Let me verify this is really how it behaves and check the zip-label edge case too.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

The shipped tests pass but they only test a POSIX folder path — they never cover a Windows folder path, which is exactly the broken case. Let me check how main.js actually stores filePath for scanned files (to confirm Windows paths reach this function with backslashes), and check the url:: ordering issue.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Confirmed. The scan builds filePath with path.join(dirPath, entry.name), which on Windows uses backslashes. So real Windows filePaths reaching deriveBundleFromFilePath will contain \, and the folder branch (which calls path.dirname before normalizing) will fail to group them. On the packaged Windows app, path.dirname uses the Windows implementation so it would work there — but this exact ambiguity is the risk. Let me verify precisely: on Windows, path is path.win32, so path.dirname("C:\\Models\\project\\part1.stl") returns C:\Models\project. Then normalizePath fixes the label. So on Windows it works.

The real, unambiguous bug is different: the zip bundleKey is inconsistent between platforms and mixed separators, and folder grouping is separator-sensitive. Let me nail down the one that reliably breaks: a zip whose path uses backslashes vs forward — but more importantly, let me confirm the concrete cross-platform grouping failure by simulating both path implementations.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

There's the real, concrete bug, now clearly demonstrated. Even under the Windows path implementation, path.dirname("C:\Models\project\part1.stl") returns C:\Models\project — and because normalizePath (the \\ → / replace) is applied to dir after dirname, it works there. But look at the win32 output more carefully: the trailing \\ is my shell escaping. The genuine defect is the ordering asymmetry: the zip branch normalizes separators before processing; the folder branch does not normalize before path.dirname. That means grouping correctness depends entirely on which path platform module is loaded, which is fragile and untested.

Let me write a small reproduction test that pins the bug down deterministically regardless of host platform, then post the review.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Important correction to my own analysis: on Windows (where path = path.win32), the folder branch does work correctly — grouping succeeds and the label is right. So this is not a Windows-runtime bug. The failure I saw earlier only happens when the function runs on POSIX (path.posix) against a Windows-style backslash path.

Now — does that POSIX-vs-Windows-path scenario actually occur in this app? Yes, in one specific place: server-bridge.js (the web/server mode). Let me check whether bundle derivation runs server-side on Linux against stored Windows paths, and check the saveModel path too.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

So it's only used in main.js (the Electron main process). On a Windows install that's path.win32 → works. On a Linux/macOS install scanning local (POSIX) paths → works. The broken case is only when a database is backfilled/migrated on POSIX with Windows-style stored paths (e.g. a DB copied from a Windows machine, or the Docker/Linux server mode reading a Windows-populated DB). That's a real but narrower scenario. Let me confirm whether the Linux server path ever backfills.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Confirmed the shape of it. The migration/backfill runs in main.js's initializeDatabase(). The Docker image runs the Electron main via xvfb on Linux → path is path.posix there. If a user runs Printventory in Docker/Linux against a database whose filePath values use Windows backslashes (a very plausible cross-platform / server-mode scenario given this app ships a Dockerfile and server-bridge.js), the folder backfill silently produces empty bundle keys and folder bundles won't group.

That's a legitimate, defensible finding. The clean fix is to normalizePath before deriving the directory, and split on / manually (or use path.posix.dirname on the normalized path) so behavior is platform-independent — exactly as the zip branch already does.

I have enough to post a well-grounded review. Let me write it up.

@testdriverai testdriverai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is closed (superseded by #69), so this is a review of the code as it stands on feature/bundle-groupingbundle-keys.js is the shared heuristic that scanning, backfill, preview, and Send-to-Slicer all depend on, so I focused there and ran the actual deriveBundleFromFilePath logic against a spread of real inputs.

What I checked, by running it (node bundle-keys.test.js + ad-hoc cases):

  • ✅ POSIX folder grouping, zip-entry grouping, and url:: exclusion all behave as the shipped tests claim. Those three pass.
  • ✅ On the Windows runtime (path.win32), folder grouping and labels are correct — so this is not a bug on a normal Windows install.
  • ⚠️ One real cross-platform bug (inline): the folder branch calls path.dirname() on the raw, un-normalized path, while the zip branch normalizes separators first. When this runs on POSIX (path.posix) against Windows-style backslash paths — which happens in the Linux/Docker/server-bridge runtime this repo ships, backfilling a DB whose filePaths use \path.dirname() returns . and the folder bundle comes back empty, so multi-part folders silently don't group. The shipped tests miss it because they only cover a forward-slash folder path.

Verdict: the feature logic is sound on the primary Windows target; the folder branch just needs to normalize separators before deriving the directory (mirroring what the zip branch already does) to be platform-independent. Not a merge blocker for Windows users, worth fixing for the Linux/server path. Everything else in the diff I could reason about (migration DDL, scan insert/update binding the three new columns, getAllModels/filtered SELECT column list) looks internally consistent.

Note: this is a desktop Electron app with no drivable URL, so I verified the pure bundle-keys module directly rather than driving the GUI.

Comment thread bundle-keys.js
};
}

const dir = path.dirname(filePath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎥 Observed in a test run

Folder grouping breaks on POSIX for Windows-style paths — separators aren't normalized before path.dirname.

The zip branch above normalizes first (normalizePath(zipPath) on line 22, then derives), but the folder branch calls path.dirname(filePath) on the raw path and only normalizes the result. When path resolves to path.posix (the Linux/Docker/server-bridge runtime this repo ships), path.dirname doesn't treat \ as a separator.

I ran the shipped function under path.posix with a Windows-style path:

derive('C:\\Models\\project\\part1.stl')
// => { bundleKey: '', bundleLabel: '', bundleKind: '' }
derive('C:\\Models\\project\\part2.stl')
// => { bundleKey: '', bundleLabel: '', bundleKind: '' }

path.dirname returns ".", so the === '.' guard on line 34 returns empty and the two siblings never group. This bites migrateBundleColumns() backfill (main.js:1811) and scan insert/update (main.js:2940) whenever the process runs on Linux against a DB populated with Windows paths — exactly the cross-platform / Docker scenario this repo supports. On a native Windows install (path.win32) it works, which is why the bug is easy to miss.

The shipped tests pass but only cover a forward-slash folder path, so they never exercise this. Fix: normalize first, then derive, e.g.

const normalized = normalizePath(filePath);
const normalizedDir = path.posix.dirname(normalized);

so folder behavior is separator-independent, matching the zip branch.

@testdriverai

testdriverai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review posted. Since this is an Electron desktop app with no drivable GUI, I reviewed by running the actual bundle-keys.js logic rather than driving a browser — no session needed.

Summary

I reviewed PR #65 (closed, superseded by #69). bundle-keys.js is the shared heuristic everything else builds on, so I ran deriveBundleFromFilePath against a spread of real inputs.

What passed (verified by running):

  • POSIX folder grouping, zip-entry grouping, and url:: exclusion all behave correctly — the 3 shipped tests pass.
  • On the Windows runtime (path.win32), folder grouping and labels are correct, so this is not broken for a normal Windows install.

One real bug (observed, posted inline):

  • The folder branch calls path.dirname() on the raw, un-normalized path, while the zip branch normalizes separators first. When the code runs on POSIX (path.posix) against Windows-style backslash paths — the Linux/Docker/server-bridge runtime this repo ships — path.dirname returns ., the guard bails, and the folder bundle comes back empty. So multi-part folders silently fail to group when backfilling/scanning a Windows-populated DB on Linux. The shipped tests miss it because they only cover a forward-slash folder path.
  • Fix: normalize separators before deriving the directory (mirroring the zip branch), e.g. path.posix.dirname(normalizePath(filePath)).

The rest of the diff I could reason about (migration DDL, the scan insert/update binding the three new columns, the SELECT column lists) is internally consistent.

I labeled the finding observed because I actually executed the shipped function and watched it return empty keys — not inferred it from reading. Since #65 is closed, the fix belongs in #69; happy to re-check it there if you'd like.

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.

2 participants