Repository navigation
feat(ui): add the Queue, Playlists, Local files and list pages, with offline mode - #459
Merged
Merged
Conversation
Desktop PodNotes no longer opens its view on every start that finds no PodNotes leaf. On desktop and mobile alike, the view opens once, when PodNotes starts with no data.json, and later starts keep the layout the user left. The layout-ready retry goes with the desktop branch: workspace.onLayoutReady only calls back once the layout is ready. J-ONB-01 now starts from a real first enable on desktop, and a new journey checks that a view closed after it stays closed across a restart until the ribbon icon opens it.
…dal and playlist manager The queue reorders by placing one entry before or after another, found by key when the move applies, so a queue that changed since cannot move the wrong episode. The Reorder Queue command opens the Queue page. The queue's menu move items, the reorder modal, the settings playlist manager and its icon picker are gone; the Playlists page lists playlists as rows. A shared text prompt replaces the feed address modal.
… pages Latest episodes, Played, a playlist and Local files render through one list page with no filter bar; search, Hide played and Refresh episodes stay on the podcast page. Cover rows name their source on the same line in the pane and on a line of its own in the sidebar and on a phone. A vault file shows a file tile, its folder, and the length its Played record learned. The head shows the queue's time left and a playlist's count, and Latest gets Refresh all feeds.
…line states screens.mjs gains the queue, playlists, playlist, latest, played, local-files and offline states. Each opens its page by page JSON, so the same states capture master now and the rebuilt list pages later. Scenes are now ingredient lists. lists adds Favorites, the prototype's playlists and a restored, paused episode; local writes four silent media files of the prototype's lengths into vault folders and registers them as playLocalFile does; offline adds downloads pointing at real vault files. offline on|off puts the renderer offline through CDP and stops the fixture server, because requestUrl runs in Obsidian's main process, which CDP does not reach. It fails unless navigator.onLine, the window event and a saved feed request all agree. The fixture server lets artwork be cached, so covers still show offline. shot hides Obsidian's status bar, which covered the view's bottom-right corner, and refuses to write a shot whose theme is not the one requested.
…tatus bar The reload journey opens a note with nine properties in the main area, so the core Properties plugin shows "9 properties" in Obsidian's status bar, which floats over the window's bottom-right corner. Before the mini player's first click and again after the reload, it checks that the bar spans the play/pause control, that the control ends at or above the bar's top, and that the control is what its center shows. screens.mjs gains a status-bar state: the queue with the mini player and a "9 properties" item in the status bar, which its shots keep and check lie inside the capture. It has no phone shot, because Obsidian hides the status bar on mobile. Refs #453
Obsidian's status bar floats over the window's bottom edge, so with a status-bar item such as "9 properties" it covered the mini player's play/pause and capture controls whenever the view sat at the window's bottom: the main pane, or the right sidebar. The shell measures how far the bar reaches up into the view (statusBarInset, from the two rects) and keeps it current with a ResizeObserver on the view and the bar, which resizes as items come and go. It follows the view to a popout, which has no status bar, and sets the result as --pn-status-bar on .podcast-view. The mini player grows by it as bottom padding, so its background still reaches the pane's edge and its controls sit above the bar in the compact and wide layouts. With nothing loaded, the page scroller's bottom padding includes it, and so does the full player's scroller, so their last rows can scroll clear. Mobile hides the bar, so its inset is 0 and nothing changes there. size: 99686 gzip (+274) Fixes #453
The Queue page lists the queue in its order. Each row has a grip that a pointer drags and Up and Down move, placing the episode beside the row it crosses, and a button that removes it. The head shows the queue's time left and a menu with the auto-queue preference, written through the plugin's one writer, and Clear queue, which offers Undo while the queue stays empty.
The Playlists page lists Favorites with a heart, then the user's playlists with their count and total length, then New playlist, which asks for a name and keeps the rules the settings manager had. Add to playlist always opens, and ends in New playlist, which creates a playlist holding the episode. A playlist's menu, from its row or its page's head, deletes it and offers Undo.
The Local files page's head has a plus button that opens the vault media picker. A vault file's row shows its file tile, its folder and the length its Played record learned; a PodNotes download listed there stays its podcast's row.
A public podcast's menu copies its feed URL, and a public feed that failed to load offers Edit feed URL. One feed address prompt serves public and private feeds: a public feed keeps its key and place, refuses an address another podcast has, and keeps a pasted private address as a secret. Remove podcast offers Undo, which puts the exact record back at its place, with a private feed's address.
One online signal, from the window's online and offline events, reaches the view, the podcast search and the transcribe commands. Offline, an Offline strip heads every page; Latest, Played, playlists and the podcast page list what plays offline first and grey what needs a connection; a tap on one says Not downloaded before the player can queue the episode playing. Refresh, Retry, search, setting a feed address and transcribing wait for the connection.
… Edit feed URL A feed address edit captured the saved record before the prompt and compared only its URL and secret reference after the address loaded, so removing the podcast and following it again at its old address let the stale edit land on the new subscription. It also checked the connection only before the prompt. Now the edit goes ahead only while the record under the key is the object it started from and the device is online, checked after the prompt and after the address loads.
A drag placed the episode by comparing only the pointer's height with each row's band, so dragging sideways into the editor and then down still reordered the queue, and so did a row scrolled out of sight. The row is now the one the browser hit-tests at the pointer, so anywhere else places nothing.
…n listeners The test compared only event names, so removing another function than the one added still passed. It now captures the listeners the drag added and requires those same ones removed on unmount.
… online Offline, the shell's feed check fails every feed due for a refresh, so the Library's cards wore the failed badge and the podcast page showed "Feed didn't load" under the Offline strip. While offline they no longer show it, and the shell checks the feeds again when the connection returns. The Played and podcast pages skipped their complete load when they opened offline and never ran it. They now run it once while open and online. Offline, a Played record that matched no episode may only be in a feed that has not loaded, so it reads and acts like any episode that is not downloaded: under Needs a connection, and a tap says Not downloaded. Online it stays unavailable with the old notice.
openEpisode refuses an episode that cannot play offline before it calls the player, so any other call of play in src/ui/PodcastView would bypass that refusal. A guard beside the library-store one scans the view's files for it.
The Queue menu said "Queue episodes I switch from"; SPEC section 9 names the preference "Queue episodes I switch away from".
…handled The Reorder Queue command's async callback returned a promise Obsidian never reads, so a view that failed to open left an unhandled rejection. The command now chains the reveal and the Queue page and logs a failure as the startup reveal does.
Obsidian styles .flair as an uppercase, letter-spaced tag with a start margin. The shell's flair reset its background and padding only, so the Queue head read "2 H 23 MIN", spaced further from its title than the design. The flair now also resets the case, letter spacing and margin.
A drag focuses the grip it starts on, so arrow keys can carry on from where the pointer left the episode. Dragging a row down the list moved its node past the next row, which took focus from the grip and left it on the page, so the arrow keys did nothing after the drag. Each move now returns focus to the grip once the rows settle, as the arrow keys do.
Edit feed URL showed a feed's address in Obsidian's default narrow text field, so only the end of the address was visible and the host was cut off. The text prompt's label keeps its width and the field takes the rest of the row, in Edit feed URL, Set feed address and New playlist. On a phone, Obsidian already stacks the label over a full-width field. The Modal mock gains modalEl, as Obsidian's Modal has.
Since the view opens once on a new install on desktop as well, mobile emulation is no longer needed to see PodNotes stay closed on later starts. Checked on desktop: one leaf with First run after the new install, zero leaves after closing it and reloading.
# Conflicts: # src/ui/PodcastView/QueuePage.svelte
… a re-follow Edit feed URL compared the saved record by identity. A sync republishes data.json and replaces every record, so the address the user typed and the fetch validated was dropped without a word. Add podcast keeps its search results, so removing the podcast and following the same result again stored the very same object, and the old edit landed on the new subscription. The edit now watches savedFeeds for the podcast leaving the library while it runs, and still compares the address fields for a sync that changed them. A refused edit says so. The edit keeps a sync's changes to the rest of the record.
When a sync saved the address for another podcast while Edit feed URL loaded it, the edit was refused without a word. It now shows the same Notice as the check before the load.
…oves to a pane of the same size Moving the PodNotes leaf from a pane beside Obsidian's status bar to an equal pane under it resized nothing, so the inset stayed 0 and the status bar covered the mini player's play/pause. The inset is now measured again on the workspace's layout-change, which Obsidian sends after a leaf moves.
…oved notice in removePodcast removePodcast now closes the 7 s "Removed X" Notice by clicking its text, as a user does, so the Notice cannot cover a later click target. The helpers add a held-mouse drag with trusted CDP input, pressKey (which replaces pressEscape), network emulation for offline, a fixture server that drops every request, and runFromPalette turns on the command palette core plugin it needs.
… then let it advance; New playlist; Reorder Queue Each journey failed on a planted defect: placing by index, a stale neighbour, no refocus, the wrong side of a row, a drag beside the list, a stale queue advance, an empty playlist name that closes the prompt, and the command navigating before it reveals the view.
…ecord and private address
…ar sliding open counts too Opening PodNotes in a collapsed right sidebar measured the inset while the view sat beyond the window's edge. The sidebar then slid open without resizing the view, so the status bar covered the mini player's play/pause until something resized it; the journey "a reload restores the last page and the episode paused where it stopped, in the mini player" failed on a fresh vault for this. Obsidian sends resize once a sidebar has opened and after a leaf moves to another pane, so the inset is measured on resize instead of layout-change.
…lias, which moved to the view
actions.ts held the target map, trusted input, waiting and reading, and navigation and lifecycle in 1,030 lines. Each concern now has its own module and every importer names the module that owns what it imports.
…feed address was edited One refusal() decides, in order, removed, changed, offline and taken, and each call site shows its message. Before, three predicates each showed a Notice, and a removed podcast got the message for a changed one.
…nce the save is asynchronous
…d taps and search once online J-OFF-01 now seeds Latest so a downloaded episode is not first online, plays a downloaded episode that is not the current one while offline, and after the connection returns plays an episode that needed it and runs a search. The feed fails through the fixture server while online, so the journey no longer waits on a request PodNotes sends offline.
Deploying podnotes with
|
| Latest commit: |
ea36929
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://cbd7a010.podnotes.pages.dev |
| Branch Preview URL: | https://feat-ui-lists.podnotes.pages.dev |
…ter several removals Undo placed a podcast or playlist at the index it had when removed, which is stale once another removal is pending. The actions now remember the order with every removed key, and an Undo resolves its place among the keys there now.
A pending Undo outlived a plugin reload, and clicking it ran the old instance's actions: a private feed's address went back into SecretStorage while the podcast never reached data.json. Unload now hides every Undo notice and disarms its button. A notice the pointer keeps up past its seven seconds stays tracked until it is gone.
… its secret reference
… the private address stays forgotten
…on release Each row a drag crossed wrote the queue, so a slow drag across three rows wrote data.json three times. The drag now holds where it would put its episode, by entry, and the page shows the queue with that placement applied, merged with changes playback makes meanwhile. Release writes one placement; Escape, a cancelled pointer, the episode leaving the queue, or leaving the page write nothing.
… counted with the write recorder
…nd measure again when a theme or snippet changes A status bar hidden with visibility: hidden keeps its box, so the mini player kept 27 px of blank space above it. A snippet that hides the bar resizes nothing, so css-change now reports too.
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.
Why
The v3 view still showed today's list pages: one filter bar on every list, a queue reordered from a modal, and playlists managed only in Settings. This PR builds the remaining list pages from the design spec and prototype and deletes the legacy UI they replace. It also fixes #453: Obsidian's status bar covered the mini player's play/pause.
What changed
Scope
obsidian://podnoteschange.Deviations from the design
Tradeoffs
Blast Radius
Every user sees the new list pages, and anyone who managed playlists in Settings now does it in the view. Desktop users who closed the view no longer see it reopen on every start. The bundle shrinks by deleting the legacy components, including the 600-name icon list only the old playlist manager loaded.
Verification
Screenshots from real Obsidian 1.14.4. Each sheet has a row per page (Queue, Playlists, a playlist, Latest, Played, Local files, Offline) with master, this PR and the design prototype side by side.
Sidebar 320, light and dark:


Pane 820, light and dark:


Phone 390 (emulated), light and dark:


#453, with "9 properties" in the status bar (master above, this PR below), at 820 and 320:




New playlist and the Undo notice:


Dragging a queue row with the mouse: screen recording
Docs at release
Fixes #453.
Note
Add Queue, Playlists, Local files, and list pages with offline mode
onlineStatusin online.ts: episodes without a downloaded copy are grouped under Needs a connection, refresh and address-editing are disabled offline, and offline playback attempts are refused before reaching the playerremovePodcastanddeletePlaylistgain position-preserving Undo,setFeedAddresssupports public feeds with duplicate-address rejection, andqueue.reorderreplaces the removedqueue.moveAPIaskTextprompt modal and a fragment-based Undo notice that is dismissed on plugin unloadqueue.moveis removed in favor ofqueue.reorder(see store/index.ts), episode menus no longer offer queue-reordering items, andIPodNotesimplementers must now exposeonlineandsetAutoQueue(see IPodNotes.ts)Macroscope summarized ea36929.