refactor: extract the copy-paste the jscpd lanes were tracking, and split the longest test file - #71
Closed
vdavid wants to merge 17 commits into
Closed
refactor: extract the copy-paste the jscpd lanes were tracking, and split the longest test file#71vdavid wants to merge 17 commits into
vdavid wants to merge 17 commits into
Conversation
…ard, shortcut pill, and toast bodies shared by two surfaces each Five frontend copy-paste pairs from the `jscpd-frontend` inventory, each replaced by one shared module so the two surfaces can no longer drift apart: - `StepBeta.svelte` and `UpdatesSection.svelte` both carried the beta contact email field's state and handlers (persist on keystroke, subscribe on commit, typed feedback). Now `settings/sections/beta-email-signup.svelte.ts` (`createBetaEmailSignup()`, per-mount `$state` behind getters). - `StepBeta.svelte` and `StepOptional.svelte` shared the toggle card's markup and styles. Now `onboarding/OnboardingToggleCard.svelte`. - `GlobalShortcutRow.svelte` and `KeyboardShortcutsSection.svelte` shared the editable shortcut pill's styles. Now `settings/sections/ShortcutPill.svelte` (class names preserved for the existing tests). - `OperationFailedToastContent.svelte` and `OperationFailuresToastContent.svelte` shared the toast body. Now `status-corner/OperationFailureToastBody.svelte`. - `AutoSendToastContent.svelte` and `ErrorReportToastContent.svelte` shared the sent-report toast body. Now `error-reporter/SentReportToastBody.svelte`. `GitSection` and `SearchSection` keep their 18 shared lines: that is the settings-section registry boilerplate every section carries, and hiding it would obscure the contract. Behavior preserved as-is, including the `analytics.email` listener that both surfaces subscribe at init without unsubscribing. Colocated `CLAUDE.md` / `DETAILS.md` module maps updated. Verified: svelte-check, stylelint, css-unused green; eslint green on the touched files; targeted vitest run of the touched dirs (41 files, 487 tests) green.
…nd boolean-setting binding between the dialogs that copied them Three frontend copy-paste pairs from the `jscpd-frontend` inventory, each replaced by one shared module: - `NewFolderDialog.svelte` and `NewFileDialog.svelte` each carried the name validation (sync validators, debounced clash lookup, `directory-diff` re-validation) plus the field's markup and styles. Now `file-operations/new-entry-name-check.svelte.ts` (`NewEntryNameCheck`) and `file-operations/NewEntryNameField.svelte`, which owns the focus/select/listen lifecycle. Both dialogs compose them; an a11y block for the new component joins `file-operations.a11y.test.ts`. - `new-folder-operations.ts` and `new-file-operations.ts` each re-derived the cursor entry. Now `file-operations/cursor-entry.ts` (`getCursorEntry()`); both pre-fill helpers are one-line wrappers. - `SettingCheckbox.svelte` and `SettingSwitch.svelte` each bound a boolean setting the same way. Now `settings/components/boolean-setting.svelte.ts` (`useBooleanSetting(id)`); both controls are four lines of script. `DeleteDialog` and `ScanPhaseBody` keep their 23 shared CSS lines: the honest fix is the delete dialog rendering `ScanPhaseBody`, which changes its DOM, needs an end-aligned variant, and orphans three catalog keys. A follow-up, not a sweep item. One ordering nuance: mkdir's field now mounts focus/validate/listen from the child while the parent kicks off AI suggestions, so only the async IPC order shifted; the existing mkdir timeout, streaming, and a11y tests all pass. `file-operations/CLAUDE.md` compressed to stay under the word budget after the module-map addition. Verified: svelte-check, eslint, stylelint, css-unused, a11y-coverage, claude-md-length, docs-dead-links, docs-link-text, docs-reachable green; targeted vitest run of mkdir, mkfile, the a11y suite, and settings/components (13 files, 102 tests) green.
… frontend into one local helper each
Five intra-file clones from the `jscpd-frontend` inventory, each now a single definition:
- `ImageSearchResults.svelte`: the thumbnail + filename tile face both grids rendered is one `{#snippet tileFace(tile)}`.
- `drag-image-renderer.ts`: the rounded-rect path tracing shared by the fill and stroke drawers is `traceRoundedRect()`.
- `mtp-store.svelte.ts`: the placeholder-device-in-error write both the exclusive-access and permission listeners performed is `markDeviceError()`.
- `viewer-copy.svelte.ts`: `runRangeIpc()` owns session/selection resolution, the busy and in-flight plumbing, and the `ViewerError` to `CopyResult` mapping; `performRead` and `saveAs` are one-line wrappers. It takes one object payload (anchor and focus share a type), per `no-confusable-callback-params`.
- `filter-popover-helpers.ts`: the inner `format()` in `resolveDatePreset` duplicated the module-level `isoLocalDate()` byte for byte; the seven call sites use the latter.
No exported surface changed. Verified: svelte-check, eslint, stylelint, css-unused green; targeted vitest run of search, drag, mtp, viewer-copy, and filter-chips (44 files, 536 tests) green.
…tor, and shared test fixtures replace six copies Four Rust copy-paste pairs from the `jscpd-rust` inventory, all in `crates/cmdr-index`: - `store/entries.rs` mapped the full `EntryRow` from a `rusqlite::Row` in five queries and streamed borrowed directory names in two loops. Now `entry_row_from()` (passed straight to `query_map` / `query_row`) and `for_each_directory_row()`. SQL strings, column order, the `inode_from_sql` bit-cast, the zero-alloc `get_ref` borrow, and `optional()` handling are unchanged. - `aggregator/readers.rs` collected children stats identically in the bulk and scoped readers. Now `collect_children_stats()`; each reader keeps its own SQL. - `writer/deferred_repair.rs` and `writer/repair.rs` each defined `dir_entry` / `file_entry` / `symlink_entry` test fixtures, and `writer/delta.rs` spelled out nine identical directory `EntryRow` literals. Now one `#[cfg(test)]` module, `writer/entry_fixtures.rs`. Verified: `cargo check -p cmdr-index --all-targets` and clippy clean, `cargo fmt --check` clean, nextest over `indexing::(store|writer|aggregator)` 255 passed.
…ing their scaffolding
Seven frontend copy-paste pairs from the `jscpd-frontend` inventory, in dev-only and debug-only routes:
- `routes/dev/components/+page.svelte` and `routes/dev/graphics/+page.svelte` carried the same header, "Open in browser" link, scroll-parent lookup, anchor scrolling, IntersectionObserver sync, and catalog CSS. Now `routes/dev/CatalogPage.svelte`, parameterized by prefix, sub-ids, route, title, and a description snippet; each page keeps only its guard, props, ids, and section list.
- `IconsSection` and `StatusBadgesSection` shared the tooltip'd glyph grid. Now the generic `routes/dev/graphics/GlyphGrid.svelte` with `intro` and `glyph` snippets.
- `SizeBadges` and `StatusBadgeSection` shared the labeled row. Now `routes/dev/components/LabeledRow.svelte`.
- `FilterPopoverSection`, `PopoverSection`, and `MenuSection` shared the demo anchor button. Now `routes/dev/components/DemoAnchor.svelte`.
- `DebugErrorPreviewPanel.svelte` rendered three identical state groups and `DebugHistoryPanel.svelte` two identical pane columns; each is one `{#each}` over a small array now.
Rendering unchanged (the one host color `GlyphGrid` carries over only affects a fixed-color `<img>` child). Verified: svelte-check, eslint, stylelint, css-unused green; the sections a11y suite green.
…move commands share their once-duplicated steps Six Rust copy-paste pairs from the `jscpd-rust` inventory in the write-operations area: - `rename/bulk.rs`: the local and remote engines settled no-op rows and the finished run identically, and probed unclaimed destinations identically. Now `complete_noop_rows()`, `BulkRenameRun::settled()`, and `rows_with_unclaimed_destination()`; each engine keeps only its own existence probe. The sync and async cycle stagers stay as deliberate twins (ten lines; unifying them would need an async abstraction over the local path). - `rename/bulk.rs` and `agent/tools/propose/rename/preflight.rs` each defined `same_local_file`. Now one `pub(crate)` fn in `write_operations/rename.rs` (cfg unix / not-unix), re-exported from `write_operations`; the two cfg'd `check_sibling_conflict` bodies in `rename.rs` collapse into one on top of it. - `operation_intent.rs` tests parked a worker on the gate twice. Now `park_worker_on_gate()`, carrying the one `allowed-test-sleep` window. - `cancellable.rs`: `run_cancellable` and `run_cancellable_scoped` shared the worker polling loop. Now `await_worker_result()`. - `commands/file_system/write_ops.rs`: copy and move built the same local transfer request. Now `local_transfer_request()` (tilde expansion, archive refusal, default config). Verified: `cargo check --workspace --exclude cmdr-fsevent-stream --features cmdr/virtual-mtp --all-targets` clean, clippy clean on touched files, `cargo fmt`, nextest over rename / operation_intent / cancellable / propose::rename / write_ops 67 passed.
…y listing, plus a shared tar member walk Four Rust copy-paste pairs from the `jscpd-rust` inventory, resolved by moving the shared code down into the crate both sides already depend on: - The name-collision half of `Volume::scan_for_conflicts` was hand-kept in three backends (the `cmdr-fs` in-memory volume, `cmdr-smb`, and the app's MTP scan). Now `cmdr_fs::volume::conflicts_in_listing()`, with its own tests. Three copies drift on exactly the field nobody re-tests, and `dest_is_directory` is what the transfer dialog classifies collisions by. - `crates/cmdr-archive/src/read/tar.rs`: the single-member and subtree streamers each opened the codec, iterated members, and mapped decoder and header failures. Now `for_each_member()`, which hands every member to a visitor and returns `ControlFlow`, so both readers report a failure the same way. - `apps/desktop/src-tauri/src/icons/per_path.rs` re-exports `is_package_dir` and `package_icon_id` from `cmdr_fs::icons::packages` but carried a verbatim copy of that module's six tests. Dropped; the crate keeps them where the code lives. Verified: `cargo check --workspace --exclude cmdr-fsevent-stream --features cmdr/virtual-mtp --all-targets` clean, nextest over the icons, tar, in-memory, scan, and conflict suites 206 passed.
…ght suites named after what they cover `transfer/volume/copy_tests.rs` was the app crate's longest file and the one a reader had to scroll to find anything in. It is now `copy_tests/` with a `mod.rs` holding the shared imports and the `make_state` / `make_volumes` fixtures, plus one child per behavior under test: `cancellation`, `conflicts`, `destination`, `error_mapping`, `progress`, `remove_tree`, `scan`, and `scan_hints` (112 to 588 lines each, all under the 800-line warn threshold). A pure move: all 51 tests kept their names and bodies byte for byte, and 51 run and pass after the split. Only the module path changed, so the sibling `#[path]` suites and the two doc comments naming the old file were updated to match. Verified: `cargo check --workspace --exclude cmdr-fsevent-stream --features cmdr/virtual-mtp --all-targets` clean, `cargo nextest run -E 'test(/volume::copy::tests::/)'` 51 passed (51 before).
…o what the sweep left behind `pnpm check jscpd-rust jscpd-frontend file-length` shrink-wrapping itself after the extractions, plus the doc line re-wraps its auto-fixer made in the files the sweep touched. Frontend duplication drops from 22 file pairs and 560 lines to 2 pairs and 39, Rust from 46 pairs and 1,019 lines to 31 and 640, and `copy_tests.rs` leaves the file-length list entirely. Every change is a removal or a ratchet down (`rename/bulk.rs` 44 to 10); no entry gained a number and none was added.
…ted with tier-3 a11y tests `a11y-coverage` is an error, and the sweep's new shared components arrived without it: `SentReportToastBody`, `OnboardingToggleCard`, `ShortcutPill`, and `OperationFailureToastBody`. Each block joins its directory's existing a11y file rather than opening a new one, since `svelte-tests` charges per test file (`docs/testing.md` § "What a test actually costs"). The states covered are the ones that differ: title and title-less for the sent-report body, switch on and off for the toggle card, editable / read-only / empty / recording-with-conflict for the pill, and title with and without a reason for the failure body. One skipped test comes with the pill: axe flags `nested-interactive` on the `<span role="button">` remove control inside the pill's own button. That is the same finding the `KeyboardShortcutsSection` test above it has documented and skipped all along; the markup only moved into `ShortcutPill.svelte`. The TODO names both fix options and says to unskip the two together. Verified: `pnpm check a11y-coverage` OK, `desktop-svelte-eslint` and `svelte-check` clean, targeted vitest run over the four directories 577 passed.
…ectory, as it already skips the single file Splitting `copy_tests.rs` into `copy_tests/` moved 2,300 lines of test code from outside this lane to inside it: the ignore list caught `*_tests.rs` but nothing named `copy_tests/progress.rs`, so the children's shared setup surfaced as 42 lines of new production duplication. The lane's stated job is duplication in production Rust, and a suite that outgrows one file and becomes a directory of the same name is the same test code it was the day before. So `**/*_tests/**` joins the list, covering the convention one level up. Without it, every future long-test-file split pays the same toll, and the only ways out would be an allowlist entry for test code the lane was never meant to see, or leaving the file long. This also drops one pre-existing pair from the allowlist, `cold_drive_tests/branches.rs ↔ switches.rs`: that directory follows the same convention and was in the lane by the same naming accident. David, that is the one bit of coverage this trades away, and it is 6 lines of duplication between two test files. Verified: `pnpm check jscpd-rust` OK (31 pairs, 636 duplicated lines), `gofmt`, `go-vet`, and `go-tests` green.
…tly, taking both to 100% Extraction moves logic out of the components whose tests used to reach it, and `svelte-tests` caught the shortfall: `new-entry-name-check.svelte.ts` at 55% and `ShortcutPill.svelte` at 62.5%, both under the 70% per-file threshold. Both are now at 100%, tested through their own surface rather than through a dialog. `NewEntryNameCheck` gets 12 tests: empty and whitespace names, each sync validator short-circuiting before the clash lookup, folder versus file clash wording including the entry-gone case, stale-message clearing, a failing lookup leaving the field clear, `isChecking` held for the duration, `schedule()` coalescing to the latest name, `listen()` re-checking only its own listing, and `dispose()` with and without a pending timer. `ShortcutPill`'s remove control gets 3: no × on a slot that cannot be removed, a click removing without bubbling into the pill's own `onclick`, and Enter and Space removing where other keys do not. Both blocks join existing files, which already own the mocks and fake timers they need; `svelte-tests` charges per test file (`docs/testing.md` § "What a test actually costs"). Verified: `pnpm check svelte-tests` green, 10,148 tests passed, no file under threshold; eslint and svelte-check clean.
…s tripped
`eslint-typecheck-svelte` and `svelte-check` see more than the fast eslint lane, and three of the sweep's extractions landed errors only they catch:
- `ImageSearchResults.svelte`: rendering the extracted `tileFace` snippet trips `no-confusing-void-expression`, which every `{@render}` of a locally declared snippet does. Uses the same targeted disable comment `FunctionKeyBar.svelte` already carries for it.
- `IconsSection.svelte` and `StatusBadgesSection.svelte`: the `glyph` snippet parameter typed as `any` under the new generic `GlyphGrid`, since a snippet parameter is not inferred from the `Snippet<[T]>` prop. Both now annotate it, which needed a named `IconEntry` for the icons list.
- `settled-operations.test.ts`: an autofix for the same void-expression rule in a test added a moment ago.
Verified: `eslint-typecheck-svelte` 297 files passed, `svelte-check` no type errors, the touched test file green.
…ctory as the test code it is `isRustTestFile` already says the principle in its own doc comment: a module that outgrows one file becomes a directory, and every file in one is as much a dedicated test file as the `tests.rs` it was split out of. It only recognized a directory named exactly `tests`, so splitting `copy_tests.rs` into `copy_tests/` moved 2,300 lines of unchanged test code into the jurisdiction of every scanner that trusts this predicate. The result was 25 findings against code nobody wrote today: 23 `lock-poison` sites (`events.complete.lock().unwrap()` in assertions), 2 `probe-unwrap-justified` swallowed probes, and an `error-string-match` opt-out comment that read as unused once its file was scanned for the first time. So a `<subject>_tests/` (or `_test/`) directory now counts alongside `tests/`, in the one predicate all of these share, and `isRustTestPath` routes its relative path through it too. The alternative was 25 opt-out comments apologizing for test code being test code. Verified: `error-string-match`, `probe-unwrap-justified`, `lock-poison`, `test-sleep`, `no-hand-rolled-fixture`, and `mtp-dropping-timeout` all green; `gofmt`, `go-vet`, `staticcheck`, and `go-tests` green.
`scan_conflicts.rs` arrived with the duplication sweep and is production code three backends call, so it belongs in the map beside its siblings.
…e way the house set does, so lint stops removing the binding
`NewEntryNameField.svelte` typed `value` as required while defaulting it with `$bindable()`. `@typescript-eslint/no-useless-default-assignment` reads a default on a required property as useless, and its autofix deletes it, which silently turns off two-way binding: `pnpm check` then rewrote the file and `svelte-check` failed on both dialogs with "Cannot use 'bind:' with this property". CI never sees the rewrite (`--ci` disables auto-fixing), so this only ever breaks a local run, and it breaks it in a way that reads like a bug in the dialogs.
`value?: string` with `$bindable('')` is what `TextInput`, `TextArea`, and `Chip` already do, and it survives the lane. The prop comment says why, since the required-looking version is the tempting one.
…ht themed files, so a test is found by its subject instead of by scrolling 2,265 lines `entries/tests.rs` was the longest file left on the `file-length` list. It becomes `entries/tests/`, following the `copy_tests/` model: `mod.rs` carries the shared imports, the `insert_dir_with_stats` / `insert_file` / `seed_row` fixtures, and the child declarations, and every child opens with `use super::*;`. The seams follow the behavior each test drives: `insert_upsert.rs` (212), `delete.rs` (141), `delta_propagation.rs` (355), `hardlink_dedup.rs` (420), `symlinks.rs` (257), `move_entry.rs` (587), `bulk_reconcile.rs` (154), `id_counter.rs` (96), plus `mod.rs` (80). Delete-by-id and delete-subtree sit with the other delete paths while their `*_auto_propagates_delta*` twins sit with the upsert propagation tests, because the propagation contract is the thing under test there, not the delete. The bulk-reconcile suppression guard and the id-counter healing tests, both about the writer's own bookkeeping rather than a single mutation, get one small file each rather than a shared grab bag. Pure move: all 33 tests keep their names and their bodies byte-for-byte, verified by comparing every extracted body against the original; the only line-level differences across the whole split are the six section-banner comments becoming per-file `//!` docs, the eight `mod` declarations, and the eight `use super::*;` lines. The three helpers moved once each into `mod.rs` and are shared, not copied. No production code changed. Proof: the sorted leaf test-name list from `cargo nextest list -p cmdr-index -- writer::entries` is identical before and after (33 names), and `cargo nextest run` reports 33 passed. `file-length` shrink-wrapped the stale 2,138-line entry away and added none; `jscpd-rust` is unchanged; `clippy`, `rustfmt`, `lock-poison`, `error-string-match`, `test-sleep`, and `probe-unwrap-justified` are green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013JPKrH6kufjw9Q4eKmxqsy
Owner
Author
|
Landed on Two resolutions worth recording, since the result differs from this PR as written:
The allowlist shrink this PR was after all landed: Branch deleted. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A parallel sweep over the
jscpd-rust/jscpd-frontendinventories and the top of thefile-lengthlist. Every pair was handled by extracting the shared code, or deliberately skipped with a reason. No allowlist number was raised and no entry was added.What it does to the numbers
jscpd-frontendjscpd-rustfile-lengthNet across 117 files: +4,887 / -4,728, most of the insertions being the 2,266-line test file moving into eight.
The extractions
Frontend. The two new-entry dialogs shared their whole name-validation flow, now
new-entry-name-check.svelte.tsandNewEntryNameField.svelte. Onboarding and Settings shared the beta email signup, now one store. Four more shared components came out of duplicated markup and CSS:OnboardingToggleCard,ShortcutPill, and the two toast bodies. The dev gallery pages shared their entire scaffolding, nowCatalogPage,GlyphGrid,LabeledRow, andDemoAnchor. Five within-file clones collapsed into local helpers.Rust.
cmdr-indexgained one entry-row mapper, one children-stats collector, and a shared test-fixture module in place of six copies. The rename engines, the cancellable runners, and the copy/move commands each stopped repeating a step. Three backends that answerscan_for_conflictsby listing now sharecmdr_fs::volume::conflicts_in_listinginstead of keeping three copies of the field mapping, and the tar reader walks members through one function.The split.
transfer/volume/copy_tests.rs(2,266 lines) is nowcopy_tests/with one child per behavior. All 51 tests kept their names and bodies; 51 run and pass.Skipped on purpose
DeleteDialog↔ScanPhaseBody(23 CSS lines): the honest fix changes the delete dialog's DOM and orphans three catalog keys.GitSection↔SearchSection(18 lines): section boilerplate that a helper would obscure. The sync and async cycle stagers inrename/bulk.rs(10 lines): unifying them needs an async abstraction over the local path.Two things worth your attention
The split exposed a naming assumption in five checks.
copy_tests.rswas excluded fromjscpd-rustand from the Rust source scanners by name;copy_tests/progress.rsis not, so splitting it enrolled 2,300 lines of unchanged test code in lanes meant for production code (42 duplicated lines, 23lock-poisonsites, 2 swallowed probes, 1 orphaned opt-out comment). Rather than 25 opt-out comments apologizing for test code being test code, a<subject>_tests/directory now counts alongsidetests/inisRustTestFileand in the jscpd ignore list.isRustTestFile's own doc comment already stated the principle fortests/. This trades away one pre-existing allowlist pair,cold_drive_tests/branches.rs ↔ switches.rs(6 lines between two test files), which was in the lane by the same naming accident.A lint autofix silently breaks bindable props.
NewEntryNameField.sveltetypedvalueas required while defaulting it with$bindable().@typescript-eslint/no-useless-default-assignmentreads that as a useless default and its autofix deletes it, turning off two-way binding: a localpnpm checkrewrote the file andsvelte-checkthen failed on both dialogs. CI never sees it (--cidisables auto-fixing), so it only ever breaks a local run, in a way that reads like a bug in the dialogs. Fixed here by matching whatTextInput/TextArea/Chipalready do (value?: stringwith$bindable('')), but the trap is general.Verification
pnpm checkminus the lanes this Linux container cannot run: 104 OK, 3 warn, 1 skipped, 0 failed (19m38s). That includes clippy, rustfmt,svelte-check, both type-aware eslint lanes, all six Rust source scanners, the i18n and docs lanes, the Go suite, and 10,148 frontend tests.The three warns:
claude-md-reminder(19 directories touched without a doc edit, mostly one-line changes),invariant-density(mothballed), and onesvelte-testsnote thatSettingCheckbox.svelte's coverage-allowlist entry now looks unneeded because the extraction took it to 100%. That entry is yours to remove; I left it alone.Coverage and a11y gaps the extractions opened were closed rather than allowlisted:
new-entry-name-check.svelte.tsandShortcutPill.sveltewent from 55% and 62.5% to 100%, and the four new components got tier-3 a11y tests.Not run here, and why:
desktop-e2e-linux,rust-integration-tests(no Docker daemon),bindings-fresh(compiles the macOS-onlycmdr-fsevent-stream),changelog-links(shallow clone, so 2,390 historical SHAs do not resolve),cargo-audit/cargo-udeps/govulncheck, and the Playwright and website E2E suites.rust-testsneeds your eye: 6,684 tests run, and the same 17 fail on an untouchedmainin this container as on this branch (virtual-MTP device discovery, network-hang cancellation, and two timing-sensitive tests). The exact set shifts run to run because the MTP tests share a device lock, so please treat CI's verdict as the real one.Not attempted
The remaining 31 Rust pairs are led by ~200 lines of macOS-only code (
menu/linux ↔ macos,clipboard,quick_look,native_drag,volumes ↔ volumes_linux) that does not compile on Linux, so an agent here would be writing unverifiable changes. That is a Mac session's work. The other 88 over-length files are mostly per-file architectural judgment rather than sweep material, and splitting the big Svelte components wants a visual check this container cannot give.🤖 Generated with Claude Code
https://claude.ai/code/session_013JPKrH6kufjw9Q4eKmxqsy
Generated by Claude Code