Repository navigation
Conversation
…and/tent Records how it landed (PR #5, CI green on the xcode-27 runner, fast-forward so every referenced hash is on main), that listening-stats now needs a rebase, and the two queued features with their probe verdicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…b bar is vertical Where iPhone Duo lays the tab bar out vertically, iOS's minimize-on- scroll never folds the accessory, so the pill sat over the content. The probe ruled out animating the system accessory: toggling isEnabled pops or crossfades and reflows the content 38-56pt each time. So where \.toolbarVerticalEdge is non-nil (iOS 27.1+), the accessory is off and the same NowPlayingPill is mounted as our own glass overlay, resting where the accessory did. Scrolling down sinks it with a squash (easeIn 0.22s); scrolling up, reaching the top, or ~1s idle springs it back with an overshoot. Reduce Motion fades; it never ducks while VoiceOver runs, and a ducked pill isn't hit-testable or focusable. - PillDuckPolicy (pure, 22 tests): 24pt of unbroken downward travel ducks, 12pt upward shows, the top 8pt and non-scrolling content always show; offsets clamp to the scrollable range so rubber-banding doesn't count. ScrollGeometry.containerSize means different things for ScrollView (insets removed) and List (insets included), so the sample uses visibleRect. - One shared reporter on 9 scroll roots writes only state CHANGES. In overlay mode each scroll view gets a constant 38pt bottom safeAreaPadding (inset 72 = the accessory's), so ducking never reflows content; the toast clears the pill. - Regular iPhones and horizontal-bar displays keep the system accessory and minimize behavior; iPhone 17 Pro / 26.5 Home is pixel-identical. 197 tests. Unverified: any real scroll (simctl can't scroll). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
The Apple Team ID, the notary profile name and a device UDID were in the public repo's CLAUDE.md. They now live only in the maintainer's local memory; the doc points there. (Older history keeps them — a rewrite isn't worth it; none of them is a credential.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
Every header that identifies the app to nugs.net (the API and stream resolver User-Agents and the CDN Referer) now comes from NugsIdentity. `.current` is byte-for-byte today's identity (pinned by tests), so this changes nothing in production; switching to an honest identity later is a one-line change. The DEBUG-only Network Probe (account menu) lets the maintainer measure, with his real session, what nugs accepts before that switch: 16 sequential requests, 1s apart, only on Run — subscriptions and the catalog API (api.aspx) with the current vs an honest "Yeti/<ver> (unofficial; +github)" UA, the stream resolver per platformID tier with each UA, and the CDN with/without the Referer. "Copy results" yields a report with no tokens or stream URLs. Verified absent from Release binaries (strings/nm). 231 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…networking Acting on the live probe (Tim's Duo, 2026-11-07): nugs accepted an honest "Yeti/<ver> (unofficial; +github)" User-Agent on every surface it tested, and the CDN served media without the faked Referer. - Identity: every nugs request now identifies as Yeti; no Referer. The old Android strings survive only as the DEBUG probe's control, absent from Release (symbols + disassembly checked). - Streams: tier 7 (ALAC 16-bit, what playback already chose) first; other tiers one at a time only if it has no usable stream. One resolve per track instead of four parallel; in-flight resolves are shared; per-tier 4h cache. Pure StreamTierPlan, 12 tests. - Sign-in: the in-app email/password (ROPC) screen is gone; sign-in happens on nugs.net's own page, so the app never sees the password. Existing sessions keep working. - Politeness: 4 req/s token bucket (burst 8) on API/resolver calls; playback retries back off 0.5/1/2s and skip failed tiers; artist catalogs cached 30 min with a 30-page cap; lazy video rails; at most 2 download transfers per host, 300ms between resolves. 16 tests. 259 tests. Live-unverified: id.nugs.net (refresh) and catalog.nugs.net with the honest UA; tier fallback; HLS-only download fall-through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
"AppleNugs" built its name from two companies' trademarks. The app is now Yeti (named for the Goose song) everywhere a user sees it: menu bar and About (CFBundleName), Home/Dock (CFBundleDisplayName), login screen, Home eyebrow, the Tape Room cassette label, Help menu, Keychain label, Credits, appcast title, and an iOS account-menu version line. Kept on purpose, so existing installs lose nothing: bundle ids, Application Support/AppleNugs, the Keychain service/account, the Sparkle feed URL + EdDSA key (verify-sparkle-config passes), the repo, and the .app file name for now. yeti:// is added; applenugs:// keeps working for goosealmanac's links (10 new DeepLink tests, case-insensitive schemes). Disclaimers: "Unofficial. Not affiliated with nugs.net or Apple." on the login screen, in About and at the top of the README. NOTICE rewritten: both trademarks, the Goose homage, honest "Yeti" identification, no DRM circumvention, and why it still uses the official OAuth client. README drops the downloader pointers and protocol notes, no longer claims the CDN needs faked headers, describes browser-only sign-in, and fixes the stale download link. 269 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…tive subscription Downloads used to be the raw CDN FLAC/ALAC files — unencrypted, with Reveal in Finder on macOS, never expiring and surviving logout, so content could outlive a cancelled subscription. - At rest: each finished download is sealed into <uuid>.yeti — 64 KiB chunks, AES-GCM, nonce = per-file prefix + chunk counter, the whole header + chunk index authenticated (reorder/splice/truncate/edit all rejected). Per-install key in the Keychain (AfterFirstUnlock ThisDeviceOnly, never synced); no file fallback. The plaintext temp is deleted immediately. iOS: completeUntilFirstUserAuthentication so lock-screen and gapless playback keep working. - Playback: yeti-local:// + an AVAssetResourceLoaderDelegate that decrypts only the chunks a request covers. Verified with generated FLAC and ALAC: duration matches, a mid-file range decodes to bit-identical PCM, an AVPlayerItem seeks and plays to the end. - Migration: existing plaintext downloads are sealed in place on launch (write new → manifest → delete old; crash-safe); a show that can't be sealed is deleted, never left plain. - Lifecycle: logout deletes every download and the key. Offline play needs a subscription confirmed active within 30 days; the stamp is renewed only by a FRESH subscription answer (re-fetched on each token refresh, one extra request), never by the sign-in snapshot, so a cancelled account can't keep its downloads by browsing. - macOS: Reveal in Finder removed; downloads stay available. 302 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…re rules Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…ckground
Measured with a repeatable harness (scripts/sealed-footprint, two
10-minute tracks, play + park + seek + gapless cross), peak
phys_footprint in MB:
FLAC ALAC
plain file 19.3 18.6
sealed, before 134.0 105.5
sealed, after 19.5 20.8
- SealedAssetLoader sets isEntireLengthAvailableOnDemand, so
AVFoundation reads <=64 KiB on demand instead of buffering the whole
file. Bit-identical PCM re-verified (FLAC + ALAC, many requests); a
test fails if the flag is removed. Bounded responses weren't needed.
The old "3.0 s FLAC plays to 3.04 s" was AVPlayer's own item
duration, not corruption.
- iOS background: the 0.25 s periodic observer is removed on
background and restored (with one immediate tick) on foreground. A
once-a-minute, player-clock observer saves the resume position (only
if it moved >=5 s). Lock-screen Now Playing no longer needs the tick:
elapsed time is read live, seeks push their target, duration is read
at item-ready/gapless adoption, and out-of-app play/pause flips are
pushed. Position is saved on pause and at queue end.
- Fixes a race: the play/pause observer usually lands before an
interruption's .began, so a phone call would never resume music; a
pause within the last 2 s now counts as "was playing".
- DEBUG memory readout in the Network Probe: footprint, kernel peak
(lifetime high-water mark), jetsam headroom, background tick count.
Deferring the local preload was measured and skipped: AVQueuePlayer
already prepares a parked item ~6-9 s before the boundary; early vs
late peaks are within noise. 307 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…lock Memory comes first, sampled at copy time, and Copy works without a network run — so a paste after a long background session carries footprint, peak, headroom and background ticks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…ticks) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
…ssion
- docs/design/yeti-theme/: the song-inspired sixth theme ("Peg Hook":
cover art in a toy blister on night blue, foil-pink accent only, sale-
yellow stickers, one FoilSweep on a new track), a token sheet that maps
onto Theme fields, a wiring list, and the three concepts it beat.
Produced by a research → brief → 3 concepts → 2 judges → refine
workflow; paraphrases the song, quotes no lyrics.
- CLAUDE.md: title/intro now say Yeti; a START HERE block lists the
branches (pill-duck + yeti unpushed), the next steps in order, and the
session gotchas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN
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.
The app becomes Yeti — an unofficial player for nugs.net (named for the Goose song) and follows nugs.net's Terms of Use where it matters. Also includes the iPhone Duo pill-duck.
Compliance
Yeti/<ver> (unofficial; +github; <OS>)with no faked Referer, instead of posing as the official Android app. A live probe on a real account showed nugs accepts it everywhere, including token refresh. The only borrowed piece left is the official OAuth client (documented in NOTICE; needs a nugs-issued client)..yetifiles (chunked AES-GCM, per-install Keychain key), playable only inside the app; Reveal in Finder removed; logout deletes them; offline play needs a subscription confirmed active within 30 days.yeti://added;applenugs://still works.Performance
Encrypted playback runs at plain-file memory (peak 134 MB → 19.5 MB per FLAC track, via
isEntireLengthAvailableOnDemand), and the 4 Hz player tick no longer runs in the background. On a real iPhone Duo: 60.9 MB peak for the whole app over a locked-screen session, 0 background ticks.iPhone Duo
Where the tab bar is vertical, the now-playing pill ducks out of the way on scroll and springs back.
Verification
SWIFT_STRICT_CONCURRENCY=complete;AppleNugsTests307 passing.Also adds
docs/design/yeti-theme/, the song-inspired design that will become the app's only design.🤖 Generated with Claude Code
https://claude.ai/code/session_01VriHZgxfrBNm1js1UNLYTN