Sidebar layout:
- Replace AdwNavigationSplitView with GtkPaned for a resizable sidebar
with a persistent width stored in GSettings.
- Apply navigation-sidebar CSS class to the content Stack only (not the
ToolbarView) so both header bars share the same colour and height.
- Override Adwaita's automatic paned-first-child header tint and gap via
application-level CSS.
- Remove the gap between the sidebar header and the first list item.
- Add toggle-sidebar button and F9 shortcut; sidebar visibility and width
are persisted across restarts.
Loading indicator:
- Replace the large AdwSpinner status page + header Stack with a small
Gtk.Spinner (16×16) in the header Stack so the header height never
changes during loading.
Article row:
- Add hexpand to title and excerpt labels so text reflows when the
sidebar is resized.
Content:
- Inline CSS into the HTML template at load time (/*INJECT_CSS*/
placeholder) so WebKit does not need a custom URI scheme handler.
- Fix max-width centering and padding for article body and header.
- Fix embedded video/iframe auto-opening in browser by checking
NavigationType::LinkClicked instead of is_user_gesture().
Content filters:
- Add Preferences dialog with a TextView for content-rewrite rules
stored in GSettings (content-filters key).
- Rule format: "domain find replace [find replace …]" one per line.
- Rules are applied to article HTML before display and reloaded on
every refresh.
Shortcuts:
- Add Ctrl+W to close, Ctrl+Q to quit, F1 for keyboard shortcuts
overlay, j/k and arrow-key navigation via a capture-phase controller
so keys work regardless of which widget has focus.
Misc:
- Set window title to "FeedTheMonkey" (fixes Hyprland title bar).
- Update About dialog website URL.
Three bugs fixed:
- No tokio reactor: glib::spawn_future_local does not provide a
tokio context, so reqwest/hyper panicked at runtime. Introduce
src/runtime.rs with a multi-thread tokio Runtime (init() called
from main before the GTK app starts). runtime::spawn() posts the
async result back to GTK via a tokio oneshot channel awaited by
glib::spawn_future_local, which only polls a flag (no I/O).
runtime::spawn_bg() is used for fire-and-forget background calls.
- Enter key didn't submit login: connect_apply on AdwEntryRow only
fires when show-apply-button is true. Switch to connect_entry_activated
which fires on Return in all three login rows.
- Wrong API URL: the app constructed /accounts/ClientLogin directly
off the server host, yielding a 404. Add normalize_base_url() in
api.rs that appends /api/greader.php when the URL doesn't already
contain it, so users can enter just https://rss.example.com.
Add the full application logic on top of the Epic 1 skeleton:
Epic 2 — Authentication
- LoginDialog (AdwDialog, Blueprint template) with server URL,
username, and password fields; emits logged-in signal on submit
- credentials.rs: store/load/clear via libsecret (password_store_sync /
password_search_sync / password_clear_sync, v0_19 feature)
- api.rs: Api::login() parses Auth= token from ClientLogin response;
fetch_write_token() fetches the write token
- Auto-login on startup from stored credentials; logout with
AdwAlertDialog confirmation; login errors shown in AdwAlertDialog
Epic 3 — Article fetching
- model.rs: Article struct and ArticleObject GObject wrapper with
unread property for list store binding
- Api::fetch_unread() deserializes Google Reader JSON, derives unread
from categories, generates plain-text excerpt
- Sidebar uses a GtkStack with placeholder / loading / empty / error /
list pages; AdwSpinnerPaintable while fetching; Try Again button
Epic 4 — Sidebar
- article_row.blp: composite template with feed title, date, title,
and excerpt labels
- ArticleRow GObject subclass; binds ArticleObject, watches unread
notify to apply .dim-label on the title; relative timestamp format
Epic 5 — Content pane
- content.html updated: setArticle(), checkKey(), feedthemonkey: URI
navigation scheme; dark mode via prefers-color-scheme
- content.css: proper article layout, dark mode, code blocks
- WebView loaded from GResource; decide-policy intercepts
feedthemonkey:{next,previous,open} and all external links
Epic 6 — Read state
- Api::mark_read() / mark_unread() via edit-tag endpoint
- Optimistic unread toggle on ArticleObject; background API calls;
mark_unread_guard prevents re-marking on navigation
- AdwToast shown on mark-unread
Epic 7 — Keyboard shortcuts
- GtkShortcutController on window for all shortcuts from the backlog
- shortcuts.blp: AdwShortcutsWindow documenting all shortcuts
- F1 opens shortcuts dialog; Ctrl+W closes window; Ctrl+Q quits
Epic 8 — Zoom
- zoom_in/zoom_out/zoom_reset wired to Ctrl+±/0; zoom level saved to
and restored from GSettings zoom-level key
Epic 9 — Window state persistence
- Window width/height/maximized saved on close, restored on open
- (Sidebar width deferred — AdwNavigationSplitView fraction binding)
Epic 10 — Polish
- AdwAboutDialog with app name, version, GPL-3.0, website
- Logout confirmation AdwAlertDialog with destructive button
- Win.toggle-fullscreen action (F11)
- Api dropped on window close to cancel in-flight requests
Add the full Rust + GTK4 + libadwaita project skeleton:
- Cargo.toml with all dependencies (gtk4 0.11, libadwaita 0.9,
webkit6 0.6, reqwest, serde, tokio, libsecret)
- build.rs that compiles Blueprint .blp files and bundles a GResource
- data/ui/window.blp — AdwApplicationWindow with AdwNavigationSplitView,
sidebar with refresh button/spinner and primary menu,
content page with article menu
- data/resources.gresource.xml bundling UI, HTML, and CSS
- data/net.jeena.FeedTheMonkey.gschema.xml with all GSettings keys
- html/content.html and html/content.css (minimal placeholders)
- src/main.rs, src/app.rs — AdwApplication with APP_ID net.jeena.FeedTheMonkey
- src/window.rs — AdwApplicationWindow GObject subclass loading the
Blueprint template and persisting window size in GSettings
- COPYING (GPL-3.0) restored from master
The app compiles and the binary is ready to open a blank window.