Skip to content

Seseri 3.1.0 — production finalization (security, queue UI, a11y, RTL, release CI)#3

Merged
IACBI merged 51 commits into
mainfrom
redesign/seseri-2.0
Jul 8, 2026
Merged

Seseri 3.1.0 — production finalization (security, queue UI, a11y, RTL, release CI)#3
IACBI merged 51 commits into
mainfrom
redesign/seseri-2.0

Conversation

@IACBI

@IACBI IACBI commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Production-finalization pass on top of the 3.0 rewrite. Everything below is verified by the full `npm run verify` chain (lint + tsc + 104 frontend tests + build + worker tsc + 26 worker tests) plus two headless runtime smokes (mini-player/queue 7/7, offline download/seek 9/9).

Security

  • Worker redirect SSRF closed: redirects followed manually (max 3 hops), every `Location` re-validated by the private-host guard (+4 tests).
  • Prod CSP cleaned: build-only Vite plugin strips `http://127.0.0.1:8787\` and bare `ws:` from `dist/index.html`; fails the build on token drift.

Features / UX

  • Queue panel — the play queue finally has a UI (view / move up-down / remove / clear, count badge, keyboard accessible).
  • Styled confirm dialog — all four destructive actions prompt consistently (clear-downloads and unsubscribe previously deleted without asking).
  • Offline banner, export success toasts.

Accessibility / i18n

  • Focus management on navigation, aria-label/aria-busy fixes.
  • RTL: logical CSS properties — Arabic now mirrors correctly.
  • ≥44px touch targets, safe-area (notch) support, design-token adoption + z-index scale.
  • 15 new i18n keys × 8 languages (compile-time completeness enforced).

Release engineering

  • `desktop.yml`: `v*` tags build the NSIS installer on `windows-latest` → draft Release (v3.1.0 already built: `Seseri_3.1.0_x64-setup.exe`).
  • Non-blocking `npm audit` job in CI.
  • All four version fields aligned at 3.1.0; Cargo scaffold metadata fixed; CHANGELOG cut; STORE.md iOS roadmap + SECURITY.md residual-risk notes.

Remaining (credential-blocked, documented)

Windows code signing · Play keystore/assetlinks fingerprint · Apple developer account.

Merging this deploys the web app to https://iacbi.github.io/seseri/ via pages.yml.

IACBI added 30 commits June 19, 2026 14:33
…nt SW

- Work around iTunes API serving CDN-cached responses with another origin's
  Access-Control-Allow-Origin (the "Failed to fetch"/CORS error): route
  search & lookup through itunesFetch() with a cache-busting param + proxy
  fallback so every request gets a fresh, correctly-attributed response.
- Make service worker path-independent (relative precache/scope) and
  network-first for navigations so new deploys are picked up; bump cache.
- Fix manifest scope/start_url and 404.html redirect to derive base path
  instead of hard-coding the old /podcast-player/ location.
- Wordmark "Seseri" no longer disappears under its animated sheen: the white
  highlight band is now a theme-aware token (--wm-shine) that becomes a violet
  shimmer on the light canvas instead of white, so letters stay legible at
  every point of the sweep, in every theme.
- Hero vignette is theme-aware (--hero-vignette): no more muddy grey corners
  on the light background.
- Darken light-theme --text3 (captions/dates) for AA-level legibility on white.
…tokens

Refined Studio direction — make Seseri feel like real audio hardware.

- Signature: replace the flat progress bar with a real waveform scrubber.
  Per-episode amplitude bars are generated from a deterministic seed (mulberry32
  hashed from the track id; no audio analysis, which is impossible on cross-origin
  podcast MP3s). Played region is accent-filled via clip-path; drag to scrub with
  a time tooltip, a leading-edge playhead, keyboard support, and a subtle
  per-bar "breathing" + playhead glow only while playing.
- Living equalizer: now-playing eq animates only while playing; the currently
  playing episode row swaps its index for a mini-equalizer (gated by body.is-playing).
- Design tokens: add a 4-pt spacing scale (--s-1..8), a type scale (--fs-*),
  motion-duration tokens (--d-fast/base/slow), a layered elevation ramp, and
  waveform tokens; route player rhythm/transitions through them.
- Motion: now-playing title crossfades on track change; quieter ambient glow.
- Floors held: single file, no new deps, all 3 themes, i18n, focus-visible, and
  full prefers-reduced-motion (waveform/eq/ambient all static).
- Settings jank fix: the drawer stacked three backdrop-filter blurs (overlay
  blur(6px), panel blur(28px) saturate, sticky header blur(16px)). Animating
  translateX on a blurred panel forces a full backdrop re-rasterization every
  frame. Replaced with a solid --surface drawer + plain scrim; added
  will-change:transform and contain:layout paint so the slide is composited.
- Episode rows get content-visibility:auto + contain-intrinsic-size so off-screen
  rows in long feeds (100+ episodes) are not rendered until scrolled into view.
- Waveform "breathing" now animates every other bar instead of all 56, halving
  active animation layers while keeping the living feel.
- Settings scroll jank: freeze all continuous background motion while the drawer
  is open (body.settings-open pauses the ambient glow, wordmark/logo, waveform,
  equalizers, pulse) so the panel scroll gets the full frame budget.
- Drop the blur(8px) filter on the hero glow — the radial gradient is already
  soft, and a continuously animated filter pass steals frames; promote it with
  will-change so the breathe is purely compositor work.
- Scroll containers (settings panel, episode list, results) get
  overscroll-behavior: contain + -webkit-overflow-scrolling: touch for momentum
  and no scroll-chaining; panel uses contain: paint.
- Merge README.md + README.tr.md into one bilingual README with in-page
  language anchors (English / Türkçe); delete README.tr.md.
- Update all docs to the seseri deployment: live URL, clone URL, path-independent
  deploy note; document the waveform feature.
- Refresh privacy policy (date, drop the Android mention — pure web app now, add
  Google Fonts disclosure); SECURITY proxy note.
- CHANGELOG 2.1.0: CORS workaround, path-independence, design overhaul, perf.
- Trim .gitignore to web-relevant entries (drop Android/Gradle/JetBrains cruft).
Use #lang-en / #lang-tr so the in-page language jump links don't collide with
auto-generated heading slugs and work across renderers.
Add og:site_name, og:image:type/width/height/alt and twitter:image:alt so
platforms render the share preview without re-fetching or guessing dimensions.
…l guard

- Replace render-blocking @import with a <link> stylesheet + preconnects to
  fonts.googleapis.com / fonts.gstatic.com / itunes.apple.com (faster first
  paint and first search).
- Add referrer policy (strict-origin-when-cross-origin) so deep-link query
  params (?rss=<url>, ?podcast=<id>) don't leak in the Referer to third parties.
- Download: pass the episode URL through httpsOnly() before assigning it to the
  <a> element (defense-in-depth against non-https / javascript: URLs).
- #4 Replace all emoji/symbol glyphs with one consistent inline-SVG icon set
  (sprite + <use>): fav/share/settings/play/pause/prev/next/rewind/forward/
  download/sort/moon/settings-heading. Strip baked-in glyphs from i18n strings.
- #3 Favorited state now shows a filled accent star + accent chrome (not just ☆→★).
- #5 Custom <select> chrome (no native arrow) matching the button cluster; moon
  icon label for the sleep timer.
- #8 RTL: skip buttons are icon+number (no misplaced −/+); speed select forced LTR
  so it reads "1×" not "×1".
- #6 Localize aria-labels via new data-i18n-aria; t() now falls back to English.
- #7 Waveform slider announces aria-valuetext (time) and handles Home/End.
- #9 Sleep timer: active accent state + a transient status message on set/expiry.
When a feed is open but no episode is selected yet, the player now reads as a
prompt instead of a loaded track:
- hide the "now playing" label + equalizer (it wasn't playing anything);
- de-emphasize the waveform (dimmed, grayscale) and make it non-seekable
  (pointer-events off, removed from tab order, aria-disabled);
- disable the play and skip buttons (prev/next were already disabled at idle).
Selecting an episode clears the idle state and activates the transport.
Paste a YouTube playlist/channel/video link to listen to shows that publish
on YouTube. Episode list comes from YouTube's keyless Atom feed (existing
CORS-proxy path); playback uses the official youtube-nocookie IFrame embed —
no API key, no third-party stream servers. A shared playback facade routes
transport/keyboard/scrubber/Media Session to either <audio> or the YT player,
so audio podcasts are unchanged. Download hidden for YT items; CSP tightened
(script-src + frame-src), SW cache bumped to seseri-v3.
…rrors

The feed fetch waited on one CORS proxy and, on the 25s blanket timeout,
returned silently — leaving 'loading episodes' forever. Now all proxies are
tried in parallel (first non-empty body wins, each with its own timeout) and a
third proxy (codetabs) is added; total failure surfaces a real error instead of
an endless spinner. connect-src updated for codetabs.
Two fixes for YouTube playback:
- extractYouTube only matched www.youtube.com (dot-prefixed); a bare
  'youtube.com/...' link fell through to the RSS path and failed. Guard now
  accepts youtube.com with/without www/subdomain and youtu.be.
- Generic CORS proxies are unreliable; fetch the feed via rss2json (a
  CORS-enabled RSS->JSON service) first, falling back to the raw Atom XML
  through the proxies. connect-src updated for api.rss2json.com.
Hide the embed off-screen (kept rendered so audio keeps playing — never
display:none) so YouTube feeds look like a normal audio podcast (cover art +
waveform). Request suggestedQuality:'small' to reduce data; the official embed
still streams video in the background (no audio-only stream exists), but at the
lowest rendition.
For playlist links, enumerate the full list (up to ~200) through the IFrame
player's getPlaylist() instead of only the feed's latest ~15. Items beyond the
feed show without a title/date ('Bölüm N') until played, when the title is
filled from the player. Falls back to the feed-only list if enumeration is
blocked or times out (graceful degradation, verified). Drops the '~15' note
when the full list is available.
…ates, download)

Resolve YouTube shows through public Piped/Invidious instances to a direct
audio URL played by the normal <audio> element — ad-free, background/lock-screen,
resume, download, full list (up to ~200) with real dates + durations, lowest
bitrate to save data. Several instances raced in parallel. Falls back to the
keyless feed + official youtube-nocookie embed when no instance serves the
content (then embed limits apply). CSP connect-src updated for the instances.
- ytServiceAudioUrl now picks the highest-bitrate audio stream (was lowest).
- On the embed fallback, episodes that only have a 'Bölüm N' placeholder get
  their real title from noembed (keyless CORS oembed); for shows that put the
  date in the title this also surfaces the date. CSP connect-src += noembed.com.
Tagged current state as v1-legacy. Screenshots double as store-listing
source material later (P6).
- Legacy app moved intact to legacy/ (runnable via npm run legacy)
- Design-token CSS extracted verbatim into src/styles/{tokens,themes,base,components}.css
- Strict tsconfig, ESLint flat config, Prettier, Vitest wiring
- vite-plugin-pwa injectManifest with sw.ts port of legacy sw.js
- Core domain types in src/feeds/types.ts
- public/ carries manifest.webmanifest, icons, privacy policy, 404
- 2,070 lines of legacy JS ported into src/{state,feeds,youtube,player,ui,storage,i18n}
- i18n: 8 languages as typed langs/*.ts (Lang interface — missing key = compile error)
- feeds: unified resolveFeed() replaces loadPodcast/loadRss/loadYouTube duplication;
  parseRss is a pure, unit-tested function; proxy chain has a VITE_API_BASE Worker slot
- player: engine.ts transport facade (<audio> + YT embed) emits typed events
- UI: typed h() DOM builder — no innerHTML with remote data, no inline handlers;
  toast replaces alert(); visible error states replace empty catches
- Router: pushState history — back button navigates feed → home; legacy
  ?podcast=/?rss=/?yt= deep links unchanged
- CSP: script-src no longer needs 'unsafe-inline' (verified 0 violations)
- Tests: 34 passing (rss-parser via jsdom, input-parse, i18n completeness)
- Smoke-verified end-to-end: search→play→resume, YouTube video (real audio +
  title fill), RSS (mocked proxy, 175 eps), themes, settings, favorites
- storage/db.ts: idb `seseri` v1 (feeds + downloads stores); settings/
  progress/subscriptions intentionally stay in localStorage (legacy compat)
- player/offline.ts: episode audio in Cache API bucket `seseri-audio`,
  blob-URL playback (native seek, no SW range handling), storage estimate,
  persistence request at boot
- player screen: stale-while-revalidate feed open (cached copy paints
  instantly, network refresh preserves the playing episode), offline copy
  preferred for both direct audio and YouTube episodes, download toggle
  with legacy file-download fallback for CORS-blocked CDNs
- sw.ts fix: activate no longer wipes `seseri-audio` on shell upgrades
- storage/opml.ts + tests: OPML export/import incl. iTunes/YouTube link
  mapping; settings panel gains storage usage, OPML/JSON export, import,
  clear-downloads (11 new i18n keys × 8 languages)
- scripts/smoke-p3-offline.cjs: headless Edge offline smoke — download →
  offline reload → cached feed renders, downloaded episode plays & seeks
  (9/9)
… pool

- worker/: Hono app on workerd — GET /v1/feed?url= (RSS proxy, SSRF guard,
  20 MB cap, 15 min edge cache), /v1/itunes?url= (host-pinned, 1 h cache),
  /v1/yt/list & /v1/yt/resolve (plan option B: server-side race over a
  cron-health-checked Piped/Invidious pool stored in KV)
- CORS: prod origin allowlist + any localhost origin; KV fixed-window rate
  limit (~60/min/IP)
- 22 handler/unit tests on @cloudflare/vitest-pool-workers (SSRF matrix,
  validation, proxy happy paths, CORS reflection)
- root: worker:* scripts, verify chain now includes worker typecheck+test;
  .env.development sets VITE_API_BASE=http://127.0.0.1:8787 (client falls
  back to public proxies when the Worker is down)
- scripts/smoke-p4-worker.cjs: e2e proof — The Daily (18 MB RSS, 2907 eps)
  renders in the browser through the local Worker (3/3)

Verified live on wrangler dev: itunes lookup 200, 18 MB feed 200 (cache
hit 0.26 s), YT endpoints degrade to 502 when every public instance is
down (client then uses its embed fallback). youtubei.js spike (option A)
deferred; tracked for the deploy phase.
- theme: 'auto' (default) follows prefers-color-scheme live; theme-color
  meta + accent-soft resolve against the painted theme; new theme_auto
  i18n key in all 8 languages
- layout: screen switching is now class-driven (body.feed-open) with
  #app{display:contents}; ≥900px an open feed shows a library rail
  (compact brand, search, subscriptions) beside the episode pane —
  the idle home keeps its full-width hero
- mini player: leaving a feed no longer stops playback — a floating
  glass transport (art, title, progress hairline, play/pause) appears on
  home; tapping it re-enters the loaded feed without reloading
  (state/now-playing.ts signal + ui/mini-player.ts)
- back button fix: deep-linked entries used to history.back() out of the
  site; router now tracks whether home exists behind us (canGoBack) and
  falls back to goHome()
- scripts/smoke-p5-mini.cjs: play → home → mini bar visible & playing →
  pause-in-place → reopen feed without reload (5/5); screenshots at
  1280/768/390 reviewed
- episode rows: partially-played episodes show an accent hairline at the
  listened percentage; ≥96% marks the row listened (dimmed, ✓ badge)
- feed load errors render a real retry button (new btn_retry key × 8
  languages) that re-runs the failed request
- settings drawer is a native <dialog>: platform focus trap + Esc,
  ::backdrop dimming replaces the overlay div, animated close preserved,
  outside-click detection via the panel box; RTL slide direction kept
- state/queue.ts: session-scoped queue of trackIds with position lookup
  and end-of-track dequeue
- every episode row gets a queue toggle (new ic-queue icon); while queued
  it shows the 1-based position as an accent chip; second tap removes
- on 'ended' the queue is consulted before plain auto-next; opening a
  different feed clears it
- btn_queue / queued i18n keys in all 8 languages; smoke extended to 7/7
Brand:
- new icon: single-stroke geometric "S" (two 270° arcs, round caps) on the
  violet gradient tile; master SVG at public/icons/seseri.svg, all PNG
  variants (any 192/512, maskable 192/512 with safe zone, monochrome for
  Android themed icons) generated by scripts/icons.cjs via headless Edge
- in-app logo: the S draws itself once on load (pathLength dash animation),
  then rests; reduced-motion collapses it to the final frame; SVG favicon

Store readiness (P6):
- manifest completed: id, categories, dir, display_override
  (window-controls-overlay), launch_handler navigate-existing, maskable +
  monochrome icons, wide/narrow screenshots (scripts/store-shots.cjs),
  shortcuts — "?resume=1" actually works (last feed persisted as
  pp_last_feed and reopened at boot)
- .well-known/assetlinks.json template for the Play TWA
- docs/STORE.md: full Turkish release guide (Worker deploy → Pages deploy →
  PWABuilder MSIX for Microsoft Store → TWA for Google Play, update flow,
  release checklist)

Quality (P7):
- new suites: lib/format (time/duration) and feeds/proxy-chain (first-win,
  all-down, abort, per-attempt timeout via stubbed fetch) — 51 frontend +
  22 worker tests; docs/ci.yml verify workflow kept local until push
- smoke-p3 fixed for the queue button sharing .ep-dl-btn (data-act selector)

Housekeeping:
- removed root duplicates of public/ files (404.html, privacy-policy.html,
  icons/); pages.yml now builds with Node and deploys dist/ (reads the
  VITE_API_BASE repo variable)
- README rewritten for the v3 architecture; CHANGELOG 3.0.0 (version was
  behind the legacy 2.3.0 line); CONTRIBUTING/SECURITY updated; TESTPLAN
  gained v3 sections (offline, mini player/queue, two-pane, worker)
…proxy

Search by name:
- worker GET /v1/yt/search?q= — Innertube search first, health-checked
  Piped pool as fallback; normalized {kind,id,title,author,thumb,extra}
- client: iTunes and YouTube searched in parallel; results render in
  "Podcasts" / "YouTube" sections (channels, playlists, videos with
  duration/count badges); rows open straight into the yt feed
- ytServiceSearch falls back to public Piped when the Worker is down;
  sec_podcasts/sec_youtube i18n keys in all 8 languages

Background / lock-screen playback:
- worker/src/innertube.ts: youtubei.js session (rebound fetch for workerd,
  player retrieval); tubeAudio races IOS/TV/ANDROID/MWEB/WEB clients until
  one yields a decipherable audio format (SABR-era: some clients only
  expose server-ABR); await-based decipher, URL validation
- GET /v1/yt/audio?id= streams the bytes range-aware through the worker
  with the minting client's UA (googlevideo rejects foreign UAs and
  range-less requests — full-file requests are sent as bytes=0- and
  unwrapped back to 200); format URL cached in KV 30 min with 403-retry;
  rate-limit exempt (seeking bursts range requests)
- /v1/yt/resolve now hands the client the proxy URL when Innertube
  resolves (stream URLs are IP-bound to the worker), else the old pool
  path; CSP media-src gains http://127.0.0.1:8787 for local dev

Verified E2E (scripts/smoke-p8-ytsearch.cjs, 3/3): "lex fridman podcast" →
YouTube section renders → channel opens (10 eps) → REAL AUDIO plays through
/v1/yt/audio (background-capable <audio> + Media Session). When YouTube
blocks the proxy (PO-token enforcement varies), the chain degrades to
Piped → official embed exactly as before.

Tests: youtubei.js stubbed in vitest-pool (import-attribute bundling);
+2 validation cases — 51 frontend + 23 worker green.
- innertube: clients reordered (TV/embedded first) and every candidate
  stream URL now passes a MID-FILE range probe before being accepted —
  under PO-token enforcement most clients only serve the first chunk,
  which would truncate playback ~1 min in; the probe guarantees the
  /v1/yt/audio proxy only activates when it will actually stream
- audio chunks fetched via googlevideo's own `range=` query param in 1 MB
  slices; failed resolutions negative-cached in KV (15 min) so
  /v1/yt/resolve falls through to the Piped pool quickly
- protocol-relative Innertube thumbnails normalized to https
- CSP connect-src += https://seseri-api.bozdogancanahmet.workers.dev
- scripts/smoke-live.cjs: production smoke — 7/7 against the live site
  (search via deployed worker, YouTube section, 41-ep feed, real playback,
  zero CSP violations)
- desktop/: Tauri v2 shell (loads the live site; NSIS installer) + STORE.md
  rewritten: Microsoft Store replaced by direct Windows installer flow

Site deployed: github.com/IACBI/seseri @ 0c0d463 → iacbi.github.io/seseri
(v3 build with VITE_API_BASE baked in).
IACBI added 18 commits July 5, 2026 12:28
Tauri NSIS installer (1.8 MB) built, smoke-launched (app.exe + WebView2 up),
published: https://github.com/IACBI/seseri/releases/tag/v3.0.0
The old flow awaited both sources before painting anything, so a cold
worker-side YouTube search (15 s+) held the iTunes results hostage — on
slow runs the search looked dead. Each source now renders into its own
section the moment it settles; the empty/error state only shows when both
came back empty. Global search abort raised 10 s → 30 s (podcasts paint
long before it fires).

Deployed to iacbi.github.io/seseri; live smoke 7/7 against the user's
redeployed worker (probe-validated YT audio path live).
…kage metadata

- docs/ci.yml -> .github/workflows/ci.yml (verify gate now runs on push/PR)
- remove superseded docs/screens-v1/ (no references anywhere)
- desktop/package.json: real name/description/author, MIT license, drop
  nonexistent main and failing default test script
- src/ui/states.ts: single stateBox(loading|empty|error) used by search and
  player; error variant gets role=alert + .empty-state--error (replaces
  inline color styles); search errors now offer a retry button
- search result rows: role=button, tabindex, Enter/Space activation
  (previously mouse-only)
- results list announces changes via aria-live=polite
- settings close button: localized aria-label (new 'close' i18n key, 8 langs)
- :focus-visible rings for s-select, s-range, lang-select (outline:none
  had removed them with no replacement)
- light theme --text3 #6e7488 -> #62687c (>=4.5:1 WCAG AA on bg/surface)
At <=520px the label wrapped into 2-3 lines next to the filter input
(three lines at 320px). The toggle button still shows the direction.
…lient

Every YouTube episode had fallen back to the iframe embed (which mobile
browsers pause on screen lock) because stream resolution broke on all
fronts: PO-token enforcement caps IOS/MWEB/WEB URLs at the first ~2MB
(mid-file ranges 403), the TV/embedded clients now fail playability or
need a JS evaluator workerd can't provide, and the public Piped/Invidious
pool is dead (verified: all 12 instances 4xx/5xx).

Worker changes, each verified against live googlevideo endpoints:
- resolve via ANDROID_VR (Meta Quest) — the remaining PO-token-exempt
  client: direct URLs, full-range (mid-file 1MB chunk 200, ~3MB/s)
- generate_session_locally: false — locally generated visitor data now
  trips the 'Sign in to confirm you're not a bot' wall
- retrieve_player: false — no cipher needed for direct URLs; also cuts
  the CPU spikes that caused intermittent 1102s
- drop dead clients; don't cache a failed Innertube.create for the
  isolate's lifetime

Client: one-time toast when the embed fallback is used (region-locked
videos), warning that lock-screen playback may stop (new i18n key
yt_embed_bg, 8 languages).
Geo-blocks, bot walls and unplayable videos all surface as a bare
"no stream" to the client; log the real reason to wrangler tail so the
datacenter-IP bot wall is diagnosable in production.
…s from prod CSP

- fetchWithTimeout now follows redirects manually (max 3 hops) and runs
  safeTarget() on every Location target — closes redirect-based SSRF.
- Build-only Vite plugin removes http://127.0.0.1:8787 and bare ws: from
  the shipped CSP meta (fails the build on token drift; dev untouched).
- SECURITY.md documents the fix and residual DNS-rebinding risk.
…d keys

Adds confirm_*, queue_*, offline_banner, toast_*_exported and
lang_select_label; removes unused brand_sub / search_hint.
…ompt

Replaces native confirm(); adds missing confirmation on clear-downloads
and unsubscribe. Cancel is default-focused; Esc/backdrop cancel. Also
success toasts on OPML/JSON export.
Feed open lands focus on the title, back restores the originating row;
deep links don't steal focus; language/speed selects labelled; result
and episode lists announce aria-busy while loading.
…, safe areas

- components.css consumes the existing fs/s/r/d tokens (exact matches only)
- one --z-* scale replaces magic z-indexes
- direction-relevant physical properties -> logical (Arabic mirrors)
- interactive controls extended to >=44px hit areas, visuals unchanged
- viewport-fit=cover + safe-area-inset-top on header/topbar/drawer
- queue panel + offline banner styles
The queue logic existed with no UI. A sort-bar dropdown now lists queued
episodes with keyboard-accessible up/down/remove/clear and a count badge;
pure moveInQueue() added to state. Offline banner shows while the network
is down. player.ts carries the deferred post-3.1.0 split plan as a note.
…resolve, theme parity

Frontend unit tests 51 -> 104; worker 22 -> 26 (redirect guard).
…re docs

- all four version fields (root, tauri.conf, Cargo, desktop pkg) -> 3.1.0
- Cargo scaffold metadata replaced (crate seseri, real author/license/repo)
- STORE.md: iOS roadmap section, KV-id de-drift, updated checklist
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…etired

main carried the pre-3.0 deployed-site layout (built assets committed at
the repo root, pages.yml publishing '.'). The 3.x layout builds from
source (pages.yml publishes dist/), so the merge keeps the source tree
and drops the stale root artifacts; public/ remains the single origin of
manifest/icons/screenshots/404/privacy files.
@IACBI
IACBI force-pushed the redesign/seseri-2.0 branch from e7c83bc to 2a86981 Compare July 8, 2026 12:44
IACBI added 2 commits July 8, 2026 15:49
…CI failure

The mock stored the original Response, sharing its one-shot body stream
across reads; real Cache API semantics copy on put() and mint a fresh
Response on match(). Passed on Windows by luck, failed on ubuntu CI.
…ses broke CI

jsdom's Blob passed into Node's (undici) Response throws TypeError on the
CI Node build, sending downloadOffline into its cors-blocked branch. The
suite now stubs Response/fetch/caches with plain fakes so no real fetch
classes mix across realms; behavior assertions unchanged.
@IACBI
IACBI merged commit 0fae97e into main Jul 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant