Repository navigation
feat(ui): rebuild searchable settings and add headset mappings - #468
Merged
Merged
Conversation
Each SPEC 9 section is a native settings group, and every row is its own render definition, so Obsidian's settings search finds each row by name. The 27-entry alias list and the single "Podcast preferences" wrapper go. - Remove Default volume (the player remembers volume) and the feed cache rows (Cache podcast feeds, Cache duration, Clear cache). - Text fields write data.json once per completed edit instead of once per keystroke. Typing changes only the field; the edit applies on change, when the row or tab closes, or when its window hides or unloads. A field with no edit never writes, so it cannot undo a synced or imported value. - Default speed is a dropdown of the player's speed menu. A saved speed it does not offer is shown as an extra option and is not rewritten. Rates keep hundredths so 1.25 and 1.75 survive a reload. - The auto-queue toggle is "Queue episodes I switch away from" and follows changes from the Queue page while Settings is open. - Diarization rows stay listed and searchable, disabled while they do not apply. - Path previews and the download warning use the row's description and native error message. Restating descriptions are removed. - Plain-word import notice and OPML progress, and no long dashes in the settings migration docstrings.
Feeds are cached for six hours whatever the saved preference said, and Refresh still fetches at once. Users who turned caching off get it back. feedCache leaves the settings type, defaults, codec and settings transfer. A saved feedCache, including enabled:false, decodes without a warning or a startup rewrite, is not kept as an unknown field, and the next save drops it, also when an older device syncs it back. Releases up to 2.21.1 read the missing field as caching on for six hours. Clear cache had no caller left, so FeedRepository.clear goes too.
Settings has a Headset and lock screen section with Previous track (Capture timestamp, Back by the skip length, Previous chapter) and Next track (Forward by the skip length, Next in queue, Next chapter, Capture timestamp). Each press reads the mapping, so a change made in Settings or synced in applies at once. The defaults keep today's buttons, except that next track now skips by the skip-forward length (15 s by default) through the API's skip, which stops short of the end and never goes back, instead of a fixed 30 s. Next in queue plays the queue's first episode. With auto-queue on, the episode switched away from goes to the end of the queue, so repeated presses go through it instead of switching between two. Chapter actions load the episode's chapters once per URL, ask again after a failed or empty load, and move nothing when the episode changed meanwhile. The two settings are validated where data.json and imports are read; old data without them gets the defaults with no rewrite.
Obsidian dims only disabled action rows, so a disabled diarization dropdown or key button looked usable. The timestamp template field sits under its name at full width, as on a phone it otherwise stopped short of the other fields.
… mapping - Settings search finds a row of each section by name, a diarization row while diarization is off, and none of the removed rows. - Typing 22 characters into Episode note path writes data.json nothing until Tab completes the edit, then once. An edit still in its field is saved when Settings closes. - The queue toggle follows the Queue page menu while Settings is open. - A saved feedCache enabled:false still caches feeds, and the next save drops it. - Previous track follows a remap in Settings with no reload, and next track defaults to the skip-forward length. - A restarted player starts at the volume saved last.
Each completion test now reads data.json before any other path could complete the edit, and the six-hour boundary no longer waits on a fetch it does not expect, so a planted defect fails on its assertion instead of a timeout.
Deploying podnotes with
|
| Latest commit: |
17b5bca
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b00adc3b.podnotes.pages.dev |
| Branch Preview URL: | https://feat-ui-settings.podnotes.pages.dev |
This was referenced Oct 14, 2026
chhoumann
added a commit
that referenced
this pull request
Oct 14, 2026
… of the full player (#472) * refactor(feeds): drop the ignored FeedRepository cache option Feeds are always cached since #468. The constructor still accepted a cache option that nothing read, and eight view tests still passed it. * refactor(player): move Next in queue into PlayerController.playNext The headset's Next in queue read the queue store and called play inline in bindMediaSession. PlayerController now owns it, beside play and the natural end's queue advance. * fix(settings): say an import that could not save yet saves by itself Since #470, PodNotes saves on its own once it can write data.json again. The Notice after an import whose final save failed still told the user to change a setting to save again. * refactor(ui): co-locate each component's styles and split Deck and Panel out of Player PodcastView.svelte held 1,300 lines of page and component CSS in one global block. Each component now carries its own styles, anchored on its own root class with the `.root :global {}` pattern the shell already used. The shell keeps its tokens, its layout, the Obsidian DOM rules and the classes several components share. Player.svelte loses the deck (title, scrubber, transport, capture bar) to Deck.svelte, and ChaptersPanel and UpNextPanel share their chrome through Panel.svelte. Cover draws a local file's tile, and SearchField takes the first run's large variant, so no page reaches into them for it. Variants of the shared .pn-iconbtn and .pn-btn classes win by specificity instead of by stylesheet order. * test(player): format the playNext tests * feat(ui): move between the wide player's tabs with arrow keys The Chapters and Up next tabs follow the ARIA tabs pattern: Left and Right move focus to the previous or next tab and wrap, Home and End go to the first and last, and the focused tab shows its panel. Only the selected tab is in the Tab order. * fix(ui): hide the fullscreen button in fullscreen and let play respect reduced motion In fullscreen the video frame's fullscreen button stayed visible and did nothing. It now hides there; Escape leaves fullscreen. The play button's 120 ms press transition ran even when the system asks for reduced motion. It now presses without a transition then. * refactor(verify): split screens.mjs by concern screens.mjs grew past 1,100 lines. It keeps its usage header, the verb dispatch and set; the instance calls, the seeded scenes and the contact sheet move to screens/instance.mjs, screens/scenes.mjs and screens/sheet.mjs. The code moved verbatim apart from imports, exports and the paths that moved one directory down. * refactor(ui): keep each button's touch cascade and move queue rows into EpisodeRow On a touch device Obsidian adds .mobile-tap to a tapped button, and its button:not(.clickable-icon).mobile-tap rule paints a fill. Search results, playlist rows, chapter rows and Up next's Edit button keep the specificity that resisted it, and the mini player's open button and the rail's add button keep the plain scoped rules that let it through. Queue rows are an EpisodeRow variant (queue, dragging) instead of rules QueueList laid over EpisodeRow and Cover. Deck no longer takes `wide`: the player passes no onchapters when the chapter name cannot open a panel. .pn-alt moves whole into AddPodcastPage, and the shell's head and back button rules move from the shared block to its layout.
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
Rebuild Settings into seven native searchable sections: Playback, Headset and lock screen, Notes, Transcripts, Downloads, Library, and Import and export. A 22-character path edit now writes
data.jsononce after a 500 ms typing pause, or immediately on completion, down from 22 writes.Each section returns native
SettingDefinitionGroupitems, with one namedSettingDefinitionRenderper row. Obsidian indexes those rows directly. This deletes the synthetic alias list. The headset rows retain topical aliases because Obsidian does not index group headings. Custom renderers keep writes on the existing persistence writer; nativecontrolsetters callsaveDataper keystroke.Existing controls that SPEC 9 did not name explicitly remain:
Search podcasts and Playlists had already moved out of Settings on the base branch. The duplicated result-card tooltip and missing artwork alt text belonged to that removed search UI.
Verification
Node 22.23.2. Commands use
TMPDIR=/data/home/tmp/podnotes-ui3 fnm exec --using 22 -- ....npm run lint,npm run format:check,npm run typecheck,npm run build,npm run test,npm run test:tooling,npm run docs:build, andnpm run sizeall passed. Unit suite after merging #466: 121 files / 2,525 tests. Tooling: 2 files / 13 tests. Docs: 18 pages; all 112 legacy fragments resolve.Real Obsidian 1.14.4 on headless Linux, isolated vault
podnotes-feat-ui-settings, linked to this checkout's build. Final fresh-vault full suite after merging #466: 22 files and 166 tests passed in 577.39 seconds on17b5bca50ec18c8a8ee82e607804dbb61cce5e03. Capture Undo/refusal and player journeys pass alongside the Settings and focused-edit regression tests. The isolated instance was stopped and the final dry run found no processes. Runtime coverage includes search in every section, focused text edits across whole-app quit/restart and CLI renderer reload, text-edit writes and completion, headset capture and remapping, cache retirement, volume across restart/import, and the existing journeys. Fixtures includehttp://podnotes.test:<fixture-port>feeds andFixtures/Local Fixture.wav.The write recorder targets PodNotes' exact
data.json. Before: 22 completed writes for 22 typed characters. After: one write for the 22-character pathV468/My podcastnote.mdafter the debounce, with zero extra writes on Tab. Memory already held the full draft while disk still held the old value. The normal-session sequence remains four writes, with identical counts after every action. Recorder integrity and plugin identity remain intact, with zero failed writes.The V-468 focused-edit failure is reproduced by two real E2E tests at c0a6d3d. Both retain
Initial/{{title}}.mdinstead of the focused edit after a 750 ms pause. Both pass after this correction. A further regression proves that reverting a saved field to its original text still saves immediately on blur. Focused-edit correction report and rerunnable evidence.Twenty-four unit mutations and two runtime mutations failed on their named assertions. Eight refreshed/new mutation runs also caught their planted defects after review. Review regressions were reproduced in Obsidian before fixing them: import-lock bypass on a store emission, a current episode at the queue head, delayed chapters after switching away and back, and unavailable Previous chapter seeking to zero.
Whole-tab before/after comparison, desktop and phone 390, light and dark. These are native viewport slices; fixed headers repeat and the final slice overlaps.
Verification report | Review resolutions and dispatch ledger | Raw evidence and rerunnable probes
Bundle size
Production
main.js, gzip level 9, measured after each step withnpm run buildandnpm run size.The mapping initially added 592 gzip bytes after the cache cleanup. The complete unit remains net-negative and below the 100,000-byte gate. Compared with the new master
35461cd0rebuilt using the same dependencies and Node 22, UI3 removes 5,373 raw bytes and 744 gzip bytes (master: 316,148 raw / 98,927 gzip). The final bundle leaves 1,817 gzip bytes under the gate.Release impact
feedCache.enabledandfeedCache.ttlHoursare retired. Old data decodes without warnings, no startup rewrite occurs, and the next normal save drops the retired key. Tests cover oldenabled:falsedata, #388 three-way merge preservation, settings import/export, and a frozen 2.21.1-shaped reader falling back to caching on with a six-hour TTL. All other settings and library data remain preserved.Two additive headset fields default to previous-track capture and next-track forward skip. Missing fields in old data get those defaults. Next track now follows the configured forward skip length (15 seconds by default), replacing its old fixed 30 seconds.
defaultVolumekeeps its stored shape and means the player's last volume. No API or URI changes.Each keystroke updates settings in memory. The normal writer persists after 500 ms without another input, or immediately on blur, Enter in a single-line field, Tab, closing Settings, or closing its popout. Enter in a template remains a newline. The main-window timer follows QuickAdd 0142db3d. Completion never replays stale text over a later synced value.
Quitting or reloading inside the debounce window can still lose the unsaved typing burst. This is the accepted residual. The quit hooks and write-refusal protections are unchanged; the verifier's experimental quit-task patch was not adopted. Background visibility is covered by a unit test, not a real mobile suspension test.
Integration and follow-ups
Merge head:
17b5bca50ec18c8a8ee82e607804dbb61cce5e03. Integration report and logs, merge patches, and rerunnable checks.#466 landed as
35461cd0. This PR merges that master without rebasing, with parents23217bb1and35461cd0. The player's rate menu now importsPLAYBACK_RATESfromsrc/utility/playbackRate.ts, as Default speed already does:0.75, 1, 1.25, 1.5, 1.75, 2. Its inline list is deleted. A saved speed outside that list still appears as an extra Settings option without changing data.json on open.CaptureService keeps #466's snapshot-equality Undo, success notice, refusal wording, and disposal exactly. The only difference from #466 is UI3's existing two-line diff in the missing-template notice, one deletion and one insertion:
Capture timestamp formatbecomesTimestamp template, matching the renamed Settings row. The file otherwise matches #466 byte-for-byte.The four merge conflicts preserve #466's verification instructions and hundredths normalization while retaining UI3's new Settings labels and constant. The keyboard helper keeps #466's Space handling and UI3's Tab handling. The complete isolated E2E suite passed (166 tests). It was run in full because the merge also touches the shared harness and runtime tests.
UI4 follow-ups: give queue advancement a controller action, and remove FeedRepository's ignored optional
cacheconstructor member when the frozen view tests stop supplying it. Cache policy does not read that member. These seams avoid concurrent edits to UI2's files.Docs at release
docs/scripts/screenshots.mjs.No published docs content is changed before release.
Note
Rebuild settings tab with grouped rows, debounced saves, and headset mappings
PodNotesSettingsTab.getSettingDefinitionsin PodNotesSettingsTab.ts as grouped sections (playback, headset, notes, transcripts, downloads, library, import/export) with shared row renderers for text, dropdown, secret, and conditional controls. Text and textarea settings now save drafts after a 500 ms typing pause and flush on change, blur, pagehide, or tab hide.headsetPreviousTrack,headsetNextTrack) in constants.ts and Headset.ts. The media session in mediaSession.ts reads mappings at press time and supports chapter navigation and next-in-queue playback, with stale chapter requests rejected on episode switch.feedCacheis retired in podNotesData.ts and settingsCodec.ts, andFeedRepositorynow always caches with a fixed six-hour TTL in FeedRepository.ts.SettingandPluginSettingTabwith a structured settings-definition model including cleanup, disabled-state propagation, and inline errors, in obsidian.ts.FeedRepository.clearis removed and the constructor's cache property is ignored; stale persistedfeedCachevalues are dropped on next save. Settings labels are renamed (Default Playback Rate→Default speed,Episode notes→Notes), which breaks any flow targeting the old names. DefaultheadsetPreviousTrackiscaptureTimestampandheadsetNextTrackisskipForward; invalid persisted values fall back to defaults with warnings.Macroscope summarized 17b5bca.