Files
taninux/HANDOVER.md
T
karim 30899de5a3 gui: native-backend session work + fix shim crashes; add page smoke test
Bundles the pending native-UI session (dark shell, style overhaul, audio
page) with review fixes:
- native Row: set_subtitle_lines + set_activatable_widget (store and
  online-accounts pages crashed with AttributeError under TANINUX_UI=native)
- wine page routed through the ui seam (last direct Adw import removed)
- console: delete no-op log_expander/collapsible_log and .clear() call sites
- ui/__init__ docstring now states the actual default backend (native)
- tests/test_pages_native.py builds every registered page under the native
  backend (29 pass; skips headless)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:21:52 +02:00

16 KiB

TANINUX — Handover (Session 2026-06-23)

Context for a fresh instance. Repo: ~/projects/taninux (distro codename camel, niri-only GTK4/libadwaita + eww desktop). User: Karim (German; iterates visually; hates UI jank; blanket perms in ~/projects). UI strings are English.


THIS SESSION (2026-06-23) — Native GTK4 shell, CSS overhaul

TANINUX_UI=native means the app uses src/taninux/gui/ui/native.py widgets and loads src/taninux/gui/ui/style_apple.css instead of libadwaita. This session fixed the native theme comprehensively.

What was done

src/taninux/gui/ui/style_apple.css

  • All hardcoded blue accents (#007aff, #0a84ff, rgba(0,122,255…)) replaced with @accent_color + alpha(@accent_color, 0.10).
  • Dark headerbar: rgba(20,20,22,0.97) with box-shadow: none (matches sidebar, no GNOME "lighter band" effect).
  • Light headerbar: #ebebeb. Light sidebar: #ebebeb. Light page bg: #f6f6f6 (matches libadwaita white).
  • Explicit light-mode text colors (color: #1d1d1f) for all nav rows, nav header, group titles — because GTK4 Adwaita can leave text light-colored in light mode.
  • button.suggested-actionbackground-color: @accent_color !important (was blue).
  • progress, trough > progressbackground-color: @accent_color !important.
  • Nav selection dark: glass wash alpha(@accent_color, 0.18) + border.

src/taninux/gui/style.py

  • apply() (native backend): now calls Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", dark) so GTK4 Adwaita widgets (buttons, sliders) follow the chosen scheme.
  • _build_css() swatch buttons: selector is now button.fuji-swatch.swatch-{key} with background-color: {hex} !important; background: {hex} !important; — fixes swatches appearing black (Adwaita overrode them without !important).
  • Swatch checked state: uses outline: 2px solid {hex}; outline-offset: 3px (removed box-shadow: inset 0 0 0 2px @window_bg_color which was undefined in native mode).
  • _set_gsettings(): writes TANINUX-dark/TANINUX as the GTK3 theme (for greetd/GTK3 apps). Also sets color-scheme=prefer-dark/prefer-light. ⚠ This is SYSTEM-WIDE — affects eww and all other GTK4 apps.

src/taninux/gui/pages/settings.py

  • persist(): after saving the color scheme, now also calls native.set_dark(root, …) live so the theme switches immediately without restart.

src/taninux/gui/console.py — complete rewrite

  • LogConsole is now a plain Python class (NO GTK widget). Writes to ~/.local/share/taninux/taninux.log with timestamps.
  • RunnerCard: button only (no embedded log widget). Process output goes to _write_log().
  • log_expander() and collapsible_log(): kept as no-ops returning None — all callers already cleaned up.

Log expanders removed from all settings pages: backup.py, update.py, kernel.py, maintain.py, network.py, bluetooth.py, shares.py, bundles.py, appdetail.py, store.py, printers.py — all formerly called log_expander(…) which is now a no-op; the UI no longer has a "Log" section on any page.

PKGBUILDs (packaging/taninux/PKGBUILD, packaging/tanin-desktop/PKGBUILD)

  • networkmanager, bluez-utils, wireplumber, brightnessctl, wlsunset, cups, pacman-contrib moved from optdependsdepends in taninux/PKGBUILD.
  • wlsunset, cups moved from optdependsdepends in tanin-desktop/PKGBUILD.
  • paru, flatpak, timeshift remain optdepends.

Files touched this session

src/taninux/gui/ui/style_apple.css
src/taninux/gui/style.py
src/taninux/gui/pages/settings.py
src/taninux/gui/console.py
src/taninux/gui/pages/{backup,update,kernel,maintain,network,bluetooth,shares,
  bundles,appdetail,store,printers}.py
packaging/taninux/PKGBUILD
packaging/tanin-desktop/PKGBUILD

Current state

Thing State
Native CSS theme (dark + light) fixed, live
Accent colors correct (@accent_color)
Live dark/light switch in Settings
Log expanders removed everywhere
PKGBUILDs updated source only
taninux package rebuilt needs ./packaging/finish-tanin-repo.sh
tanin-icons built+committed (c5b0848) from previous session
[tanin] repo published blocked on TANIN_PKG_TOKEN

THE CRITICAL BUG THIS SESSION — awww-daemon black screen

Symptom: Entire desktop goes black (no wallpaper, no windows visible). Opening any eww panel made it worse (visible as black backdrop).

Root cause: awww (the blurred-wallpaper daemon used for niri's Overview backdrop) creates a surface at the Wayland Background layer. After suspend/ resume, awww's cached blurred image goes stale or the surface loses its content → the surface renders as solid black. Because awww-daemon sits ABOVE swaybg in the Background layer stack, it covers the wallpaper entirely.

The transparent panel-backdrop was fine — what appeared "black" was the awww surface showing through the transparent backdrop, covering the wallpaper behind it.

Fix (immediate): pkill awww

  • This kills the awww daemon, the black surface disappears from niri's Background layer, swaybg's wallpaper shows through again.

Permanent fix needed (TODO for next session): Option A — Regenerate blur cache on resume: Add a systemd user sleep hook that calls ~/projects/eww/scripts/overview-backdrop.sh sync on post-resume. File: ~/.config/systemd/user/awww-refresh.service + .path. Option B — Make awww not start at login (only at Overview open): Remove awww from launch.sh; call overview-backdrop.sh start only when niri's Overview is about to open. niri has no "overview-opening" IPC event yet → tricky. Option C — Restart awww after resume with a fresh cache: In the sleep hook: pkill awww; sleep 0.5; overview-backdrop.sh start.

Diagnosis dead-ends (don't re-investigate):

  • NOT caused by our ~/.config/gtk-4.0/libadwaita.css (tested: removed → still black)
  • NOT caused by color-scheme=prefer-dark gsettings (tested: default → still black)
  • NOT caused by GSK renderer (GSK_RENDERER=cairo → still black)
  • CONFIRMED: pkill awww → wallpaper returns instantly

CURRENT SYSTEM STATE (after this session's debugging)

gsettings were modified during debugging. Restore if needed:

gsettings set org.gnome.desktop.interface color-scheme prefer-dark
gsettings set org.gnome.desktop.interface gtk-theme TANINUX-dark

The awww daemon was killed (pkill awww). The desktop now shows swaybg's wallpaper correctly. Restarting eww (~/projects/eww/launch.sh) will restart awww via launch.sh — which may make the black screen return. If it does: pkill awww.


PREVIOUS SESSION (2026-06-22) — Icon theme

A. New packages in the [tanin] repo

  • librewolf-bin, arch-update, timeshift (+ timeshift-autosnap), xdg-terminal-exec added to AUR rebuild list.
  • paru built into repo but stays optdepend.
  • papirus-folders removed.

B. Icon theme: tanin-icons

Custom package packaging/tanin-icons/ — 5 thin Adwaita-based themes (Tanin-<accent>) that recolour only folder/place icons in Fuji accent hues. Built + committed as c5b0848. Installed to ~/.local/share/icons/Tanin-* (user dir, not pacman). style.py_set_gsettings() sets icon-theme=Tanin-<accent_key> on accent change. tanin-desktop/PKGBUILD: depends +tanin-icons, removed papirus-icon-theme. ⚠ NOT yet in the published [tanin] repo (needs TANIN_PKG_TOKEN).


Gotchas to respect

  • NEVER run lock/swaylock/suspend commands to test (locked Karim out once). Exception: Karim explicitly authorised sudo systemctl suspend in the 2026-06-23 session — that's not a standing permission.
  • write_global_theme() writes ~/.config/gtk-4.0/libadwaita.css which is read by ALL GTK4 apps including eww (USER priority 800 — above GTK theme). It only defines @define-color vars and button border-radius; it does NOT define window backgrounds. The globals are safe.
  • TANINUX tree has unrelated uncommitted files (tanin-calendar, online_accounts, gtklock, iso/) — NOT from this session; do NOT git add -A.
  • GTK3 can't exact-Fuji recolour at runtime (parse-time @define-color limit).

Open / not done

  • Permanent fix for awww black screen after suspend (see above).
  • Rebuild + publish [tanin] repo (needs TANIN_PKG_TOKEN env).
  • Rebuild ISO.
  • camel.toml rebuilt_aur list is stale (missing librewolf-bin, arch-update, timeshift-autosnap, xdg-terminal-exec).
  • Manual icon picker in desktop.py still lists all 5 Tanin-* themes (minor).

SESSION 2026-06-27 — TANINUX Music scaffold (native TIDAL client, "Weg 3")

New package src/taninux/music/ — a native Apple-Music-style TIDAL client. Strategy: reuse High Tide's UI-free backend, build our own native shell (no libadwaita fork). High Tide's shell is built on heavy Adw layout widgets (NavigationView, OverlaySplitView, MultiLayoutView, Breakpoints, Carousel) that have no plain-GTK4 equivalent and that our native.py does NOT cover — so forking its UI was rejected. Its backend (~1650 LOC: GStreamer player, tidalapi session, token store) is cleanly decoupled and worth reusing.

Structure

  • backend/player.py — copied verbatim from High Tide's lib/player_object.py (GPL-3.0), except ONE edit: from . import discord_rpc, utilsfrom . import discord_rpc + from . import state as utils. Player's only coupling to the old god-utils was 4 names (CACHE_DIR, MUSIC_DIR, session, send_toast) — all provided by state.py.
  • backend/state.py — slim shim providing those 4 names + init()/set_session()/set_toast_sink().
  • backend/secret_store.py — copied from High Tide (LGPL-3.0), schema/key renamed to TANINUX.
  • backend/auth.py — extracted PKCE flow (was in an Adw.Dialog): Auth.try_restore() / pkce_login_url() / finish_pkce_login() + login_async().
  • backend/discord_rpc.py — no-op stub (player calls set_activity/disconnect).
  • ui/transport.py — bottom now-playing bar, binds to player GObject signals (song-changed, update-slider, notify::playing, can-go-*). Async cover fetch.
  • ui/library.py — favorites-tracks list; row-activate → player.play_this(tracks, i).
  • ui/login_dialog.py — native ui.Dialog PKCE login (tidalapi imported lazily inside).
  • window.py — shell: sidebar (nav) + Gtk.Stack content + transport bar. tn-dark.
  • app.py — entry tmusic. Lazy-imports player/auth (so app loads without tidalapi → shows "backend missing" StatusPage). Flow: CSS → state.init → Auth → try_restore? → shell ±login.
  • CSS appended to gui/ui/style_apple.css (section "TANINUX Music"): .tn-transport, .tn-content, .tn-track-list, .tn-music-sidebar. Reuses existing .tn-sidebar.
  • Entry point added to pyproject.toml: tmusic = "taninux.music.app:main".

Verified

  • All new files py_compile clean. Backend state/discord_rpc import without tidalapi/Adw.
  • All UI+app modules import without tidalapi (lazy imports hold).

NOT verified (needs real env)

  • End-to-end playback: tidalapi is NOT installed here, and there's no TIDAL account/display in the sandbox. To run: pacman -S python-tidalapi (or pip install tidalapi), then PYTHONPATH=src python -m taninux.music (or tmusic after install). Expect: login dialog → paste redirect URL → library loads → click track → GStreamer plays → transport updates.

Next building blocks (all go into the content Gtk.Stack)

  • Home page (mixes/recommendations carousels) and Search page (currently placeholders).
  • The "few missing native layout widgets" if richer nav is wanted: a NavigationStack (push/pop with back button) and horizontal carousels. v1 deliberately uses a flat Gtk.Stack.
  • Toast sink: state.set_toast_sink(...) is wired but app currently leaves the default (logs).

SESSION 2026-06-27 (cont.) — TANINUX Music FULL build + playback verified

User gave full autonomy ("entwickle die app komplett aus"). The scaffold above is now a working app. Playback verified end-to-end via a headless test (logged-in PKCE session → favorite track → GStreamer pipeline reached PLAYING, position advanced to 8.2s).

Login: must be PKCE, not device-flow (IMPORTANT)

  • tidalapi has TWO clients. The OAuth device-flow (session.login_oauth(), the smooth link.tidal.com/XXXXX flow) logs in and can BROWSE, but cannot stream — playback hits 429 Too Many Requests on playbackinfopostpaywall. tidalapi docstring: PKCE "is the only way" to stream.
  • So login uses PKCE (auth.pkce_login_url() → browser → copy the redirect/"Oops" URL → finish_pkce_login). LoginDialog auto-opens the browser, shows a clickable link, AND watches the clipboard (Gdk.Clipboard.read_text_async poll) so pasting is automatic.
  • auth.try_restore() discards any stored NON-pkce token (is-pkce flag in keyring) to force a PKCE re-login. Tokens persist in libsecret → normally no dialog on subsequent launches.

Runtime deps installed this session (pacman)

  • python-tidalapi (extra) — the API/player lib.
  • gst-libav + gst-plugins-bad + gst-plugins-uglyrequired for audio. Without them GStreamer can't decode TIDAL's AAC/DASH streams ("No suitable plugins found", no sound). Key elements now present: avdec_aac, avdec_flac, faad, dashdemux2.
  • These are NOT expressible in pyproject; package them as distro deps for the [tanin] repo.

Architecture (all under src/taninux/music/)

  • backend/api.py — thin tidalapi data layer (UI-free): home/explore/for_you_categories, search, favorites/playlists, album_tracks/playlist_tracks/mix_items/artist_*, item classification (item_kind/title/subtitle), IdCache. NOTE: session.home() returns 400 sometimes → home_categories() falls back to legacy endpoint, and HomePage also pulls explore() + for_you().
  • ui/images.py — async cover loader, disk cache under state.IMG_DIR, sets Gdk.Texture on Gtk.Picture.
  • ui/nav.pyNavigationStack (back button + title + content; set_root/push/pop). Replaces Adw.NavigationView.
  • ui/context.pyMusicContext(session, player, nav, toast); open_item(item) classifies + pushes the right detail page; play/shuffle. Passed to all pages/widgets (no globals).
  • ui/widgets.pyCard (cover+title+sub, click→open_item), Carousel (titled h-scroll), TrackList (row-activate → play).
  • ui/pages_base.pyLoadablePage (spinner → thread fetch() → main-thread build()).
  • ui/pages_home.py, pages_search.py (live clipboard-less SearchEntry → debounced search), pages_detail.py (Album/Playlist/Mix share _TrackDetailPage; ArtistPage), pages_collection.py (Bibliothek: playlists/albums/artists/tracks), pages_queue.py.
  • window.py — shell: sidebar (Home/Suchen/Bibliothek) → nav.set_root; Gtk.Overlay for toasts; TransportBar at bottom. reload_after_login().
  • ui/transport.py — now-playing bar: cover, prev/play/next, seek, shuffle/repeat/queue, volume.
  • backend/mpris.py — ported from High Tide, DECOUPLED: MPRIS(player, app_id, raise_cb, quit_cb), cover path via state.IMG_DIR. Verified on bus as org.mpris.MediaPlayer2.ch.gabrielevarano.Taninux.Music.
  • app.py — entry tmusic. CSS → state.init → Auth → try_restore? → shell ±PKCE login; wires toast sink + starts MPRIS (both non-fatal).
  • CSS: "TANINUX Music" section in gui/ui/style_apple.css (cards/covers/carousel/transport/ toast/nav + title-1/title-3/dim-label/error fallbacks).

Known minor issues / TODO

  • Cosmetic GTK warning "GtkGizmo (slider) reported min width -4" from a Gtk.Scale — harmless.
  • Per-section nav stacks are not preserved: switching sidebar resets that section to its root (Apple keeps a stack per tab). Acceptable for v1.
  • Queue page is read-only (no reorder/remove). No lyrics page. discord_rpc is a no-op stub.
  • DONE (2026-06-27 cont.): track→album/artist navigation — TrackRow has a "⋯" MenuButton + right-click popover (Zum Album / Zum Interpreten / Als Nächstes / Zur Warteschlange); the transport bar's title→album and artist→artist are clickable (.tn-link). Carousel cards already opened detail pages. Sidebar switched to the Settings .tn-nav look (glassy accent wash + a frost gradient on window.tn-dark .tn-sidebar).
  • Search is debounced via SearchEntry's search-changed; could add a small delay/throttle.