ed91423666c6733191979c6038b94eae6facd60c
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ed91423666 |
Hold back the Disk panel and OS drag-out from the ship branch
This is a scope reduction, not an abandonment. Both subsystems are
preserved in full on `hold/disk-and-dragout` and are intended to come
back once they have been hardened separately. Nothing here is a
judgement that the features are unwanted — three successive
audit-and-fix cycles each closed a critical defect in these two areas
and each opened a new one, so the rest of the round ships now and these
two get their own cycle rather than holding it up.
Removed: the Disk settings panel and its whole reclaim / destroy /
compaction surface — `DiskSettings`, `DiskProjectTable`, `useDiskUsage`,
`docker/disk.rs`, `disk_tests.rs`, the disk commands in
`docker_commands.rs`, and their `generate_handler!` entries. Dropping
the IPC entries is the point: a UI-only removal would have left five
commands callable by a compromised webview, one of them a verified
arbitrary-DELETE primitive. `sweep_orphaned_snapshots`'s *command* goes
with them (the panel was its only caller); the sweep itself stays.
Removed: OS drag-out from the Files tab — `stage_container_file_for_drag`
and its host staging lifecycle, the pointer gesture and `dragPreview`,
`stageForDrag` / `isStagedHostPath`, the `tauri-plugin-drag` and
`@crabnebula/tauri-plugin-drag` dependencies, and the
`drag:allow-start-drag` capability grant, which could not be scoped.
The capability test's expected list is updated; its `*:default` and
`store:*` assertions are untouched.
Kept, deliberately: drag-and-drop *into* the app (Files pane and
terminal) and "Save to host…", which is now the only route out of a
container. The prevention work is untouched — the pre-commit scrub and
`SNAPSHOT_SCRUB_PATHS`, capped container logs, the `triple-c.base` /
`triple-c.managed` labels, `sweep_orphaned_snapshots` and the startup
housekeeping, the migration pin/probe reapers, scheduler log pruning,
`formatBytes.ts`, and `project_lock.rs` in full with every acquisition
site outside `disk.rs`.
Entanglements, resolved rather than deleted blind:
* `container.rs`'s `a_compaction_runs_this_module_s_scrub_script_byte_for_byte`
pinned the compaction Dockerfile against `snapshot_scrub_script()`.
Dropped — it existed only for compaction. `snapshot_scrub_script` and
its containment tests are untouched.
* `lib.rs`'s startup reap of `:compacting` tags and `triple-c-compact-*`
containers is dropped: nothing on this branch creates them.
* `project_lock`'s `Compaction` / `CacheClear` variants and
`any_held_excluding`, `migration_commands::is_migrating`, and
`formatBytes{Delta,Ceiling}` lose their last production caller but are
kept and still tested, annotated with why.
* `projects_store::corrupt_since` and `migration_store::peek_ownerless_since`
were read only by the disk survey and are removed. The corrupt-load
marker and `.bak` are still written.
Verified: `npm run test` 611 passing, `npx tsc --noEmit` clean,
`npm run build` green; `cargo test` 419 passed / 2 ignored,
`cargo build` 0 warnings. Every test removed belongs to a removed
feature — no kept-behaviour test was weakened or deleted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
|
||
|
|
d6f065a2b6 |
Fix HIGH and MEDIUM frontend defects
Files pane
- F16: a drag-out released back inside the app no longer re-imports its own
staged copy over the container original. An in-flight flag (cleared from the
drag plugin's `onEvent` channel, with a watchdog) suppresses the drop and the
"Drop files into …" hint, and an exact staged-path filter is the second line
of defence — the `path|size|modified` cache could otherwise write a
minutes-old snapshot over a file an agent had since rewritten.
- F17: a slow upload/rename no longer yanks the user back to the directory the
operation started in. Every operation captures its target path and re-lists
only if the user is still there; failures go to the toast host either way.
- The grid keeps keyboard focus. Roving tabindex (one tab stop, not one per
row) plus focus restore after navigation, rename commit/cancel and Escape.
- Transient failures now surface in `ToastHost` (z-[60], persistent aria-live)
instead of a `role="alert"` 300 rows down a scroller or behind a modal
overlay. The inline error is kept only for the listing failure.
- `navigate` is sequenced by generation; "Save to host…" sets `busy`.
- Grid a11y: column headers, a text affordance for folder vs file, a live
region that is mounted empty and announces completion, Label-in-Name fixed.
- FileViewerModal: the blob URL is released only once its replacement exists;
the preview is a focusable, named, scrollable region.
Native drop routing
- New `lib/dropTarget.ts`: the hit test now refuses a drop while any
`[aria-modal="true"]` dialog or `[data-blocks-drop]` overlay is up, and
checks z-order where the environment can answer it. Shared by FilesTab and
TerminalView; App's shutdown overlay opts in.
Disk
- A partially failed reclaim says so in words ("… — 2 of 5 failed"), not by hue
alone.
- The scan/reclaim race is closed: every mutation retires an in-flight scan, so
a scan can no longer repaint a pre-reclaim report plus a clickable plan of
objects that are gone. Scan is disabled while working; the status is a live
region; a failed destructive action keeps its dialog open and reports there.
- The "unknown" layer count gets a screen-reader fallback; `--text-disabled`
no longer carries live information.
Terminal / OAuth
- After the toast is dismissed, a truncated heuristic guess can no longer fill
the slot that an exact OSC 8 or relay URL occupied — the detector remembers
every exact URL and drops any candidate that is a strict prefix of one.
- The prompt is reachable by keyboard: Ctrl+Shift+O jumps to the default
action, Escape dismisses, focus returns to the terminal, and auto-dismiss
holds off while focus is inside. It deliberately does not steal focus.
- UrlToast renders through `ui/Button` and `--shadow-overlay`.
Elsewhere
- AuthBridgeRow: a pushed `auth-bridge-changed` status always outranks an older
awaited toggle result.
- The last two ad-hoc byte formatters route through `lib/formatBytes`.
Contract for the backend agent: `upload_file_to_container` refusing to
overwrite must satisfy `isFileExistsError` in `src/lib/uploadErrors.ts` (marker
`FILE_EXISTS`) and accept an `overwrite` argument; the frontend turns that into
an `ui/Modal` Replace/Skip prompt rather than a raw error string.
Tests: 536 -> 627 passing. `npm run build` and `npx tsc --noEmit` green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
|
||
|
|
611f67cca7 |
Fix what review found in the Disk section
Safety: - `destroy`'s rollback-pin arm took a tag over IPC and interpolated it straight into an image reference it then removed. `tag: "latest"` named the project's live snapshot, deleted under a dialog saying "rollback pin". It is the one destructive variant carrying a free-form string, so it now goes through `parse_rollback_tag`. - The compaction's scratch container was named `triple-c-scrub-*`, which is what the scrub reclaim bucket hunts and force-removes. A reclaim from a second window would have destroyed the container a running compaction was about to commit. It gets `triple-c-compact-*`, swept at the start of the next compaction rather than from a bucket anything else can fire. - Deleting a home or config volume only refused a *running* container, but a stopped one still pins its volumes — the resting state of every project ever started — so the user typed the project name and met a raw 409. The container is now removed first and `loses` says so. Correctness: - The compaction Dockerfile emitted no `LABEL`, so the flattened intermediate could never match the sweep's `dangling` + `triple-c.managed` filter that three cleanup paths rely on. Verified on Docker 29.7.2 that the label lands on the final stage, the build still yields one layer, and untagging the staging tag after the commit leaves the committed snapshot intact and startable. - `snapshot_commit_layers` silently meant something else when `triple-c.base-image-id` was absent — the normal case for a pre-label project — counting the base's own layers and letting a never-recreated project qualify for compaction. `base_lineage_known` now carries that, the column says "unknown", and the plan does not offer the rewrite. - `destroy` returned a `ReclaimResult` wearing a `ReclaimTarget` that named work it had not done (a home-volume deletion came back as `OrphanVolume`). Split into `target` / `destroyed`, exactly one set. - `formatBytes` ran `toFixed` after the divide loop, so 999,999 rendered as "1000.0 KB" — in the app's only byte formatter, in a panel full of near-boundary sizes. - `is_base_image_reference` split on the first colon, so a registry port ate the repo name. UI: - `snapshot_above_base_bytes: null` — deliberately unmeasurable — rendered as "0 B", the one guessed number in the table. - Layer count was flagged by colour alone; it now says "stacked". - The tick list survived a reclaim, so the same call could be re-fired at objects that no longer existed. The plan is dropped after any action and the panel says the totals predate it. - `setReport` landed before the plan call was awaited, so a plan failure rendered fresh totals above the previous scan's rows. - Both confirmation modals unmounted before awaiting, making the entire busy path dead code during multi-second work. - `buildx du` failures silently showed `docker system df`'s under-reported build-cache figure with no explanation. - Tooltip text reached no assistive tech, so two headers announced as "Help"; hardcoded input id; error-toned glyph in warning-toned panels; `sweepOrphanedSnapshots` and `clearOutcome` had no callers. - Four docstrings claimed things the code did not do, and two tests were named for behaviour they did not assert. Tests: 513 frontend (was 502), 370 Rust (was 365). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc |
||
|
|
77ef2291d7 |
Add a Disk section: see where the bytes went, and get them back
Every recreation runs `docker commit`, which stacks a layer and never rewrites one, and 24 conditions in `container_needs_recreation` trigger a recreation. Prevention landed earlier on this branch; this is the half a user can act on. The per-project table leads with the two numbers that explain the mechanism rather than just the total: how many commit layers a snapshot has stacked above its base, and what the container's writable layer will add at the next commit. Backend (`docker/disk.rs`, commands in `docker_commands.rs`): - `get_docker_disk_usage` — one `df()` joined against the project store, behind an explicit Scan button because it walks the whole daemon. - `list_reclaimable` / `reclaim` — classified buckets with measured bytes, planned off the existing report so re-planning costs no second scan. - `destroy_project_disk_object` — one object, typed confirmation. - `sweep_orphaned_snapshots` — exposed, so its report is finally visible. Safety is structural: `reclaim` takes `ReclaimTarget`, which has no variant that can name a live project's data. Destructive work is a separate type reached only through `destroy`. No unfiltered prune is called anywhere, and nothing outside a `triple-c*` name or `triple-c.*` label is touched. Orphan detection subtracts ids from the project store and consults nothing else. From the daemon's side an idle live project and a deleted one are indistinguishable — volumes present, no container, no image — so inferring from container or image absence would offer a live project's credentials and transcripts for deletion. A store that loaded empty from an existing `projects.json` is treated as a failed load, not as "no projects", because `ProjectsStore::new()` recovers from a corrupt file by starting empty. Three things verified against a live Docker 29.7.2 rather than assumed: - Compaction is a two-stage build (`FROM scratch` + `COPY --from`), which keeps every byte inside the daemon; bollard's import buffers a whole image into memory. uid/gid and setuid survive; a 192.6 MB/4-layer synthetic came out 45.7 MB/1 layer. Image config does not survive, so it is replayed via create+commit, which round-trips a multi-line env var that a Dockerfile `ENV` could not. - Flattening breaks base-layer sharing, so the result carries its own copy of the base. Eight of ten real projects had a 0.10–1.32 GB delta over a 4.72 GB shared base — compacting those costs ~4 GB. The bound now subtracts that penalty, such projects are not offered at all, and the run compares unique bytes and abandons a rewrite that would grow. - `docker builder prune` reports `Total:`, not `Total reclaimed space:`, so the first parser scored every prune as freeing nothing. The Windows/WSL2 note is mandatory and its copy lives in Rust beside the tests that pin it: pruning frees space inside `ext4.vhdx`, which never shrinks on its own, so C: does not change until the disk is compacted. Also adds `lib/formatBytes.ts` — the app had four disagreeing copies, and `projects/home/format.ts` and `migrationCopy.ts` now delegate to it with byte-identical output. Base 1000 by default, matching what Docker prints. Tests: 502 frontend (was 453), 365 Rust (was 322). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc |