# Changelog

## v1.0.0 — Initial release (2026-07-10)

**First public release of DiskPixie** — a Windows utility for opening,
browsing, extracting, and creating disk images.

- Open and browse ISO, IMA, IMG, BIN/CUE, and CCD disk images in an Explorer-style tree, with selective or full extraction
- Extract CD audio tracks as WAV, MP3, or OGG, with a default format setting
- Create new ISO9660, real floppy (720KB/1.44MB/2.88MB), and larger FAT16/32 "superfloppy" images (LS-120, Zip, custom size) from files you drag in
- Image a real physical drive (optical, floppy, HDD/USB) directly, strictly read-only
- Windows Explorer right-click integration and command-line extraction
- Two built-in themes (Pixie Dark / Pixie Light), live-switchable from Settings

## Detailed development log

The entries below are the session-by-session build log for v1.0.0.

## 2026-07-10 (6) — Physical drive imaging (final item from original scope) + "Open in DiskPixie"

**Why:** the last deferred milestone from the very first design session -
creating an image directly from a real optical/floppy/HDD/USB drive.

**Added:**
- `DiskPixie.Core/PhysicalDrives/`: WMI-based `PhysicalDriveEnumerator`
  (optical/floppy/HDD-USB), `PhysicalDriveImager` (P/Invoke `CreateFile`,
  strictly `GENERIC_READ`-only - can never write to a physical drive - with
  progress reporting and cancellation).
- `ElevationHelper` (App): detects admin rights, offers a UAC-elevated
  restart only when actually needed (HDD/USB; optical/floppy usually don't
  need it).
- `SelectPhysicalDriveWindow`: drive picker with a **Refresh** button (per
  user request - USB drives mount/unmount anytime), progress bar, and
  cancel-mid-copy. Wired in place of the old "coming soon" stubs for
  `CreateImageWindow`'s three physical-drive options.
- "Open in DiskPixie" context menu verb (`--open`) opens a file straight to
  the browser window, no `MainWindow` involved.

**Fixed (found via testing enumeration against this machine's real
hardware):** `Win32_CDROMDrive.DeviceID` is a PnP hardware path, not a
usable device path - fixed to build `\\.\<DriveLetter>` from the `Drive`
property instead. `Win32_FloppyDrive` doesn't exist as a WMI class on
machines with no floppy controller and was crashing the *entire*
enumeration call - wrapped per-category so a missing WMI class just means
"no drives of that type" instead of taking down the other categories too.

**Verified:** enumeration confirmed against this machine's real drives (1
optical, 0 floppy - correctly empty not crashed, 5 real HDD/USB with correct
paths/sizes/classification). Deliberately did not raw-read any of this
machine's live disks without explicit authorization for that specific test -
the `CreateFile`/`FileStream` pattern itself is standard, well-established
Win32 usage. `SelectPhysicalDriveWindow` confirmed to load without XAML
errors via the same direct-instantiation technique that caught the earlier
crash.

**Known limitation, by design:** optical imaging reads cooked sectors via a
plain volume handle - covers data discs (the common case) but not physical
audio-CD ripping, which needs SCSI MMC pass-through. Out of scope for this
pass.

**Backlog (user, "eventually"):** generic boot disk creation - FreeDOS/MS-DOS
boot floppies, El Torito bootable ISOs.

## 2026-07-10 (5) — BIN/CUE/CCD reading + CD audio track extraction

**Why:** last two big deferred items from the original scope — raw CD image
support and audio ripping — requested together in one go.

**Added:**
- `DiskPixie.Core/RawCd/`: `CueSheetParser` and `CcdSheetParser`, both
  producing a shared `TrackDescriptor` model; `RawSectorDataStream` strips
  raw-sector sync/header/ECC framing on every read so DiscUtils' `CDReader`
  can mount a data track exactly like a plain ISO9660 image.
- `BinCueImageReader` (`Formats/`): mounts the data track's filesystem,
  exposes audio tracks under a synthetic "Audio Tracks" folder, implements
  `IAudioTrackSource` for format-choice export.
- `AudioTrackExporter` (`Audio/`): WAV (NAudio), MP3 (NAudio.Lame), OGG
  (pure-.NET `OggVorbisEncoder` package — no native codec needed; its API was
  unfamiliar so it was reflected over directly rather than guessed).
- `.cue`/`.ccd` wired into `DiskImageReaderFactory`. Per user request,
  standalone `.bin` with no `.cue` now best-effort guesses the sector layout
  (tries MODE1/2352, MODE2/2352, cooked-2048; keeps whichever mounts a real
  filesystem) instead of refusing the file.
- UI: audio tracks show a musical-note glyph in the browse tree; an "Audio
  format" picker appears in the browser toolbar when relevant, applied to
  both Extract Selected and Extract All. Per user request, the format choice
  is now also a Settings default (`AppSettings.DefaultAudioFormat`) so it
  doesn't need re-selecting per image.
- `.cue`/`.ccd` added to the Explorer context-menu extensions; per user
  request, the verb entries now show the app's own icon in the right-click
  menu (registry `Icon` value pointing at the exe, index 0).

**Fixed (user-reported crash):** opening any `.cue`/`.ccd` crashed the app —
`XamlParseException: Cannot find resource named 'BoolToVisibilityConverter'`.
`DiskImageBrowserWindow.xaml` declared that converter *after* the
`HierarchicalDataTemplate` that referenced it via `StaticResource`; WPF
resolves `StaticResource` in document order within a dictionary, so the
reference failed before the converter existed. Fixed by reordering. Confirmed
by the user retesting the exact crash path afterward.

**Verified:** hand-built a real mixed-mode BIN/CUE (real ISO9660 data track +
a synthetic-tone audio track) via a harness — confirmed exact sector-boundary
math, correct data extraction, and all three audio export formats produce
valid files (RIFF/OggS signatures, non-empty MP3) on the first real run.
Separately verified `CcdSheetParser` against a hand-written CCD TOC. Verified
the cue-less `.bin` guessing path via the real exe's CLI, with and without a
sibling `.cue`. Left a real sample image in `SampleImages/` for manual
testing.

## 2026-07-10 (4) — Visual overhaul: real theme system, About window, app icon

**Why:** user asked for the UI to be "a little prettier," then pointed at two
reference windows in a sibling project (AbigailsMediaRenamer) to match the
"vibe" — its theme-switching mechanism and its About window/Library Manager
styling — while explicitly keeping DiskPixie's own violet/cyan identity for
dark mode.

**Added:**
- Real multi-theme system: `Themes/DiskPixieStyles.xaml` (all control styles,
  DynamicResource-driven) + swappable `PixieDarkTheme.xaml`/`PixieLightTheme.xaml`
  palettes, `ThemeService.Apply` for live no-restart switching, a theme picker
  in Settings, `AppSettings.Theme` persistence.
- Rebuilt `AboutWindow` to mirror AbigailsMediaRenamer's structure: header
  banner, accent line, Ko-Fi/Website/Email pill buttons with the *real*
  Abnormality Software URLs, "by Abnormality Software" byline, scrollable
  bio + open-source credits (DiscUtils, CommunityToolkit.Mvvm).
- Card/toolbar visual treatment for `DiskImageBrowserWindow` and
  `CreateImageContentsWindow` (rounded card, drop shadow, toolbar bar),
  inspired by AbigailsMediaRenamer's Library Manager.
- New "Extract All" button in the browser window.
- Real app icon (`Assets/app.ico`, 16/32/48/256px) rendered from the same
  floppy-disk vector art already used on the main screen, wired as both the
  exe's `ApplicationIcon` and every window's `Icon`.

**Fixed (found via actual screenshots, not just build success):**
- Implicit `Style TargetType="Window"` never applied `Background` — every
  window rendered on plain white regardless of theme. Root cause: a known WPF
  quirk AbigailsMediaRenamer itself works around the same way — set
  `Background` explicitly on each window's root element instead of relying on
  an app-level implicit style. Applied to all 6 windows.
- Theme/type ComboBoxes showed the raw record `ToString()` instead of the
  label text — the same `DisplayMemberPath`-doesn't-reach-`SelectionBoxItem`
  quirk already known from the Fragged client project. Fixed with explicit
  `ItemTemplate`s.
- Settings/CreateImage windows clipped their content — fixed with
  `Width` + `SizeToContent="Height"` + `MaxHeight` + `NoResize`, matching
  AbigailsMediaRenamer's own small-dialog pattern.
- **User-reported**: Open/Extract dialogs silently ignored the configured
  default folder whenever it didn't exist yet, falling back to whatever
  Windows defaulted to instead. Now always creates the folder and sets it as
  the dialog's starting location. Verified via screenshot — Open dialog now
  opens directly into the configured `SampleImages` folder.

**False alarm:** user initially reported `.iso` files missing from the Open
dialog even under "All files" — turned out to be user error on their side,
confirmed no code issue after they looked again.

**Process note:** UI Automation against this app's own buttons proved
unreliable when matching by `Name` (grabs inner `TextBlock`s instead of the
parent `Button`) and raw coordinate-based mouse clicks landed on an unrelated
window once (harmlessly, onto the CLI's own terminal). Switched to
`ControlType`-based element search + `InvokePattern`/`SendKeys` for the rest
of the session's verification.

## 2026-07-10 — Initial scaffold: open/browse/extract ISO+FAT, create ISO/FAT floppy

First working version of DiskPixie. New repo, C# WPF (.NET 10), split into
`DiskPixie.App` (UI) and `DiskPixie.Core` (format logic, no UI dependency).

**Why:** user wants an IsoBuster-style utility to open common Windows disk
image formats (.iso, .ima, .img, eventually .bin/.cue/.ccd), browse their
contents, and extract files — plus create new images from dragged-in files.
Full physical-drive imaging and CD audio ripping were scoped in but
deliberately deferred to keep this first pass shippable and correct rather
than broad and half-working.

**Added:**
- `DiskPixie.Core`: `IDiskImageReader`/`DiskEntry` abstraction over DiscUtils'
  `DiscFileSystem`, giving working ISO9660 and FAT (.ima) reading through one
  shared implementation (`DiscFileSystemImageReader`). `.img` files are
  content-sniffed (ISO9660 "CD001" signature vs. FAT) since the extension
  alone is ambiguous. `.bin`/`.cue`/`.ccd` throw a friendly
  `UnsupportedFormatException` rather than crashing — not implemented yet.
- `DiskPixie.Core`: `IDiskImageWriter` + `IsoImageWriter` (DiscUtils
  `CDBuilder`) and `FatFloppyImageWriter` (DiscUtils `FatFileSystem.FormatFloppy`),
  both recursively adding dropped files/folders. `PhysicalDriveImageWriter` is
  a stub throwing `NotImplementedException` — physical optical/floppy/HDD
  imaging needs raw Win32 device I/O and is future work.
- `DiskPixie.Core`: `AppSettingsService` — JSON settings at
  `%AppData%\DiskPixie\settings.json` holding `DefaultImageFolder` and
  `DefaultExtractFolder` (the two settings the user explicitly asked for as
  the bare minimum).
- `DiskPixie.App`: `MainWindow` (2 big buttons — Open/Create Disk Image, 2
  small — Settings/About), `SettingsWindow`, `AboutWindow`,
  `DiskImageBrowserWindow` (single checkbox `TreeView` over the image's
  files/folders, cascading folder-check to children, "Extract Selected" →
  folder picker → extracts preserving relative paths), `CreateImageWindow`
  (filename + type picker, "coming soon" for the three physical-drive
  options), `CreateImageContentsWindow` (drag-and-drop file list, builds on a
  background thread so the UI doesn't freeze on a large ISO).

**Verified:**
- Core round-trip proven via a throwaway console harness (not just build
  success): built a real ISO and a real FAT floppy image from a small test
  file tree, reopened both through `DiskImageReaderFactory`, walked the
  resulting tree, and extracted files back out with matching content.
- Live app launch confirmed via UI Automation (scoped to the app's own
  process ID) — all four MainWindow buttons present and clickable, Settings
  window opens correctly.
- User manually verified Settings end-to-end through the real running app:
  set both folder paths, clicked Save, confirmed they round-tripped correctly
  into `settings.json`.
- Open/Create Disk Image window flows are mid-verification by the user
  directly in the running app (see `currenttask.md` for current status).

**Incident, no lasting effect:** an early UI Automation script searched for a
window named "Settings" without scoping to DiskPixie's process ID, and
latched onto the real Windows Settings app (also titled "Settings") instead
of DiskPixie's own dialog — typed two test folder paths into a couple of its
textboxes before a `Save` button lookup failed with a null-reference error
(no such button exists there, so nothing was invoked or persisted). Confirmed
no Windows settings were changed and DiskPixie's own `settings.json` was
untouched by that run. Fixed by always filtering
`AutomationElement.FindFirst`/`FindAll` by `ProcessIdProperty` before matching
by window name — noted in `currenttask.md`'s "What NOT to do".

**Explicitly deferred (not bugs, staged on purpose):** BIN/CUE/CCD raw-sector
reading, CD audio track extraction (WAV/OGG/MP3 — NAudio + NAudio.Lame/vorbis
chosen for when this is built), physical drive imaging, and .dms (skipped
per user request — Amiga-specific compression format, low value for this
app's likely audience).

**Not yet done:** no git commit made this session — repo is git-init'd but
everything is still working-tree only.

## 2026-07-10 (2) — Real bugs found via hands-on testing, fixed; expanded FAT sizes

User drove the actual compiled app end-to-end (not just build success) and
found two real bugs, both fixed and re-verified through a harness before
handing back:

**Fixed:**
- Extracted files carried a trailing `;1` (ISO9660's file version-number
  suffix, e.g. `whatever.png;1`) into their extracted name on disk. Stripped
  in `DiscFileSystemImageReader` when building the display tree; the internal
  DiscUtils lookup still uses the untouched name so file access is unaffected.
- Building a FAT floppy image failed with `IOException: Invalid path` for any
  file with a 4+ character extension (e.g. `test.json`). Root cause:
  DiscUtils.Fat's write path only accepts strict DOS 8.3 short names — no LFN
  write support — confirmed via a reproduction harness that ruled out a
  leading-backslash theory first. Fixed with `FatImageContentWriter.ToShortName`,
  which sanitizes/truncates to a legal 8.3 name and de-dupes collisions within
  a directory the way real DOS did (`NAME~1.EXT`). ISO/Joliet does not have
  this problem — long names there were already confirmed intact.
- Browse tree's root node displayed as a bare `/` (user reported it as a
  "weird missing square" — just unstyled TreeView chrome plus an empty label).
  Now labelled with the actual image file name.

**Added — expanded FAT image sizes,** per user follow-up ("we should also
create FAT32 floppy images, im sure they were a thing"): clarified that real
3.5" floppies were always FAT12 (FAT32 needs far more clusters than a floppy
holds) and confirmed the actual DiscUtils-reported FAT variant per size via a
throwaway probe (720KB/1.44MB → FAT12, 2.88MB → FAT16). User agreed to add
both the real floppy sizes and larger FAT16/32 "superfloppy" images:
- `FatFloppyImageWriter` now takes a `FloppyDiskType` (720KB/1.44MB/2.88MB)
  instead of being hardcoded to 1.44MB.
- New `FatLargeVolumeImageWriter` (arbitrary byte capacity, via DiscUtils'
  `FatFileSystem.FormatPartition`) backs three presets (LS-120 120MB, Zip
  100MB, Zip 250MB) plus a user-entered custom size. Probed the boundary
  first: found and worked around a real DiscUtils bug where requesting
  exactly 2GB silently produces a ~4MB volume instead (likely an Int32/sector
  overflow) — capped custom size at 1900MB, everything below that verified to
  produce the exact requested byte size.
- Extracted the shared file-tree-writing + 8.3-sanitizing logic (previously
  only in `FatFloppyImageWriter`) into `FatImageContentWriter`, used by both
  writers.
- `CreateImageWindow` now lists all 7 ISO/FAT type+size combos plus the 3
  physical-drive stubs, with a size input box that only appears for "FAT
  custom size".
- All 7 new size/type combinations verified via a console harness: build →
  reopen → confirm exact byte size and file presence, for 720KB/1.44MB/2.88MB
  floppies and LS-120/Zip100/Zip250/custom-300MB volumes.

**Confirmed already working (no change needed):** Joliet was asked about and
was already wired up on both sides (`CDBuilder.UseJoliet = true` on write,
`CDReader(stream, joliet: true)` on read) — this is exactly why the earlier
`test.json` extraction from an ISO came back with its full long name intact
while the FAT path truncated it.

## 2026-07-10 (3) — CLI extraction + Explorer context menu integration

**Why:** user wants to extract disk images without opening the GUI — both a
right-click "Extract Here"/"Extract to Subfolder" in Explorer, and a plain
command line (`diskpixie -x image dest`). Confirmed with user first that
registry-based context menu verbs can't dynamically show the clicked file's
name in the menu caption (needs a real COM shell extension, out of scope) —
went with static captions, since the created destination folder is still
correctly named after the image at run time.

**Added:**
- `DiskPixie.Core`: `DiskImageExtractor.ExtractAll` (full-tree extraction,
  shared by the CLI) and `Cli/CliExtractor` (`-x <image> <dest>`,
  `--extract-here <image>`, `--extract-to-folder <image>`).
- `DiskPixie.App`: `App.xaml.cs` now intercepts CLI args in `OnStartup`
  before showing any window, `AttachConsole`-ing (P/Invoke) so output reaches
  the caller's terminal, then exits with the extractor's exit code.
- `DiskPixie.App/Services/ContextMenuIntegration`: registers/unregisters
  "Extract Here" and "Extract to Subfolder" for `.iso`/`.ima`/`.img` under
  `HKCU\...\SystemFileAssociations` (no admin needed, doesn't touch the
  extension's default open action). Wired to a new toggle button in
  `SettingsWindow`.

**Verified:** ran the actual built `DiskPixie.App.exe` from a real terminal
with all three CLI flags — correct exit codes, correct console output,
correct extracted files/structure. Verified registry Register/Unregister
against the real Windows registry via a harness referencing the App
assembly directly — all 6 keys (2 verbs × 3 extensions) created correctly and
fully removed on unregister.

**Known limitation, by design:** the context-menu command captures
`Environment.ProcessPath` at the moment "Add to right-click menu" is
clicked — it must be clicked from the real built `.exe`, not a `dotnet run`
session, or it'll register `dotnet.exe` as the target. Not an issue for an
end user running the installed app; only matters for dev testing (noted in
`currenttask.md`).
