Repository navigation
+ a Defragment ribbon tab gives the block map the whole window - #439
Merged
Merged
Conversation
Hawkynt
force-pushed
the
feat/defragment-ribbon-tab
branch
7 times, most recently
from
October 7, 2026 15:54
8d2deaf to
c43fbb5
Compare
Maintenance lived in a separate 2200-line dialog that squeezed the block map between an image picker, a strategy group, an output box and two rows of buttons. It is now a contextual "Disk Tools" tab, Defragment, shown while the open or selected image supports any maintenance operation. Selecting it hides the browser (not torn down, so leaving restores it exactly) and gives the client area to the block map, files panel, colour key and a progress line. Ribbon groups: - Operation: Defragment, Optimize, Shrink, Compact, Clear, Purge, Scramble as a radio group; Start; Stop where cancellable. - Defrag Mode: Consolidate (+ Pack at End), Defrag (FillHolesLazy), Re-order (AscendingOrder), Sort Entries, Carve Hole with size/offset fields shown only while picked. - Options: interleave (1-256, refused outside rather than clamped), metadata zone, layout profile + Edit Profiles; seed, chunk placement and minimal geometry appear with their operation. - View: Blocks / Circle / 3D Stack, Files, Legend, Analyze. Every item asks one adapter, MaintenanceCapabilities, which is the only place that maps registry interfaces to answers; modes and layout options are probed against the descriptor's own refusal guard with a stream that throws on first touch, and only for descriptors that implement the options overload themselves (the interface default would accept and ignore them). Sort Entries is answered "no" for every format: no descriptor offers it through the registry yet. Optimize is offered only where an optimizer changes something (ZIP/gzip/zlib/MacBinary/compound tar re-encode, 7z solid regrouping, CVF clusters, file-internal chunks), not where ArchiveOperations.Optimize would copy the file through. The window's non-UI logic moved into MaintenancePresenter (choices -> DefragOptions, running each verb off the UI thread, progress) and BlockMapSnapshot (layout reading); a refusal (NotSupportedException) is reported and leaves the image untouched, a change re-lists the archive in place, and a nested archive is maintained as a temporary copy written back after each change (MaintenanceSession). DefragmentWindow is gone; Tools > Maintenance (Ctrl+Shift+D) and the list's Maintenance context entries open the tab with their operation picked, and the Archive tab keeps only Reconfigure. While browsing a folder, the archive left behind is no longer a maintenance target. The headless test backend gains a real combo-box peer: the interface takes a ReadOnlySpan, which a dispatch proxy cannot box. Verified: FAT, ZIP and partitioned-disk capabilities; interleave and hole parsing boundaries; strategy -> engine mode mapping; tab visibility, client-area swap and restore, per-capability enabling, carve fields, view toggles; Start on a scrambled FAT keeps size and every file byte-identical and reports no fragmentation afterwards; an oversized carve hole is refused with the image byte-identical.
On GTK the field shows the end of a long entry, so "Don't move metadata" read as "metadata". The choices are one or two words now; the tooltip explains each.
GTK draws a combo box at its natural width, wider than the 88 px the field gave it, so it spilled under its caption and showed "nchanged". The input part is 116 px now.
Symptom: on GTK the Metadata and Profile fields showed only the top of "Unchanged" and "(none)". Root cause: GTK draws a native combo box at its natural height whatever bounds it is given, and that is taller than a ribbon row. Fix: the fields are an owner-drawn ChoiceBox (a DropDownButton whose menu holds the choices), which keeps exactly the bounds the ribbon lays out. A test pins every hosted input inside its row and rules out native combo boxes there.
Symptom: "SUCCEEDED (31 ms) — Defragmentation complete — 4…" ran off the right edge of the log under the files panel. Root cause: a multiline TextBox neither wraps nor scrolls sideways on every backend. Fix: the log is drawn by LogView, which wraps each line to its width (at spaces, inside a word only when the word alone is too wide), keeps the newest line in view and scrolls back with the mouse wheel.
Symptom: on a FAT16 volume written by Linux the Frag column read "—" for every file while the status line said one file was fragmented. Root cause: the counts were keyed by the layout walker's names and looked up by the lister's. FatExtentMap reports a short entry's stored 8.3 name (BIG.BIN) and ignores the lowercase flags that the lister applies (big.bin), so no name matched. The runs were also counted after the extents had been sorted by offset, which merges a file stored backwards into one run. Fix: an exact match is tried first, then a match ignoring case and a leading slash, used only when it is unambiguous. Runs are counted in the order the walker yields, which is the file's own order. Tests cover upper- and lowercase FAT fixtures against the extent map's own counts, the lookup rules, and log wrapping.
…bo boxes and spinners and keeps the window frame outside the client area
NativeForms 1e14385 draws RibbonComboBox and RibbonSpinner itself: always one stacked row, and still usable from a collapsed group's popup, which a hosted control cannot follow into. They replace the hosted text boxes and the owner-drawn ChoiceBox. - Interleave is a spinner over 1-256, so an out-of-range stride can no longer be typed; it clamps. - Metadata placement, layout profile and chunk placement are combo boxes. - Carve Hole takes a size in KiB, a placement (End / Offset) and an offset in KiB, which is enabled only for Offset. - Seed is a spinner. Tests: every input on the tab is a one-row ribbon field and none is a hosted control; the interleave spinner clamps 0 and 257; the carve fields reach the engine as bytes.
…tries sorts FAT and exFAT directories The tab's adapter guessed capabilities itself. It probed IArchiveDefragmentable with a stream that throws on first touch, and decided Optimize from a list of format ids. The registry now answers the question directly (MaintenanceCapabilities.Describe -> MaintenanceProfile). The adapter, renamed TargetCapabilities so it no longer shares the registry type's name, is now a view of that profile. - Defragment: offered for DefragmentExtents or SortDirectoryEntries. The extent strategies and the interleave, metadata and profile options follow the profile's DefragFeatures. Sort Entries runs MaintenanceOperations.SortDirectoryEntries and moves no data, so the layout options do not apply to it. - Optimize: Compress, Repack or Canonicalize, as offered, chosen in a Method combo. Each runs through MaintenanceOperations: staged, verified, replaced atomically. The ad-hoc routes (re-encode, 7z regrouping, CVF, chunk placement) are gone with the probe code. - Shrink, Purge, Clear, Scramble and Compact: run through the object that carries the capability (IArchiveShrinkable, IArchivePurgeable, IWipeEmpty). Minimal Geometry is gone because Compact now refuses it; changing geometry is its own operation, offered nowhere yet. Tests: the adapter agrees with the profile for every registered format. Sort Entries is offered for FAT and exFAT, not NTFS or ZIP. On an unsorted FAT volume it puts the root in name order with size and every file byte-identical, both through the presenter and from the ribbon. fsck.fat -n accepts the sorted volume (ExternalFsInterop, WSL).
…its name Symptom: with f19.bin selected inside an open FAT16 volume, the tab worked on f19.bin as a BIN/CUE image. It showed "Real archive layout — 0 regions, 12.6 KB wasted" and an empty files panel, and disabled nearly every operation. The open volume should have been the target. Root cause: an entry inside the open archive became the target by DetectByExtension on its name alone, ahead of the open volume. Fix: - An entry of the open archive is a candidate only when its content proves it a container. It is opened with ArchiveOperations.OpenEntry and identified with FormatDetector.DetectByContent on its head (up to 1 MiB when the entry stream cannot seek); the verdict is cached per archive, timestamp and entry. A file on disk is still judged by DetectCached. - A Target field on the tab lists "Open volume (disk.img)" and, when proven, "Selected: inner.img (Fat)". The open volume is the default; the list's context-menu verbs aim at the proven selection. - The status line names the target: "disk.img — FAT Filesystem Image · 16.0 MB". - Changing the selection on the tab never retargets. It only adds or removes the offer, a chosen selected target stays listed, and nothing is re-evaluated while an operation runs. The tab moves to another volume only when the open volume it worked on is no longer open. Tests: a plain .bin selected inside a FAT volume leaves the volume as the target with every FAT operation enabled. A real FAT image inside a ZIP is offered and becomes the target when picked. fake.img (plain bytes) and notes.txt are not offered. A selection change keeps the chosen target and the picked mode.
Symptom: at 1180 px on GTK the new Target group, one combo over two empty rows, pushed Options and View into drop-downs, hiding interleave and the view modes the owner wants directly visible. Fix: - The target field is a small row of the Options group, under Profiles…, shown only when there is a choice to make. The header line always names the target. - Start leads the Operation group and Stop takes the free slot under Scramble, which saves a column. - "Edit Profiles" is now "Profiles…". A headless test asserts that no group collapses at the shell's default 1180 px width, both on a plain FAT volume and in the widest state (a nested target offered while Optimize shows its method). Widening a field makes it fail.
Hawkynt
force-pushed
the
feat/defragment-ribbon-tab
branch
from
October 7, 2026 17:06
a3fddf6 to
1a3bf39
Compare
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.
What changed
Maintenance is no longer a separate dialog. A contextual Disk Tools → Defragment ribbon tab appears while the open archive/image, or an archive file selected in a folder, supports any maintenance operation. Selecting it hides the browser (hidden, not rebuilt, so leaving the tab restores it exactly) and gives the whole client area below the breadcrumb bar to the block map, with an optional files panel, the colour key and a progress line.
BlockMapControl.ViewMode), Files panel, Legend, AnalyzeOwner's names → engine: sort entries →
SortDirectoryEntries, optimize → Compress/Repack/Canonicalize as offered, consolidate →ConsolidateAtStart(Pack at End →ConsolidateAtEnd), defrag →FillHolesLazy, re-order →AscendingOrder, carve hole →CarveHole, clear → wipe-empty, purge/optimize/shrink → the existing verbs.Sort Entries is backed by the registry's
SortDirectoryEntriesoperation (#440,IFilesystemDirectoryOrderer), offered for FAT and exFAT and run throughMaintenanceOperations.SortDirectoryEntries.Design
Compression.NativeUI/Maintenance/TargetCapabilities.csis the only place that turns the registry's answer into the ribbon's terms. That answer isMaintenanceCapabilities.Describe→MaintenanceProfile(Maintenance operations split by effect: compress, canonicalize, repack, sort entries, defragment extents, change geometry #440): operations plus the honouredDefragFeatures. The adapter was renamed fromMaintenanceCapabilitiesso it no longer collides with the registry type. The probe code, the format-id lists and the ad-hoc Optimize routes are gone. Defragment = DefragmentExtents or SortDirectoryEntries. Optimize = Compress / Repack / Canonicalize, run throughMaintenanceOperations(staged, verified, atomic). Shrink, Purge, Clear and Scramble run through the object carrying the capability. Minimal Geometry is gone, because Compact refuses it now. A test holds the adapter to the profile for every registered format.MaintenancePresenter(choices →DefragOptions, validation, running each verb off the UI thread with live progress) andBlockMapSnapshot(layout reading). ANotSupportedExceptionends as Refused: the status line reports it, a message box explains it, and the image is untouched. After a change the archive is re-listed in place (ReloadArchiveInPlace).MaintenanceSessionkeeps the old nested-archive behaviour: the archive is maintained in a temporary copy and written back into the host.DefragmentWindow(2,221 lines) and the Archive tab's verb buttons (that tab keeps Reconfigure). Tools → Maintenance (new shortcut Ctrl+Shift+D) and the list's Maintenance context entries open the tab with their operation picked.--screenshot=maintenancenow shows the shell on the Defragment tab. Nothing else opened the window;PartitionsWindownever referenced it.NativeForms (pin bumped to 1e14385)
The first round worked around missing ribbon fields with hosted
TextBox/ComboBoxcontrols. NativeForms 1e14385, now pinned in its own commit, adds owner-drawnRibbonComboBoxandRibbonSpinner: always one row tall, and still usable from a collapsed group's popup. Win32Sizenow means the client area. The tab uses those fields throughout: Interleave is a 1–256 spinner; Metadata, Profile and Chunks are combo boxes; Carve Hole takes a size in KiB, End/Offset, and an offset in KiB; Seed is a spinner. NoRibbonHostItemis left on the tab. With the frame outside the client area, the window bottom, the colour key and the status strip are fully visible at 150 %. No manual frame compensation was found in the shell or its dialogs. The headless test backend keeps its real combo-box peer, becauseIComboBoxPeer.SetItemstakes aReadOnlySpanthatDispatchProxycannot box. One gap remains:RibbonGrouphas noVisible.Review fixes (Linux run by the coordinator)
RibbonFieldItemand none is hosted.BIG.BIN), while the lister applies the lowercase flags (big.bin). Names are now matched exactly, then case- and slash-insensitively when the match is unambiguous. Runs are counted in file order, before the extents are sorted for drawing. A test uses upper- and lowercase FAT fixtures and checks the counts against the extent map. FatExtentMap itself is unchanged: it still ignores the NT case bits, and the defrag mover keys on those names.LogViewthat wraps lines to its width and scrolls back with the mouse wheel; a test covers the wrapping.Verification
MaintenancePresenterTests(44): FAT, ZIP (no Defragment, per # maintenance verbs and R/W are lossless or refused; real-volume evidence matrix #432) and partitioned-disk (nothing offered) capabilities. Interleave boundaries 0/1/256/257/non-numeric/empty, hole-size and offset parsing. Strategy → engine-mode mapping. A refused option never reaches the engine. Defragmenting a scrambled FAT keeps the image size and every file byte-identical and reports no fragmentation afterwards. An oversized carve hole and Defragment on a ZIP are both refused with the image byte-identical.MainFormTests.Defragment.cs): the tab is hidden with no target, shown for FAT, hidden again for a folder and for a partitioned disk. The client area swaps to the block view and comes back with the splitter and the folded navigation pane as they were. Items are enabled per format (FAT vs ZIP) with reasons in their tooltips. Defrag modes and layout options go grey for other operations. The carve fields appear only for Carve Hole. The interleave spinner clamps 0→1 and 257→256 and its stride reaches the engine; the carve size and offset reach the engine as bytes. The view toggles changeViewMode. Context-menu verbs open the tab with that operation picked. Start defragments in place. A refusal leaves the image byte-identical. The legend is shown on entry. The no-duplicate-shortcut test stays green, and Ctrl+Shift+D is pinned.fsck.fat -n -Vaccepts the sorted volume (ExternalFsInterop, WSL; passed locally). A test also holds the adapter toMaintenanceCapabilities.Describefor every registered format.Compression.Tests.NativeUI,Compression.Tests.Ui,Compression.Tests.Maintenanceand the directory-orderer tests, against NativeForms 1e14385. CI passed on ubuntu-latest and windows-latest at40b733f8.wsl/powershellinvocations). The Win32 build was captured withPrintWindow(no input synthesized) at 1180×680 and 1700×900, over the FAT fixture, in all three view modes, with Carve Hole, Scramble and Compact picked. The ribbon fits at its natural height, the groups are readable, and the block view fills the client area in Blocks, Circle and 3D Stack. That check found the colour key switched off on entry, which is fixed and pinned by a test. The Linux (GTK, Xvfb) capture came from the branch-screenshot job,docs/screenshots/maintenance.png, and I inspected it. All four groups fit at the ribbon's natural height and the block map, files panel and colour key fill the client area. It also showed GTK combo boxes spilling under their captions, fixed by shorter metadata choices and a wider field. After the field change the latest Linux capture shows Interleave, Metadata "Unchanged" and Profile "(none)" whole, and the Windows capture at 150 % shows the full window.