feat(wails): move the frontend onto v3's generated bindings

frontend/wailsjs/ is deleted and frontend/bindings/ takes its place —
a real TypeScript module tree nested by Go import path, generated by
wails3's static analyser rather than by building the app and running
it.  The @go alias absorbs the constant prefix, so a call site imports
'@go/library/library.js' and the codemod over all 93 sites was a
specifier rewrite plus splitting @go/models' namespaces into one
import per package.

The 12 SetContext bindings and the fake `context` model are gone, as
Phase 2's ServiceStartup port promised: 272 methods across 12
services, none of them plumbing.

@runtime/runtime is now a local shim (src/wails/runtime.ts) over
@wailsio/runtime, so the 22 EventsOn imports are untouched.  It
unwraps v3's WailsEvent into v2's callback shape, which is exact here:
nothing in backend/events passes more than one data argument, and v3
only packs arguments into a slice when there is more than one.

v3 tells the truth about two things v2 lied about, and that is most of
the diff.  A Go nil slice really does arrive as JSON null, and a Go
named string type really is an enum; v2 typed them as T[] and string.
utils/binding.ts states the app's actual contract — an absent list is
an empty list — once, at the boundary where it is true, and also drops
the CancellablePromise the app never cancels.  Four test fixtures
widen an enum field back to its value union.

Not done, and Phase 5's to fix: frontend/test/support/wails-fake.ts
still fakes window.go, which v3 does not have, so `make ui-test` is
broken and harness.test.ts fails to compile on EventsEmit.  That test
also asserts v2 ordering that no longer holds — v3's Events.Emit calls
the backend and does not notify in-page listeners at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDCbcCZQepnpSQYJ6SxxZm
This commit is contained in:
2026-08-14 17:48:38 -04:00
co-authored by Claude Opus 5
parent c9905fbcff
commit 162c68769f
133 changed files with 5755 additions and 5094 deletions
@@ -0,0 +1,454 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* Library manages scanning and querying the music collection.
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as sqlcgen$0 from "../database/sql/sqlcgen/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as jobs$0 from "../jobs/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* AcquirePipelineLock acquires the pipeline mutex for a tag write
* operation. The caller must call ReleasePipelineLock when done.
* If a scan is currently in progress, AcquirePipelineLock blocks
* until it completes (and vice versa).
*/
export function AcquirePipelineLock(): $CancellablePromise<void> {
return $Call.ByID(2056761494);
}
/**
* AddLibrary creates a new library from a directory path, emits a
* LibraryAdded event, and starts an asynchronous scan.
*/
export function AddLibrary(path: string): $CancellablePromise<sqlcgen$0.Library | null> {
return $Call.ByID(4258719739, path);
}
/**
* CancelAllScans cancels the current scan and clears the entire
* queue so no further libraries are scanned.
*/
export function CancelAllScans(): $CancellablePromise<void> {
return $Call.ByID(2826471660);
}
/**
* CancelCurrentScan cancels only the currently scanning library.
* The next queued library (if any) starts automatically when the
* current scan's goroutine completes.
*/
export function CancelCurrentScan(): $CancellablePromise<void> {
return $Call.ByID(2190880081);
}
/**
* CancelScan cancels an in-progress scan. Returns immediately;
* scan goroutines stop at their next checkpoint.
*
* Deprecated: Use CancelCurrentScan or CancelAllScans for
* queue-aware cancellation.
*/
export function CancelScan(): $CancellablePromise<void> {
return $Call.ByID(4160132270);
}
/**
* FullRescan clears the queue and player, wipes all library data
* (database records and cover art files), and performs a fresh
* scan of every library in the database. The returned ScanMetrics
* reflects the last library scanned; clear-phase durations are
* folded into its totals.
*/
export function FullRescan(): $CancellablePromise<$models.ScanMetrics | null> {
return $Call.ByID(378891236);
}
/**
* GetAlbumCompleteness answers "do I have all of this album" from the
* tags read at scan time, with no network.
*
* The album page used to ask MusicBrainz, because the only track total
* it had was the length of whatever tracklist it was already showing —
* which for a library copy is a tautology. The denominator in a file's
* "5/12" is a real answer and it is already on disk; this is where it
* gets read.
*
* Complete is deliberately >= rather than ==: bonus and hidden tracks
* routinely put a folder over its declared total, and that is a
* complete album, not a broken one.
*/
export function GetAlbumCompleteness(albumID: number): $CancellablePromise<$models.AlbumCompleteness> {
return $Call.ByID(2895787150, albumID);
}
/**
* GetAlbumTracks returns all tracks for a given album (release group), ordered by disc and track number.
*/
export function GetAlbumTracks(albumID: number): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(300451334, albumID);
}
/**
* GetAlbumTracksByLibrary returns tracks for the given album,
* scoped to the given library.
*/
export function GetAlbumTracksByLibrary(albumID: number, libraryID: number): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(3304485554, albumID, libraryID);
}
/**
* GetAlbumsByArtist returns all albums where the given artist is the album artist.
*/
export function GetAlbumsByArtist(artistID: number): $CancellablePromise<$models.Album[] | null> {
return $Call.ByID(1456840721, artistID);
}
/**
* GetAlbumsByArtistByLibrary returns albums for the given artist
* that have tracks in the given library.
*/
export function GetAlbumsByArtistByLibrary(artistID: number, libraryID: number): $CancellablePromise<$models.Album[] | null> {
return $Call.ByID(2809291, artistID, libraryID);
}
/**
* GetAllAlbums returns all albums with cover art and artist info for the cover grid.
*/
export function GetAllAlbums(): $CancellablePromise<$models.Album[] | null> {
return $Call.ByID(2015458954);
}
/**
* GetAllAlbumsByLibrary returns albums that have tracks in the given library.
*/
export function GetAllAlbumsByLibrary(libraryID: number): $CancellablePromise<$models.Album[] | null> {
return $Call.ByID(4023050470, libraryID);
}
/**
* GetAllArtists returns artists that are credited as album artists, ordered by name.
*/
export function GetAllArtists(): $CancellablePromise<$models.Artist[] | null> {
return $Call.ByID(2529088294);
}
/**
* GetAllArtistsByLibrary returns artists that have albums with tracks
* in the given library.
*/
export function GetAllArtistsByLibrary(libraryID: number): $CancellablePromise<$models.Artist[] | null> {
return $Call.ByID(1170594642, libraryID);
}
/**
* GetAllGenresWithCounts returns all genres with their track counts.
*/
export function GetAllGenresWithCounts(): $CancellablePromise<$models.GenreWithCount[] | null> {
return $Call.ByID(602231298);
}
/**
* GetAllGenresWithCountsByLibrary returns genres with track counts
* scoped to the given library.
*/
export function GetAllGenresWithCountsByLibrary(libraryID: number): $CancellablePromise<$models.GenreWithCount[] | null> {
return $Call.ByID(772684334, libraryID);
}
/**
* GetAllLibrariesWithTrackCounts returns all libraries with their
* audio file counts. Typically 1-5 libraries so the loop is trivial.
*/
export function GetAllLibrariesWithTrackCounts(): $CancellablePromise<$models.Info[] | null> {
return $Call.ByID(3420301148);
}
/**
* GetAllTracks returns an array of track structs of every file in the library.
*/
export function GetAllTracks(): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(2991050010);
}
/**
* GetAllTracksByLibrary returns tracks scoped to a specific library.
*/
export function GetAllTracksByLibrary(libraryID: number): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(3882999766, libraryID);
}
/**
* GetFilePathsByAlbums returns the file paths of every track in the
* given albums, grouped by album id.
*
* "Play this artist", "play these albums" and the album drag cache each
* resolved paths with one binding call per album, sequentially, and each
* asked for whole track rows to read one field off them (perf.m2). This
* is that question asked once. The result is grouped rather than
* flattened because the caller owns the ordering — an album list is
* sorted by name, not by id — and because the drag cache stores it per
* album.
*
* A library id of 0 means "every library", matching the caller's
* selected-library filter being unset.
*/
export function GetFilePathsByAlbums(albumIDs: number[] | null, libraryID: number): $CancellablePromise<{ [_ in `${number}`]?: string[] | null } | null> {
return $Call.ByID(1692993358, albumIDs, libraryID);
}
/**
* GetFilePathsByGenres returns the file paths of every track tagged with
* the given genres, grouped by genre name. See GetFilePathsByAlbums —
* same finding, same shape, and the caller still owns the de-duplication
* across genres because it owns the order.
*/
export function GetFilePathsByGenres(genreNames: string[] | null, libraryID: number): $CancellablePromise<{ [_ in string]?: string[] | null } | null> {
return $Call.ByID(1180707302, genreNames, libraryID);
}
/**
* GetFilePathsByRecordingMBIDs returns the file paths of every track
* whose recording MBID is in mbids, grouped by MBID.
*
* This is the catalog side of GetFilePathsByAlbums. An Explore album
* page knows what the user owns as a set of recording MBIDs and nothing
* else: that is exactly how the backend decides a track's InLibrary
* flag (markReleasesInLibrary → CheckMBIDs), and MBTrack.LocalID is a
* declared field that nothing writes, so there is no id to ask by.
*
* Grouped rather than flattened for the same two reasons as its
* siblings — the caller owns the order (the tracklist's, not the
* database's), and one recording can have more than one file, which is
* what this app's duplicate detection exists for.
*
* A library id of 0 means "every library".
*/
export function GetFilePathsByRecordingMBIDs(mbids: string[] | null, libraryID: number): $CancellablePromise<{ [_ in string]?: string[] | null } | null> {
return $Call.ByID(2789061644, mbids, libraryID);
}
/**
* GetRemovalImpact returns pre-removal counts for the confirmation
* dialog. All queries are read-only.
*/
export function GetRemovalImpact(libraryID: number): $CancellablePromise<$models.RemovalImpact | null> {
return $Call.ByID(2715588999, libraryID);
}
/**
* GetScanQueueLength returns the number of libraries waiting in the
* scan queue (excludes the currently scanning library).
*/
export function GetScanQueueLength(): $CancellablePromise<number> {
return $Call.ByID(2739382973);
}
/**
* GetTrackMBIDs returns the MusicBrainz IDs for the track at the
* given file path. Returns empty strings for entities without MBIDs.
*/
export function GetTrackMBIDs(filePath: string): $CancellablePromise<$models.TrackMBIDs> {
return $Call.ByID(56752473, filePath);
}
/**
* GetTracksByGenre returns all tracks tagged with the given genre.
*/
export function GetTracksByGenre(genreName: string): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(1674220245, genreName);
}
/**
* GetTracksByGenreByLibrary returns tracks tagged with the given
* genre, scoped to the given library.
*/
export function GetTracksByGenreByLibrary(genreName: string, libraryID: number): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(4240564783, genreName, libraryID);
}
/**
* IsScanActive returns whether a scan is currently running.
*/
export function IsScanActive(): $CancellablePromise<boolean> {
return $Call.ByID(1365062070);
}
/**
* IsScanPaused returns whether the scan is currently paused.
*/
export function IsScanPaused(): $CancellablePromise<boolean> {
return $Call.ByID(2930130958);
}
/**
* LibraryPath resolves a library's root directory by id.
*/
export function LibraryPath(id: number): $CancellablePromise<string> {
return $Call.ByID(1798172787, id);
}
/**
* PauseScan pauses an in-progress scan. Workers block at their
* next pause checkpoint until ResumeScan is called.
*/
export function PauseScan(): $CancellablePromise<void> {
return $Call.ByID(3522392788);
}
/**
* QueuedLibraryNames returns the display names of libraries waiting
* in the scan queue, in FIFO order.
*/
export function QueuedLibraryNames(): $CancellablePromise<string[] | null> {
return $Call.ByID(2036951097);
}
/**
* ReleasePipelineLock releases the pipeline mutex after a tag write.
*/
export function ReleasePipelineLock(): $CancellablePromise<void> {
return $Call.ByID(2053843147);
}
/**
* RemoveFromLibrary deletes the database rows for the given file paths
* and records each path as excluded, so the next scan does not import
* it again. **It does not touch the files on disk** — that is the
* promise the confirmation dialog makes, and the reason this operation
* is safe to put one keystroke from a focused row.
*
* The exclusion is not an enhancement. Without it the next scan finds
* the file, sees no row, and imports it again — a button that undoes
* itself, which is worse than no button.
*/
export function RemoveFromLibrary(filePaths: string[] | null): $CancellablePromise<$models.RemovalResult | null> {
return $Call.ByID(825509452, filePaths);
}
/**
* RemoveLibrary atomically removes a library and all its data,
* performing orphan cleanup, phantom metadata conversion, FTS5
* rebuild, and queue compaction. Returns a summary of what was removed.
*/
export function RemoveLibrary(id: number): $CancellablePromise<$models.RemovalSummary | null> {
return $Call.ByID(4110443412, id);
}
/**
* RenameLibrary validates and updates a library's display name.
*/
export function RenameLibrary(id: number, newName: string): $CancellablePromise<void> {
return $Call.ByID(3949052986, id, newName);
}
/**
* RestorePausedScans adopts scans that were paused when the app last
* shut down back into the job registry, still paused. Call during
* startup before SoftScanAllLibraries so the soft scan does not restart
* a library the user deliberately paused.
*/
export function RestorePausedScans(): $CancellablePromise<void> {
return $Call.ByID(4243712755);
}
/**
* ResumeScan unblocks a paused scan.
*/
export function ResumeScan(): $CancellablePromise<void> {
return $Call.ByID(3079702539);
}
/**
* ScanAllLibraries queries all libraries from the database and queues
* each one for scanning. Existing dedup logic ensures no duplicates.
*/
export function ScanAllLibraries(): $CancellablePromise<void> {
return $Call.ByID(266098306);
}
/**
* ScanLibrary queues a scan for the library with the given database ID.
* If no scan is active the library is scanned immediately; otherwise it
* is appended to the queue. Duplicate requests (same library already
* scanning or already queued) are silently ignored.
*/
export function ScanLibrary(id: number): $CancellablePromise<void> {
return $Call.ByID(2994459743, id);
}
/**
* SearchTracks performs an FTS5 full-text search and returns
* matching tracks with full metadata.
*/
export function SearchTracks(query: string): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(3848515709, query);
}
/**
* SearchTracksByLibrary performs an FTS5 search scoped to a specific
* library and returns matching tracks with full metadata.
*/
export function SearchTracksByLibrary(query: string, libraryID: number): $CancellablePromise<$models.Track[] | null> {
return $Call.ByID(152384327, query, libraryID);
}
/**
* SetJobRegistry wires the background job registry so scans report
* progress, logs, and pause/cancel controls to the frontend.
*/
export function SetJobRegistry(reg: jobs$0.Registry | null): $CancellablePromise<void> {
return $Call.ByID(4271773525, reg);
}
/**
* SetRemovalHooks provides optional hooks for cross-cutting
* orchestration during RemoveLibrary.
*/
export function SetRemovalHooks(h: $models.RemovalHooks): $CancellablePromise<void> {
return $Call.ByID(3190933207, h);
}
/**
* SetRescanHooks provides optional hooks for cross-cutting
* orchestration during FullRescan.
*/
export function SetRescanHooks(h: $models.RescanHooks): $CancellablePromise<void> {
return $Call.ByID(1944064333, h);
}
/**
* SetScanHooks provides optional hooks for cross-cutting
* orchestration after each library scan.
*/
export function SetScanHooks(h: $models.ScanHooks): $CancellablePromise<void> {
return $Call.ByID(520513414, h);
}
/**
* SoftScanAllLibraries performs a lightweight launch-time scan.
* First it claims any orphaned tracks (library_id=0) left over from
* the pre-multi-library schema. Then for each library it compares
* the number of audio files on disk against the track count in the
* database. Only libraries where the counts differ (files added or
* removed since last scan) are queued for a full scan. Libraries
* that are unchanged are silently skipped — no progress bar, no
* scan events.
*/
export function SoftScanAllLibraries(): $CancellablePromise<void> {
return $Call.ByID(1401473402);
}