Repository navigation
refactor: cut main.js by 1,739 gzip bytes without behavior change - #501
Merged
Merged
Conversation
A promise settles once, so the settled flag and the aborted re-checks were dead: the entry assertActive rules out an earlier abort, and the abort listener runs before any later settle. gzip 102900 -> 102866 (-34)
… constants As instance fields their long names stayed in the bundle at every use; as constants the minifier inlines the numbers. gzip 102866 -> 102793 (-73)
None of them reads instance state. As private methods their names stayed in the bundle at every this.<name> call; as functions the minifier shortens or inlines them. FeedDocumentParser keeps parseEpisodes, its one public method. gzip 102793 -> 102710 (-83)
savedProgress, resumeTime and startDuration read only the played-episodes store, not the controller. As functions the minifier shortens their names at each call. gzip 102710 -> 102675 (-35)
Seven checks spelled out the two statuses that isPlaying already names. gzip 102675 -> 102658 (-17)
gzip 102658 -> 102653 (-5)
getEpisodeAudioBuffer reaches readVaultAudio and getAudioContainerHint only after it has checked that the episode, and a reused download, are audio, so the hint branches never saw another value. gzip 102653 -> 102626 (-27)
gzip 102626 -> 102612 (-14)
Every update replaces the notice body. gzip 102612 -> 102606 (-6)
It reads no service state. gzip 102606 -> 102590 (-16)
A missing mask is a full-byte mask, so one comparison covers both kinds of signature. gzip 102590 -> 102571 (-19)
Both requests go to the same host, so fetchShows takes the endpoint and its query parameters instead of each caller appending them one by one. gzip 102571 -> 102537 (-34)
The settings codec clamps defaultVolume to a finite 0 to 1 when it reads data.json or an import, and setVolume is the only other writer and clamps too, so the constructor's own clamp never changed the value. gzip 102537 -> 102523 (-14)
Ten reads of this.lifetimeAbortController.signal, a property chain the minifier cannot shorten, become this.signal. gzip 102523 -> 102512 (-11)
stop() runs once because stopTime is set once, clearTimeout ignores an absent id, and one render() replaces two one-caller helpers. gzip 102512 -> 102499 (-13)
gzip 102843 -> 102814 (-29)
gzip 102814 -> 102762 (-52)
PlaylistsPage was the only component without a rune, so Svelte compiled it in legacy mode, and the legacy-mode flag kept every legacy branch of the Svelte runtime in main.js. gzip 102762 -> 102246 (-516)
gzip 102246 -> 102222 (-24)
A class={expression} attribute makes Svelte resolve it with clsx, for class arrays and objects. The back button's two classes become class directives, and the mini player's progress bar builds its class from a suffix, so clsx leaves main.js.
gzip 102222 -> 102085 (-137)
The handler already refuses a negative start time before it parses the segment end, so parseSegmentEndTime never saw one. gzip 102900 -> 102889 (-11)
The five collection decoders each repeated the same object guard, entry loop, skip warning and path building. decodeRecord owns that shape; each decoder keeps only its per-entry fields. Warning text is unchanged. gzip 102889 -> 102819 (-70)
…s empty Every decodePlaylist fallback is a built-in or new playlist with no episodes, so the fallback episode decoding and clonePlaylist only ever produced an empty list. The fallback is now the playlist's settings, and the unused default parameters are gone. gzip 102819 -> 102775 (-44)
The description, content and chapters tags each split, prefixed and joined their lines by hand. joinLines does it once. gzip 102775 -> 102734 (-41)
NoteTemplateEngine repeated the date, currentdate and episodenumber tags of the file-name engines, and the transcript engine repeated its duration tag. Both now share addEpisodeDateTags and durationTag, in the same tag order. gzip 102734 -> 102729 (-5)
time, linktime, segment and linksegment differed only in linkify and whether a segment applies. gzip 102729 -> 102721 (-8)
… notes createFeedNote carried its own copy of the folder creation and the racing-create recovery. Both note creators now use the utility, and the feed note keeps its re-check before it creates. gzip 102721 -> 102688 (-33)
snapshotOptions and the two response snapshots each spelled out one Reflect.get per field. readFields reads the same fields in the same order, once each. gzip 102688 -> 102638 (-50)
The playback rate, episode list limit and skip lengths each checked for a normalized value in their own way. decodeNormalized applies the one rule: warn when a stored value differs from its normalized number. Warning order is unchanged. gzip 102638 -> 102598 (-40)
A piece containing a dot already fails the hextet pattern, and both branches build exactly eight hextets once the length checks pass. gzip 102598 -> 102588 (-10)
The available and placeholder branches built the same entry twice. gzip 102542 -> 102530 (-12)
bind wrapped subscribe, apply and shouldPersist in lambdas that only forwarded their arguments to erase the value type. A single cast erases it. gzip 102530 -> 102512 (-18)
new RegExp(literal) copied the literal on every call. gzip 102512 -> 102498 (-14)
A lookup table and a shared regex only ever mapped ( and ) to %28 and %29. gzip 102498 -> 102474 (-24)
findUniqueTitleMatch kept each candidate's best score but only ever asked whether exactly one candidate reached the threshold. gzip 102474 -> 102444 (-30)
formatSeconds already renders a negative or non-finite time as zero. gzip 102444 -> 102438 (-6)
…y make A null file is not a TFile, and undefined chapters are not an array. gzip 102438 -> 102430 (-8)
truncateToLimit already returns a name that fits unchanged, and the platform limit differs only in its mode. gzip 102430 -> 102408 (-22)
U+200B through U+200F are contiguous, so five equality checks are one range check. gzip 102408 -> 102400 (-8)
Each refused domain matched itself and its subdomains with its own pair of checks. gzip 102400 -> 102392 (-8)
The rename handler kept its own copy of mapRecord. Both stores it writes hold null-prototype records either way. gzip 102392 -> 102358 (-34)
…headers Three spelled-out plain-object checks and two null-prototype records matched the shared helpers exactly. gzip 102358 -> 102338 (-20)
Each entry spelled out pattern and extension keys around the same regex and extension. gzip 102338 -> 102306 (-32)
The queue, favorites and local files stores restated the names, icons and flags that constants.ts already defines for those playlists. gzip 102306 -> 102281 (-25)
getUrlExtension only returns letters and digits, so escaping the template extension before stripping it changed nothing. gzip 102281 -> 102256 (-25)
The decoded copy already holds a valid mediaType or isVirtual, so assigning it again changed nothing. Invalid and missing values are still removed, with the same warnings. gzip 102256 -> 102233 (-23)
The error constructor, accepted statuses and response snapshot each spelled out the same 100 to 599 safe-integer check. gzip 102233 -> 102223 (-10)
videoStage returned an action's { destroy } that the template adapted for {@Attach}. It now returns its teardown, so Player attaches it directly and the doc comment names what it is.
gzip 101161 -> 101145 (-16)
Deploying podnotes with
|
| Latest commit: |
1c72ce1
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a621cad8.podnotes.pages.dev |
| Branch Preview URL: | https://refactor-size-headroom.podnotes.pages.dev |
This was referenced Oct 15, 2026
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.
Summary
main.jswas 102,916 gzip bytes on master, against the 103,000 budget. That left too little room for the bug fixes in flight. This PR cuts the bundle to 101,173 bytes (-1,743) with refactors that keep behavior the same. Headroom grows from 84 to 1,827 bytes. The budget does not change.PlaylistsPage) kept Svelte's legacy runtime in the bundle. The onlyuse:action and the only twoclass={expr}attributes pulled in the action runtime andclsx.The evidence archive has the per-commit table with the reason each number moved, the rejected candidates, the V-501 fixes, the bundle analysis, logs, contact sheets, and the scripts to rerun every measurement. Each commit was built in order, and the deltas sum to the total. The numbers in the commit bodies come from each explorer's own branch and do not chain.
Behavior that stays the same: user-facing and warning strings,
data.json,src/API,obsidian://podnotesURIs, the DOM, class names, and accessible names. The ES2020 floor and the tagged-template rule hold. The files that other open units change are untouched.Three candidates were dropped because they changed behavior. TimerNotice would show 00:00:00 instead of a negative time after a clock step. The independent verification V-501 found the other two, which are reverted here with regression tests.
getPodcastSegmentFormattedlost the numeric strings JavaScript workflows pass, and saving dropped a validmediaTypeorisVirtualthat an object inherits or hides from enumeration.Verification
Node 22.23.2, Obsidian 1.14.4 on Linux.
npm run lint,format:check,typecheck,build,test(126 files, 2,766 tests, both Svelte checks with 0 errors and 0 warnings),test:tooling(5 files, 35 tests), anddocs:build(18 pages) pass.npm run sizereports 101,173 gzip and 313,325 raw. Five builds gave identical bytes.00:01:55-00:02:05for numeric-string times.npm run test:e2epassed on a fresh vault, 32 files and 234 tests. After the reverts, the ten e2e files that cover capture, playback, local files and saving passed on a fresh vault, 91 tests.videoStageis now a real attachment. V-501 then found the two changes above.Release impact
None. No settings, storage, API, URI, or migration changes. No docs changes at release.
Note
Medium Risk
Most changes are mechanical DRY refactors, but persistence decode/encode paths, network option validation, and player initialization were touched alongside noted edge-case behavior shifts (e.g. invalid playlist data, default volume clamping).
Overview
This PR shrinks
main.jsby ~1.7k gzip through refactors that mostly preserve behavior, while fixing two regressions caught in review.Bundle / UI compile path:
PlaylistsPageopts into Svelte runes so the legacy runtime can drop out of the bundle. The video stage moves from ause:action to{@attach player.videoStage}, which now returns a teardown function instead of{ destroy() }. A few components switch to rune-style patterns (class:,$effectcleanup in the queue drag handler, sharedfollowResizesfor layout observers).Shared helpers replace copy-paste: Vault note creation uses a single
createFileIfNotExists(feed and episode notes). Template engines gainjoinLines,addEpisodeDateTags,durationTag, and a unifiedtimeTagfor timestamp templates. Persistence decoders sharedecodeRecord/decodeNormalized; queue/favorites/local files pull defaults from exportedPlaylistSettingsconstants.Structural refactors:
FeedDocumentParserbecomes module functions;PlayerControllerandTranscriptionServicehoist instance-free logic to module scope; URI handling drops redundant segment start checks and reusesurlCandidates.Explicit fixes (not pure size cuts): Tests lock in
getPodcastSegmentFormattedaccepting numeric strings from JS workflows, and persistence encode keeping inherited/hiddenmediaType/isVirtualon episodes and playlists.Reviewed by Cursor Bugbot for commit 1c72ce1. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Refactor PodNotes utilities and services to cut
main.jsby 1,771 gzip bytescreateFileIfNotExistsfor vault file creation (createFileIfNotExists.ts),decodeRecordfor collection decoding (collectionCodecs.ts),decodeNormalizedfor settings normalization (settingsCodec.ts),joinLinesanddurationTagin template engines (TemplateEngine.ts), andfollowResizesfor resize observers (layout.ts)use:actions to teardown callbacks to avoid keeping legacy runtime in the main bundlePlayerControllerno longer clamps the initial snapshotdefaultVolumeto 0–1; network request/response headers and options are rejected unless they are plain objects; playlist and settings decoding returns empty episode lists instead of restoring fallback episodes when persisted values are invalid;downloadFileresults no longer exposebyteLength; notice updates clear the notice body before rendering;detectAudioFileExtensiontreats missing mask bytes as 0xffMacroscope summarized 1c72ce1.