Repository navigation
Migrate the frontend to Svelte 5 and SvelteKit 3 - #24
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
- Props via $props(), prop-derived values via $derived/$derived.by - Event directives to event attributes; createEventDispatcher to a callback prop - Slots to snippets; $app/stores to $app/state - Replace svelte/legacy run()/preventDefault() shims with $effect and inline handlers - Bridge Skeleton v2 / svelte-headless-table ComponentType typings to Svelte 5 - Repair the eslint config, which still referenced eslint-plugin-svelte3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Svelte 5 hydration-checks {@html} content, and stringifying Skeleton's
setInitialClassState into svelte:head trips hydration_html_changed on
every page. An inline script in app.html does the same job and runs
before hydration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Skeleton v5 requires Tailwind 4 and replaces v2's monolithic components with Zag-based compound anatomies: - Tailwind 3 -> 4: @tailwindcss/vite, CSS-first config, purgecss dropped - cambia-theme.ts -> src/cambia-theme.css against the v5 theme contract (hex, shades 50-950, contrast ramps); data-theme moved to <html> - variant-* -> preset-*, *-token pairings -> symmetric v5 pairings - AppShell (removed upstream) -> hand-rolled scroll/header/footer layout - popup action -> Tooltip; clipboard action -> navigator.clipboard - toast store -> createToaster + Toast.Group; modal store -> a plain flag - Paginator -> shared frag over Pagination; Accordion, Progress, RecursiveTreeView -> collection-driven TreeView - lightswitch now comes from the in-repo copy, since v5 dropped it - svelte-headless-table -> @humanspeak/svelte-headless-table (Svelte 5) getCssColor now parses hex, as v5 themes no longer emit RGB triplets. ComboBox.svelte is removed: it was unreferenced and its ListBox+popup API has no v5 equivalent worth porting blind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Clears 10 of 14 npm audit findings, all in dev/build tooling that never reaches the bundle: the Vitest UI arbitrary-read (critical), the Vite fs.deny bypass and esbuild dev-server issues, and the minimatch ReDoS reached through typescript-eslint 6. The remaining four are not fixable here and are not reachable: - cookie <0.7.0 via @sveltejs/kit, which still pins cookie ^0.6.0 even on latest; adapter-static ships no server anyway - uuid via musicbrainz-api, whose advisory covers v3/v5/v6 when a buffer argument is passed, while musicbrainz-api only calls v4 in its digest-auth path typescript-eslint 8 turns on no-unused-expressions by default, which flags four pre-existing ternary-as-statement lines; left as-is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Both components built class names by interpolation, e.g. bg-{bgColor}-400.
Tailwind has never been able to see those, and under Tailwind 3 they only
resolved because the content globs scanned Skeleton v2's Svelte source,
which happened to contain the same literals. Skeleton v5 ships CSS only,
so the classes stopped being emitted and the fields lost their tint.
ChecksumSegment was also using bg-opacity-25, removed in Tailwind 4, so
even the colours that did survive rendered fully opaque.
Both now select complete class strings, which the scanner can find, with
the opacity expressed as /25. That also retires the hidden divs in Grade
that existed solely to force the classes into the bundle.
The Unknown state uses surface-950 in dark mode rather than surface-900,
because the card behind it is now itself surface-900.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Measured the migrated UI against a master build rendering the same fixture, and put back the metrics the framework upgrades had changed. app.css compatibility shims: - .btn-icon was 43px square in v2 and resolves to 30px in v5. This also set the height of the rows holding the mono hash fields, which is why the gap between a field and its label disappeared. - .chip was 0.75rem text with 0.375rem/0.75rem padding in v2. - preset-tonal-* now matches v2's variant-soft-*: the palette's 400 shade at 20% alpha with 700/200 text, instead of v5's solid 50/950 pairing, which made every chip read far brighter. - Default border-color follows a muted surface tone again. Tailwind 4 defaults it to currentColor, which rendered the dashed rules at full text contrast. Paginator now mirrors v2: arrows either side of an "x-y of n" readout in a flush pill, rather than one button per page. v5's btn-group adds gap and padding that v2 had not, so the group is styled directly. EvaluationInfo regains the branch caret, matches v2's 16px lead spacing, and no longer expands the evaluator node on first render. copyToClipboard prefers clipboard.write with a ClipboardItem, as the v2 action did. A bare writeText matches the ClickFix scam pattern and gets blocked by uBlock Origin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
unplugin-icons renders bare icons at 1.2em, so the jump-to-page arrow came out at 19px beside a 14px input, and its button sat 3px shorter than the box. The arrows now match the text they sit next to and the control stretches to a single height. The readout drops v2's range form: with one item per page "1-1 of 10" only ever repeated itself, so it now reads "1 of 10". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Sizing the input and its arrow button independently gave 34px and 31px against the paginator pill's 32: the input carries a 1px border from @tailwindcss/forms, and the previous items-stretch fix pulled the button up to the taller of the two rather than to the row. All three are 32 now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Declaring the v2 metric shims as raw CSS at the end of @layer utilities made them beat the app's own utilities: a chip carrying py-1 rendered at 28px instead of 24 because the shim's padding-block won. Declaring them with @Utility puts them where v2's component classes sat, so an explicit py-1, h-6 or w-10 wins again while un-overridden chips keep v2 sizing. Also clears the dead and misspelled classes the check surfaced: - bg-success-900 alongside bg-surface-50-950 in TocSource and AccurateRipChecksumSegment; the surface pairing already won, so this only removes the dead declaration - text-md is not a Tailwind class and resolved to the inherited 16px, which is what text-base gives - text-balanced -> text-balance, the real utility; this one does change wrapping in the two rip-info tooltips Includes the editor's rewrites to v4 shorthand, [&>*]: -> *: and rounded-[var(--x)] -> rounded-(--x); both compile to identical CSS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Tailwind 4 drops Preflight's `cursor: pointer` on buttons to match native browser behaviour, which left every button in the app feeling inert. A base rule puts it back for buttons, role=button and summary; disabled pagination arrows keep their not-allowed cursor. Copying now reports on the button rather than through a toast: a new CopyButton frag cross-fades the copy glyph to a checkmark for 1.5s. Both icons sit in one grid cell so the swap cannot shift layout, and the checkmark inherits currentColor so it matches the button. copyToClipboard returns a boolean for this and only toasts on failure, where there is nothing on the button left to show. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
It is a label for the hidden file input, and labels get no pointer by default; the underline already advertises it as the click target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Skeleton v5 pins a btn-icon's child svg to --btn-size, which overrode both the icon-* classes and the 1.2em the icons ship with: the help, theme and github glyphs rendered at 16px instead of 24. Sizing them from their own font-size again restores v2 behaviour for every icon button. The navbar's bottom-right rounding existed to clear the old scrollbar, which no longer overlaps it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Skeleton v2's .card carried an opaque surface background; v5's sets only border-radius. That left the grade-bar tooltips as a bare 20%-alpha panel sitting straight over the log table, so the text was unreadable. Blurring the backdrop matches what the Card tooltips already do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Skeleton's Svelte package is now one of several framework bindings over a shared Zag core; Bits UI is Svelte-only and idiomatic with runes. Both Skeleton packages are gone. Components: - Tooltip, Pagination, Accordion, Progress map onto Bits equivalents - AppBar contributed only data attributes, so header/footer are landmarks - Bits has no tree view; the evaluations tree is three fixed levels of expandable groups, which Collapsible models directly - Bits has no toast either, and the toaster backed a single message once showError turned out to be dead, so copy failure now reads on the button beside its tick and the toaster is gone Styling, which Bits does not supply: - cambia-theme.css moves from Skeleton's [data-theme] contract to a plain @theme block. Colour values are unchanged; the light/dark pairings are declared with light-dark() in @theme inline - app.css absorbs the base layer Skeleton's globals.css provided, the one that matters being color-scheme, which every pairing resolves against - preset-filled, card and placeholder were the only Skeleton utilities still referenced (7 usages); they are reproduced verbatim - The chip and btn-icon shims were overrides layered on Skeleton's definitions and carried only the metrics that differed. Standing alone they need the layout properties too, without which the disc-id chips wrapped their trailing icon onto a second line Verified against screenshots and computed metrics captured before the change: header, card, chip, table row, rule, grade border and both themes' backgrounds are unchanged, and the paginator is identical at 134.3x32. Stylesheet drops from 69KB to 49KB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Comparing against the v5 branch only proved the Bits swap changed nothing; it could not catch drift introduced earlier in the port. Built master and diffed against that instead, across home, help and the log views in both themes. Four real differences came back: - v2's tokens paired asymmetrically (100 light / 800 dark) where the v5 port only had symmetric pairs, shifting the entire dark palette a step darker. The pairs v2 used are declared directly now, so cards, mono fields, striped rows and the page background match again. - placeholder was surface-300 in v2, not the 200 v5 uses. - variant-filled sat on surface-900, not 950. - variant-soft used the 400 shade in light but 500 in dark; the shim had 400 for both, so every tonal chip was slightly light in dark mode. Three differences remain and are all deliberate: the pointer cursor on "click here", the copy button's tick, and the jump-to-page control at 32px rather than 34 to line up with the paginator. The page background also moves from an AppShell div onto <html>, which computes differently but paints the same colour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
The error screen centres with 'grid h-full place-items-center', but nothing in the chain had a definite height once AppShell was gone: the content wrapper sized itself with min-height, and a percentage height cannot resolve against that, so the grid collapsed to its content and sat under the header instead of centred. The page is now explicit rows -- header auto, content 1fr, footer auto. The 1fr row fills a short page, so h-full resolves and the error screen centres, and its min-content floor lets a long page expand past the viewport with the footer still last. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
The redirect to /logs was a standing condition: processedCount.subscribe replays its current value on subscribe and fires on every change, then decided routing from $fileListStore.length. That list outlives a client-side navigation, so any later notification while on / sent you straight back to /logs. The same shape set loading = true whenever a stale single file was around, with nothing running to clear it. Navigation for multiple logs now happens once, in inputChanged, next to the upload it belongs to. A `processing` store marks an upload as being in flight; the subscription ignores everything else, and the overlay is derived from it rather than from the file list. The subscription is also unsubscribed on teardown, which it never was. A failed connection never reaches the completion path, so it releases the flag itself; otherwise the overlay would cover the error page. Verified: one log goes to /log, three go to /logs, the logo returns to / and stays there with the file list still populated, and a reload stays put with no overlay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Scoped to the root route rather than every navigation: /logs reads the file list as its "is there a batch" signal, so clearing it there blanks the page, and clearing it on /log breaks going back to /logs from a row. An empty FileList rather than undefined, so bind:files clears the inputs themselves. The in-flight upload holds its own reference, so nothing depends on the selection surviving. processing is also scoped to the single-log case it actually gates, which otherwise stayed true for the lifetime of a batch. Verified with the sample logs: four logs give four rows on /logs, a row opens and browser-back returns to a populated /logs, and the logo clears the inputs and stays on /. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Tailwind only derives variants for utilities registered with @Utility. I had written the tonal presets as plain rules inside @layer utilities, so hover:preset-tonal generated nothing and every icon button lost its hover state once Skeleton -- which did register them -- was removed. Confirmed by swapping the previous app.css back in: the help, theme and github buttons stay transparent on hover with it, and tint correctly without it, in both themes. Audited the other 32 variant classes in the markup against the compiled stylesheet; hover:preset-tonal was the only one producing no CSS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Both halves of the control were already 32px tall and on the same baseline, but @tailwindcss/forms puts a 1px grey border on a bare number input. That inset the dark fill by a pixel top and bottom, so the field read as shorter than the arrow button beside it. The button has no border, so the field should not either: the two are meant to look like one pill split down the middle. Verified at 900px: input and button both 32px high at the same y, no border on either, identical background, in light and dark. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
DropScreen hides the page content on dragenter so only the drop target shows. It did that with `display: none`, which takes the content out of the grid entirely: the footer then auto-placed itself into the freed 1fr row and slid up to just under the header. That was visible because the overlay does not appear instantly. It sets duration-200 without a transition-property, so the initial value `all` applies and its background fades in over 200ms -- long enough to watch the footer move underneath it. AppShell used to wrap the content in a <main class="flex-auto">, which held the row open no matter what happened inside it. Removing AppShell dropped that element, and the landmark with it. It is back, and the content is hidden with `visibility` instead, so nothing reflows at all. Verified at 1200x800: on / and /help the footer stays at the same y through the whole drag, the overlay hit-tests on top at header, middle and footer, and h-full still resolves through <main>, which the error screen's centring needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
None of these came from Tailwind; they are TypeScript hygiene errors that predate the migration and only started being reported when the config moved from eslint-plugin-svelte3 to svelte-eslint-parser, which actually runs the TS rules over .svelte files. Dead code removed: getText in AccurateRipSummary, cubeGenerator's unused `cols` parameter, the CtdbSummary import whose only use is a commented out panel (the comment now says to bring the import back with it), the IconIncompleteCancel import, FileSystemIconLoader in the Vite config, and the view-model and page-plugin fields MultiLogView never reads. The two `any` casts become `object`, which is what castToType and castToTypeArray already take. app.d.ts moves its namespace into `declare global`, the shape SvelteKit templates use now. An unused catch binding becomes a bare `catch`. Three ternaries were being used as statements. TrackInfo's two become a `bucket` helper, which also drops a pair of non-null assertions; lightswitch's two become classList.toggle with an explicit force. That file is otherwise still byte-identical to the vendored Skeleton v2 copy. Verified: the disc geometry and AccurateRip grid render byte-identically before and after (same innerHTML hash from the same fixture, with the dev server proven to be picking up edits), and the theme toggle still flips .dark both ways and survives a reload. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Choosing files from /log sent you home instead of to the new logs.
/log resolved its id from an effect over responseStore, and treated a
miss as "go home". inputChanged calls initialiseResponseStore before
anything else, which clears hashIndexLookup and replaces the store, so
the id of the log you were looking at stopped resolving the moment you
dropped new files. That effect then fired and its goto('/') landed after
the goto('/logs') issued synchronously in inputChanged, so home won.
Master got away with the same effect because routing for a batch lived
in a processedCount subscription that fired again on every completion,
re-issuing goto('/logs') until it stuck. Removing that standing
condition -- which is what sent you back to /logs after a reload --
left nothing to paper over the race.
The redirect now runs in afterNavigate, so it answers "was this page
opened with an id nothing can resolve", which is the question it was
always meant to ask. Store churn only updates what is rendered.
Verified against the backend on 3031, seventeen checks: one log and
several logs from home, from /log and from /logs; a row click and
browser back; the logo home from both views with a reload after it;
cold /log?id=bogus and cold /logs; and a rejected file reaching a
centred /error. The suite fails on the parent commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
There was one workflow and it only ran when somebody pressed a button, so nothing was ever checked before it landed. ci.yml runs on every branch push and every pull request: npm ci, the svelte-check script, eslint, the static build, then clippy with warnings denied. rust-embed compiles web/build into the binary, so the backend job reuses the frontend's artifact rather than building the site twice. Binaries are built on pushes only -- which includes the push to the default branch that merging a pull request creates -- so a commit is never built twice on its way through review. release.yml is the only thing that touches Releases, and only a version tag starts it. It refuses to publish when the tag and the Cargo.toml version disagree, since a release that is not a version bump is a mistake. Its workflow_dispatch input exists because Actions runs a workflow as it exists at the ref being built, so a tag older than this file can never trigger the push event; dispatching it from a branch that has the file covers that. Left out of the gates: prettier differs from 105 files here and cargo fmt from most of the Rust, so both would land red. Reformatting either is a commit of its own. Verified locally: npm ci is in sync with the lockfile, npm run check and eslint are clean, and clippy passes with -D warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
The first run warned that checkout, setup-node and upload-artifact all target Node 20 and were being forced onto Node 24. That is a removal notice, not a style note, so the actions go to the majors that declare node24 themselves: checkout v7, setup-node v7, upload-artifact v7, download-artifact v8. Every input in use still exists in those, checked against each action's own manifest. rust-cache v2 already runs on node24 and stays where it is. ubuntu-latest becomes ubuntu-24.04. The runner label migrates to Ubuntu 26 in October, and for the release binaries that is a real change rather than a cosmetic one: glibc comes from the build image, so letting the label drift silently raises the floor on what the published Linux binary will run on. Windows and macOS stay on their latest labels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
The frontend at 0.1.0 does not build on Node 22 or 24: Tailwind loads tailwind.config.ts through jiti, whose sucrase interop hands back an undefined binding for the Skeleton plugin, and postcss dies with "Cannot read properties of undefined (reading 'call')". It builds cleanly on Node 20. Nothing is wrong with that tree -- the workflow it was written against said Node 21, and the pin here says 22. Tag pushes are unaffected and still get 22. The dispatch path already exists for tags older than this file; the Node they were written against is part of what is older. Verified on the same worktree at bef0975 with its own lockfile: builds on 20.20.2, fails identically on 22.21.0 and 24.15.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Brings over 0.1.1: the drive-offset work from #9 and its follow-up fixes in #17, the release workflow, the codegen crate, the clippy sweep and the regenerated tables. One conflict, in release.yml, where both sides had added the node-version input. Resolved to a default of 22, which is what master pinned to 20 until this branch landed -- the frontend here is the one that builds on it. Older tags still need 20 passed explicitly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Brings in the six experimental ripper parsers, cambia-core 0.1.1, the Shuttle config removal and the codegen lint fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Inter shipped 18 static faces across nine weights and both styles. Only six weights were ever referenced and no italic was, so twelve of the faces were unreachable, and rust-embed baked all 5.2 MB into the binary. Geist replaces them with five subsetted variable faces spanning 100-900, so the three weights that previously had no file are now available too. Greek is the one subset Inter had that Geist does not; Cyrillic, which actually turns up in rip logs, is covered. Also ships OFL.txt. Inter was OFL-1.1 as well and the licence had never been distributed alongside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
Lists the six experimental rippers, and names the two TOC ID specs that were already being generated but never mentioned, AccurateRip and freedb. Platforms become a list because several of the new entries run on more than one, so a row can carry an icon per platform. The footer link follows the repository to its new home. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDLpm8ZfJRx9rqNfSbNYxe
The parser was written against a single 10.3.0 log, and a 6.0.0 one came out with no tracks at all. 6.0.0 splits the summary table's error count into read, data and sync columns, so the row pattern now takes one or more counts. Its firmware revision is "F611" rather than "2.00", so any four-character trailing token is dropped from the drive name, not only a dotted one. Normalization was read from "Calculate ReplayGain", which only tags. "Apply ReplayGain" is what alters the audio. 10.3.0 does not log it, but it does log Calculate, and gain that was never calculated cannot have been applied, so a disabled Calculate still reads as no normalization. Checked against both logs: the 10.3.0 output is unchanged, and 6.0.0 now gives 10 tracks with matching CRCs and the drive at offset 6. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cyanrip, dBpoweramp, EZ CD, fre:ac, morituri and Rip now read like the EAC, XLD and whipper parsers: the boolean_matcher, string_match and optional_match helpers over named groups, nested crate imports, private Single and Track structs with a Translator impl, new_no_skipzero for a lone copy CRC, and no matching on bools or doc comments. Code they each carried a copy of is shared. split_tracks slices a log into per-track sections, and Time gains from_mm_ss_ff and from_frames for the MM:SS.FF and raw frame counts several of these logs use. Two dBpoweramp regexes nothing referenced are gone, as is a Rip branch that returned the same status as the one after it. Output is byte-identical on all 11 test logs these parsers handle. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
low32 counted digits after stripping leading zeros, so 0199f19e came out as 199F19E and a zero checksum as 0. Parse the value and print the low 32 bits as eight hex digits instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Migrated with sv migrate: the config moves from svelte.config.js into the sveltekit() plugin in vite.config.ts, $lib imports become #lib subpath imports, $app/environment becomes $app/env, and TypeScript goes to 6. The migration also ran prettier over src. Two follow-ups the migration left broken: - env.publicPrefix no longer exists in SvelteKit 3 and nothing read a CAMBIA_ variable, so it is gone. - TypeScript 6 rejects ws.send() on a value annotated as plain Uint8Array; the inferred Uint8Array<ArrayBuffer> is accepted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Moves the web UI to Svelte 5 runes and SvelteKit 3, replaces Skeleton with Bits UI and an owned styling layer, and brings in the parser work that landed on this branch along the way.
Frontend
sv migratecodemods. Config now lives in thesveltekit()plugin invite.config.ts,$libimports are#libsubpath imports, and$app/environmentis$app/env.Parsers
parser::split_tracksandutil::Timehelpers. Output is byte-identical on the 11 test logs.0199F19E, not199F19E).CI
Gates run on every push, releases are cut from tags, and the workflows are off the deprecated action runtime.
Verification
svelte-check: 0 errors.vite buildsucceeds./logand/logs) behaves identically before and after the SvelteKit 3 upgrade.Known, not regressions
Both reproduce on the build from before the SvelteKit 3 upgrade:
/logsto home and uploading again ends on "Connection to the API failed."/logor/logsdirectly does not redirect home, though both pages try to.The SvelteKit 3 migration also ran prettier over the ts-rs output in
src/lib/types; regenerating those files will flip their formatting back unless the directory goes in.prettierignore.🤖 Generated with Claude Code