Commit Graph
254 Commits
Author SHA1 Message Date
KeelrLeek c737809325 android: a folder of audio files, and a paste of track links
The paste door now reads itself the way the desktop's does: one track link
in the paste and every line is looked up on its own public page
(playlist_import_track_links), which is the way past Spotify's 100-track
public ceiling — the wait screen counts them as they arrive. A fourth door
imports a folder of audio files off this phone, picked with the storage
plugin's own document-tree picker; playlist_import_folder grew a path
argument for it, because on Android it had no dialog to open and always
answered nothing. A YouTube playlist link needed no door at all — the link
one already dispatches it.
2026-09-13 23:32:00 -05:00
KeelrLeek 24c6857847 android: tap-to-sync, the per-track auto-sync switch, the vocal-reduced copy
The editor the desktop drives with a mouse is better under a thumb: the
song plays, the line to time sits large in the middle of the screen with
the one behind and the one ahead faint around it, and a tap anywhere
stamps it. One layer the back gesture peels, and a set left mid-tap is
offered back by the row that opened it; Save writes the track's own
document, lines never tapped left out of it and the button saying how
many are kept. The lyrics sheet also gains the per-track auto-sync switch
and the desktop's reduced-vocals copy — the same title, artist and
duration, so the words keep following it, and one row puts the original
back.
2026-09-13 23:04:49 -05:00
KeelrLeek e693dfb868 android: your own Soulseek interests, and who the server says is like you
Community's fourth tab is Taste: the likes and hates this client
publishes to the Soulseek server (slsk_interests_mine, slsk_interest_set),
added and taken back as chips, and the people the server ranks as close to
them (slsk_similar_users) — a row each into the peer sheet the app already
has. Every declaration asks the server again and the chips stay quiet
until it has answered, the way the desktop does, because the ranking only
moves when its input does. The lists live in taste.svelte.js, read once
per account when the tab is opened; the mock walk declares a like, watches
the match grow, declares a hate and takes the like back.
2026-09-13 22:52:03 -05:00
KeelrLeek 7eae0ee945 android: a name of your own for anyone on the network
The desktop's local contact metadata on the phone: a peer's sheet ends in
Name and notes — a petname, a group, a private note and the Favorite and
Trusted marks, kept in the app's own config and published nowhere. The
petname stands in for the username on the buddy list, in the chat list, on
a thread's bar, in the peer sheet, in a room's roster and on a room line,
and the real username stays with it — on the line beneath, or beside the
name in a room — so a stranger is never quietly turned into someone you
know. Favourites sit at the top of the buddy list, and clearing a person's
details drops the entry rather than storing an empty one — the backend's
own rule.
2026-09-13 22:40:17 -05:00
KeelrLeek f792d7955d android: a peer in full — their card, their interests, and what they have that you don't
The peer sheet's new "About this user" row opens a screen of its own: the
card their client serves (their own words, their picture, the upload slot
and the queue), the likes and hates they declared, and the comparison this
phone makes of their share against the library. The comparison is the
point of opening a stranger — every record they have by an artist in your
library that you don't, each row opening the search that finds it, and a
way to forget the whole comparison again. Nothing is uploaded to work it
out. Their picture opens in the artwork viewer, which now takes a data:
key beside a library path and an https URL.
2026-09-13 22:28:38 -05:00
KeelrLeek 694a29eb0d android: downloads on Wi-Fi only
One switch the desktop never needed, off by default: on mobile data every
Save the phone can start — a record, a track, a folder, a file, a
playlist's bulk, the now-playing Save — waits, with the reason on the bar;
streaming is never held back. The walk flips the switch, makes the
connection cellular and sees a download refused.
2026-09-13 22:15:07 -05:00
KeelrLeek 3c8989dfa6 android: the hub's filters on the network results, pasted lyrics
The raw results take the Soulseek hub's filters — a free slot only, FLAC
or MP3 320+ — as chips above the folders. The lyrics sheet has the
desktop's paste door: plain text, or LRC with timestamps, kept for the
track.
2026-09-13 22:03:57 -05:00
KeelrLeek 59be9f6c3e android: the nav's badges — transfers in flight on You, unread chats on Community
The desktop's title-bar download ring and its unread indicator, on the
phone's nav: a count on You while Save is fetching, a count on Community
when a chat has unread lines.
2026-09-13 21:52:53 -05:00
KeelrLeek f8c3b90665 android: hours on the library's count line, By format, Jump back in on the empty search
The desktop Library summary's hours on the phone's count line; a By format
order from the file stats (lossless by depth and rate first, then by
bitrate, the badge as the card's line); the empty Search page's Jump back
in — the last records played — as the desktop's empty search shows them.
2026-09-13 21:31:16 -05:00
KeelrLeek b50577fc81 android: a playlist's cover from a photo on the phone
The storage plugin gains pick_image — the document picker limited to
images, the picked content copied into the app's cache so the Rust side
gets a plain path. playlist_cover_set(id, path) is the desktop pick's
install step split out and shared (squared, the master cover, the row's
revision moved). A playlist's sheet offers Choose cover photo and Remove
custom cover; the mock walk exercises both.
2026-09-13 21:20:11 -05:00
KeelrLeek d95c3f48d1 android: the room list filtered by name, the rest on demand
Soulseek lists hundreds of rooms; the phone showed the first 80 and no way
to the rest. Community → Rooms now has a filter by name and extends the
list 200 at a time.
2026-09-13 21:07:11 -05:00
KeelrLeek 60303c8a95 android: reorder from the sheet — a playlist row up, down or to the top; a queue row up or down
Without a drag: a playlist row's sheet moves it up, down or to the top and
persists the whole order in one call (playlist_set_order, as the desktop's
drop does); a queue row's sheet moves it up or down, never past the track
playing. Also src-tauri/android/ship.sh — one round end to end: the mock
walk, the embedded build installed, the phone walk, the commit, each step
gated on the one before.
2026-09-13 20:55:50 -05:00
KeelrLeek 991d52d0cc android: the library's file stats on the cards, the artist's own plays, the thumbnail prewarm
The desktop Library's per-record file stats on the phone (library_album_stats,
one call for the whole library): FLAC 24/96 or MP3 320k badges on the cards,
the size on the list rows and the record page. The artist page's own
listening (artist_play_stats): the time played as a chip, the most played
track under "On this phone". The whole-library thumbnail warm a moment after
boot, at the cards' edge, one decode at a time behind the tiles' gate.
2026-09-13 20:37:51 -05:00
KeelrLeek 26928b706d android: the repair sheet, browse, every long-press sheet, You's last sections, the lyrics tools — and the walk's audio check made honest
The playlist repair (Find a local match: the library searched by the
row's name, a pick relinks it through playlist_relink_item; an unknown
row takes the track's identity). A peer's sheet on the raw results (play
or save the folder, browse their files, message, buddy) and the Browse
screen — the hub's Browse tab: a user's whole share with sizes and totals,
a folder played or saved whole; a share can hold thousands of folders, so
it filters and renders 80 at a time. "Look up a user" on Community →
Buddies. The sheets the phone lacked: a queue row's (Play now, Move to
next, Remove from queue), a catalog song's, a catalog card's and the
record page's own, a raw file's (in the results and in Browse), a room
name's, a buddy's, the now-playing Save (the desktop's Save menu for a
streamed track or record). The artwork viewer with pinch zoom, one layer
the back gesture peels. Search's recent searches and its spelling
suggestion from the names this phone knows. You → Streaming, Downloads,
Sharing, Lyrics, Album artwork, and Clear play history; a record's
Re-fetch the cover. The lyrics tools on the sheet's face: the offset
nudged and kept per track, another LRCLIB candidate chosen, the lookup
forgotten and done again (lyrics.svelte.js, shared by the face and the
lyrics sheet).

The harness: every CDP call times out and the reload step re-attaches
(a WebView that stopped answering hung a walk for ten minutes); the
audio check counts SoulStream's own tracks' underruns from the mixer dump
instead of the thread-wide counters that any app's notification moves —
the afternoon's "dropouts" were other apps' sounds, and the browse of a
49,544-file share measured at zero; __settle resets every tab to its root
through the nav; an adb step kind; the walks cover all of the above on
the mock and on the phone.
2026-09-13 19:56:20 -05:00
KeelrLeek 6e39dc43f3 android: playlists, the import doors, the share sheet, You → Sources — and three bugs the walks found
The desktop's two playlist pages on the phone: the shelf (pinned first,
mosaic covers, badges, New playlist, Import) and the page (kind, Pinned
chip, description, the meta line's warnings, Play / Shuffle / more, a
filter, rows tagged unresolved / set audio / file missing). Sheets for a
card, the page's overflow, a row, pick-a-playlist, a prompt and a confirm;
Add to playlist on every track and record sheet; a removal undoable for
seven seconds from the neutral toast (notice.svelte.js); stream rows play
through the resolver as path-less entries, a set slice starts and ends at
its cues; Add to library / Download all tracks with the desktop's bulk
rules; nav.replace for a review that became the thing it made.

The import doors that need no file — a link (YouTube · Spotify · Apple
Music · TIDAL · Deezer), a pasted tracklist, a YouTube DJ set — with the
review screen and the desktop's catalog verification pass. A share plugin
crate (crates/tauri-plugin-share): Share → SoulStream from any app keeps
the text in a ShareActivity and the WebView hands it to the import when it
comes back to the front. You → Sources: the source-quality window and the
priority per mode.

Found by the walks and fixed: set_settings takes camelCase arguments, so
the phone's normalization and library-folder saves never landed;
Cover.svelte's non-eager wrapper has no layout box, so its intersection
observer never fired and row thumbnails never loaded (the Artists tab too);
a history push while a history.go was still in flight left the nav mirror
one entry ahead and the page blank after a confirmed delete — the mirror
now applies once per microtask and never pushes during a pending traversal.

The harness names unmocked commands, carries console warnings' frames (the
smoke bundle is built unminified), probes the history mirror on every step,
mocks app_status / play_local / waveform / lyrics_get / player_state and
applies settings patches, runs adb steps on the phone (the share intent),
and walks playlists, the imports, the share and the settings on both the
mock and the phone. ANDROID.md documents all of it.
2026-09-13 17:56:52 -05:00
KeelrLeek bb828ff2dc android: the phone, on the real core — the mockup shell, playback, the media card, the catalog, the resolver, Save, the dev flavour
The same Tauri 2 project builds for Android: mobile.html as a third Vite
entry (src/mobile/, the mockup on real data), tauri.android.conf.json, the
mobile stubs (updater, lyric alignment, vocal separation), rustls reqwest
on Android, the AAudio buffer, and a hand-driven detour build
(src-tauri/android/{env,build,deploy,dev}.sh) while Developer Mode is off.
ANDROID.md is the recipe.

Playback: the media-session plugin crate (lock-screen card, MediaStyle
notification, audio focus, headset buttons, a foreground service that
promotes itself first — Android 14 kills the app otherwise), session
restore across reload and restart, solid chrome after measuring the
backdrop-filter lag.

The catalog: Search is the desktop's top bar (the top-result rule, artist
cards), the artist page and the record page are the desktop's, Home is the
desktop's landing page, Gaps per followed shelf. Playing goes through the
desktop's own source rules, moved verbatim from state.svelte.js into
src/lib/sources.js so both apps share one resolver; the phone readies the
next three tracks and crosses boundaries gapless. Save fetches a record or
a track into the library by the download rules; Transfers shows the
burst. The raw Soulseek search stays, offered on request, a peer's copy
streaming with the lane rising as the bytes arrive. Community is the
Soulseek side (rooms, chat, buddies); the private layer waits.

The opener and storage plugins (social links, the picked library folder,
all-files access), the volume normalization switch, and the harness:
tests/mobile/smoke.mjs walks the mock (a fake network behind
src/lib/events.js) and the real phone over CDP, perf.mjs measures frames.
2026-09-13 09:30:06 -05:00
KeelrLeek 972dd103cb release: 1.8.2-alpha
release / windows (push) Canceled after 0s
v1.8.2-alpha
2026-09-10 07:19:18 -05:00
KeelrLeek 12fff9f6ad setup, updater: the installer window was a dev build with nothing inside it, and latest never matched a prerelease
Tauri decides dev-vs-production from the `custom-protocol` feature, not from
--release, and both workflows build the setup exe with a plain `cargo build`.
So cfg(dev) stayed on, the frontend was never embedded, and the window a
tester opened loaded devUrl: "localhost refused to connect" on a blank
800x580. The feature is a default of the crate now, with a build.rs guard
that fails a release build without it, and build.rs tracks setup/dist so a
changed window cannot be left out of the binary it is supposed to be in.

Behind that, the next wall: /releases/latest answers only with full
releases, and every release here is an alpha, so it 404s. The setup app read
that as a refused key and the app's Stable channel read it as a token that
cannot see the repository — both have been broken since the first alpha.
Each now lists /releases and takes the newest that is neither a draft nor
the rolling nightly, which the list really does put first.

And check_saved_key threw the saved key away on any failure at all, a
dropped connection included. The entry it deletes is the app's own updater
token, so only a refusal clears it now.

The release notes name the file to hand a tester: six assets on the page and
nothing saying which, while the bare NSIS installer is the biggest of them
and installs with no key gate and no signature check.
2026-09-10 07:19:15 -05:00
KeelrLeek 3cfff93036 release: 1.8.1-alpha
release / windows (push) Canceled after 0s
v1.8.1-alpha
2026-09-09 18:10:29 -05:00
KeelrLeek f8508da081 setup: the installer is our own window — the alpha key is the gate, the release comes down with it and is verified before the silent install, and the in-app tour asks for the key too 2026-09-09 18:00:02 -05:00
KeelrLeek 8093835482 release workflow: the version step is a block scalar, so its quotes reach bash whole
release / windows (push) Canceled after 0s
v1.8.0-alpha
2026-09-09 16:20:13 -05:00
KeelrLeek fac6fc33dd release: 1.8.0-alpha
release / windows (push) Canceled after 0s
2026-09-09 16:16:22 -05:00
KeelrLeek cd2391198e first run, updates, installer: a setup tour stands in front of a fresh install, releases arrive through an in-app updater with stable and nightly channels behind a read-only token, and the branded installer builds again 2026-09-09 16:16:19 -05:00
KeelrLeek 07810f7543 release: 1.7.1-alpha 2026-09-09 12:13:10 -05:00
KeelrLeek 4ea1582afc library: the toolbar is a solid card under dark glass, because a backdrop blur over the frost lets titles through 2026-09-09 12:12:56 -05:00
KeelrLeek aaacfc9ff5 holdings: a dash-led edition tail normalises like a bracketed one, so the catalogue's "Pinkerton - Deluxe Edition" finds the member who holds Pinkerton 2026-09-09 12:12:55 -05:00
KeelrLeek 1c66602b89 who has: the sheet closes when the page scrolls under it, instead of floating beside a card that has moved 2026-09-09 12:12:55 -05:00
KeelrLeek 67b7444f82 settings: each section heading carries the same icon as its index entry 2026-09-08 16:28:18 -05:00
KeelrLeek 48ae01c753 settings: Appearance gets a real icon — "display" was a CSS property that had leaked into my list of icon names 2026-09-08 16:16:40 -05:00
KeelrLeek a0528c6d3f settings: each index entry carries its section's icon, lit in the accent when it is the one on screen 2026-09-08 15:45:42 -05:00
KeelrLeek 07ac3d3631 settings: the long lists use the app's own chooser — the sort menu's popover under a pill — instead of native selects the webview draws with the OS's text 2026-09-08 15:43:13 -05:00
KeelrLeek 7e291cfccc settings: one long page of section cards behind a sticky index, segments and switches for the choices, and the Networks card moved in from Community 2026-09-08 15:39:19 -05:00
KeelrLeek 6c44f3e2ab community: a chat thread stretches with the pane too — no inner width cap remains 2026-09-08 14:53:05 -05:00
KeelrLeek f006c1cd5c community: the page fills the pane edge to edge instead of sitting in a centred 1520px column 2026-09-08 12:55:10 -05:00
KeelrLeek b8e6fb7396 who has this: a gap card says who holds it, the sheet and the open album name them, a Space grows a Holdings tab, and Get it is the hub's own grab or a wait for their presence 2026-09-08 12:10:49 -05:00
KeelrLeek a98c750908 holdings: a member of a Space publishes what they hold as room-salted album keys, sealed to the room, so "who has this" is answered while they are offline 2026-09-08 12:10:49 -05:00
KeelrLeek 08ca09586a community: Discover ranks the shelves it already has into one, and every card still says where it came from 2026-09-08 09:48:07 -05:00
KeelrLeek 8285b97cbe shared playlists: a follow with news says so through a toast, and a follower's rows keep the owner's catalog ids 2026-09-08 09:48:06 -05:00
KeelrLeek 594a33a302 freenet node: the update exit is watched from a thread for as long as the check takes, not for two seconds inside the start call
The supervisor that applies the node's update-wanted exit (code 42)
watched a fresh child for two seconds and then let go. The exit is the
result of a network check the node makes at startup, and it takes as long
as the network takes: on a fresh install today a pinned 0.2.132 exited
several seconds after launch, after the supervisor had already returned,
and the layer read "node absent" with only node.log saying why — the
exact failure the supervisor was written against, back again.

`start_node_inner` now puts the child in `freenet_node` itself and leaves
`watch_for_update` polling it from its own thread for 90 s, under the
lock rather than owning the child — the child belongs to the state, and
the Stop button takes it. An update is applied and the peer restarted
while the lock is held, so nothing reads the dead child as "stopped" and
starts a second peer meanwhile; any other exit is logged and the slot
emptied; an emptied slot ends the watch. The first boot is the one launch
the watcher cannot cover (its child never reaches the state), so that loop
handles code 42 itself, once, with its clock restarted.

Seen live on this machine: the other-exit branch caught a spawn that died
on a busy port (code 43) within half a second and cleared the slot; a peer
killed by hand and started again from the app was connected two seconds
later. The exit-42 branch itself is not reproducible here any more — the
binary is on the latest release — and stands on the same code path.
2026-09-06 08:17:55 -05:00
KeelrLeek f67a0a30b4 shared playlists: importing a share follows it, so the owner's next revision reads as "2 new tracks since you imported"
Importing a shared playlist was a one-time snapshot: the owner's next
revision could only arrive as a whole second copy. A copy now remembers
WHICH revision it was made from, and the app reads the pointer for it
every ten minutes — no new contract, the same pointer and revision reads
the share card already does.

The ledger gains `following`, keyed `owner:uuid` — the one key a database
cannot hand out twice. A follow records the local playlist, the synced
revision (hash + seq) and what the last check saw: the current revision,
the entries it has that the synced one never had (in the owner's order),
and how many synced entries are gone. The diff is by occurrence id, the
proposal board's rule: a reorder is not news, a removal is counted, a
duplicate stays its own row. `import` stages the fetched revision so
`follow` records exactly what the copy was made from — a pointer read of
its own could already name a newer one.

Update appends the new rows through the same resolve path an import uses
(library first, Soulseek stream otherwise) and never removes or reorders:
the copy is the follower's to edit, and a track the owner dropped is
reported, not taken away. It runs under the ledger lock end to end, so a
check landing meanwhile merges against the new synced revision or not at
all. Checks read the network WITHOUT the lock — a pointer read is a ring
search — and merge each result under it afterwards, dropped if the follow
moved on.

Deleting the local copy ends the follow, in `forget_playlist` and the
startup reconcile both: the row id goes back to SQLite, and the next
playlist created must not start taking someone else's updates. Seen live:
the re-import after a delete took row id 1 again and followed cleanly.

Surfaces: an Inbox card when a check finds news ("2 new tracks since you
imported it · 1 removed", Add 2 tracks / stop following), and a Following
row in the playlist's Share sheet (up to date / N new / withdrawn by the
owner, Check now, Stop following). The poll rides the inbox watch: its own
ten-minute interval, plus one pass when the adapter connects.

Live, two instances on this machine: import + follow 582 ms; the owner's
revision 2 (one dropped, two added) read back as exactly "2 new, 1 removed"
in 2.7 s; Add appended two stream rows in 46 ms and left the dropped track
in place; a withdraw read as withdrawn in 57 ms; delete took the follow
with it; re-follow and unfollow both left the copy alone. One pointer read
after a republish timed out at 120 s and arrived a second later — the same
first-read-after-a-change lag the board has.

Tests: the diff (order, removal, a duplicate occurrence) and the ledger
reading files written before follows existed. App 152 · 6 ignored.
2026-09-06 08:16:45 -05:00
KeelrLeek df06f849e0 spaces: a room carries what its members are playing, sealed in the window its chat already uses 2026-09-04 12:20:27 -05:00
KeelrLeek 4ca9ab043b shared playlists: a deleted playlist takes its share with it, so the next one does not go out wearing its name 2026-09-04 11:06:34 -05:00
KeelrLeek 0ff65fd9b5 community: the board counts your pending proposals under the key you wear there 2026-09-04 09:56:39 -05:00
KeelrLeek 06baa5a805 spaces: leaving a room says so, so an owner stops publishing a seat nobody sits in 2026-09-04 09:56:39 -05:00
KeelrLeek d6102101af spaces: a rotated key goes to the person behind the room key, and the epoch it belongs to is found by number 2026-09-03 16:01:40 -05:00
KeelrLeek 7b7f48ebc6 community: declare what you like on soulseek, so the similar-users shelf finally has something to rank on 2026-09-03 15:05:23 -05:00
KeelrLeek 8093adc89e a room-key proposal signs the hash the verifier checks, and the ping's pill shortens a raw id 2026-09-03 13:52:04 -05:00
KeelrLeek 6f7452c803 a second instance on one machine: its own data, its own peer port, and a join that says itself again when it was never seated 2026-09-03 13:39:06 -05:00
KeelrLeek 67e52697b2 spaces: a member wears a key minted for that room alone, so a roster stops naming personas 2026-09-03 13:06:32 -05:00
KeelrLeek b3c121fa94 community: the inbox check belongs to the app, not to the page that happened to be open 2026-09-03 12:38:57 -05:00