- Add ARCHITECTURE.md with full system overview (data flow, modules, build) - Add AGENTS.md as a guide for future AI agents working on this project - Split monolithic styles.css into focused files under assets/: variables, animations, layout, navbar, header, cards, config, filters, utils, greet - Fix justfile: replace `cargo run tauri dev/build` with `cargo tauri dev/build` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.8 KiB
Agent Guide: marstemedia
This document is for AI agents working on this codebase. Read it before touching anything.
What is this project?
marstemedia is a cross-platform desktop (+ Android) news reader app built with:
- Tauri v2 as the native shell
- Yew (Rust → WebAssembly) for the UI
- Rust for all backend logic (networking, file I/O, cryptography)
It has three features:
- Nostr feed — anonymous browsing of decentralized social posts, filterable by hashtag
- RSS reader — fetches and displays articles from a user-configurable list of RSS/Atom feeds
- AI news — one-click tech news summary from Groq (Llama 3) or OpenRouter
Repository Layout
marstemedia/
├── src/ # Frontend (Yew/Wasm)
│ ├── main.rs # Wasm bootstrap
│ ├── app.rs # Router + top-level layout
│ ├── navbar/
│ │ ├── mod.rs
│ │ └── navbar.rs # Bottom navigation dock
│ └── pages/
│ ├── mod.rs
│ ├── home.rs # Nostr feed page
│ ├── news.rs # RSS + AI news page
│ └── greet.rs # Placeholder/demo page
│
├── src-tauri/ # Backend (native Rust)
│ ├── Cargo.toml
│ ├── tauri.conf.json # App config (window size, build commands)
│ ├── capabilities/
│ │ └── default.json # Tauri capability permissions
│ └── src/
│ ├── main.rs # Desktop executable entry
│ ├── lib.rs # Tauri setup, command registration
│ ├── home.rs # Nostr: fetch_nostr_posts command
│ └── news.rs # RSS + AI: all news commands + NewsState
│ └── easy-nostr/ # Local path-dependency (nostr-sdk wrapper)
│
├── Cargo.toml # Frontend workspace root
├── styles.css # All CSS for the app
├── index.html # Trunk entry point
├── Trunk.toml # Trunk (Wasm bundler) config
├── justfile # Dev shortcuts
└── .gitea/workflows/
└── android.yaml # CI: builds Android APK
How to Add a New Page
- Create
src/pages/yourpage.rsas a Yew#[function_component]. - Export it in
src/pages/mod.rs:pub mod yourpage; - Add a variant to the
Routeenum insrc/app.rs:#[at("/yourpage")] YourPage, - Add a match arm in the
switchfunction insrc/app.rs:Route::YourPage => html! { <YourPage /> }, - Add a
<Link<Route>>insrc/navbar/navbar.rs.
How to Add a New Backend Command
- Write your async function in the appropriate module (
src-tauri/src/home.rs,news.rs, or a new file). - Annotate it with
#[tauri::command]. - If you need shared state, add a field to
NewsStateinsrc-tauri/src/news.rs, or create a new state struct and call.manage()inlib.rs. - Register the command in
src-tauri/src/lib.rsinsidetauri::generate_handler![...]. - In the frontend, call it with
invoke("your_command_name", args).
How to Call a Backend Command from the Frontend
Every page that needs backend calls declares this extern at the top:
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], catch)]
async fn invoke(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;
}
Serialize args with serde_wasm_bindgen::to_value(...), deserialize results with serde_wasm_bindgen::from_value(...). Use spawn_local to run async calls inside Yew callbacks.
Key Patterns to Know
State management (frontend)
Yew uses use_state hooks. There is no global store — state lives inside components. If two pages need to share data, consider lifting state to app.rs or using Tauri's managed state on the backend.
State management (backend)
NewsState in src-tauri/src/news.rs is registered with .manage() and injected into commands via State<'_, NewsState>. All fields are Mutex<T> — always lock, read/write, and drop the lock quickly. API keys are in-memory only (not saved to disk).
Config persistence
RSS feeds are saved to <app_config_dir>/rss.ron using the ron crate. Access the config path via app.path().app_config_dir(). Pattern is: read on load, write on save, keep in-memory state in sync.
Markdown rendering
The News page renders article content as Markdown using pulldown-cmark. The output is injected with Html::from_html_unchecked. Only use this for content you control or have sanitized.
Privacy pattern (Nostr)
A new random keypair is generated on every fetch_nostr_posts call. This is intentional — do not change this to a persistent key without understanding the privacy implications.
CSS
styles.css in the project root is the single entry point — it only contains @import statements. All actual styles are split into files under assets/:
| File | What's inside |
|---|---|
assets/variables.css |
CSS custom properties (colors, fonts), reset, body/html |
assets/layout.css |
.feed-container, responsive media queries |
assets/navbar.css |
.navbar-dock, .nav-link |
assets/header.css |
.feed-header, .mode-toggle, .toggle-btn, .action-btn, .provider-switch |
assets/cards.css |
.post-card, .markdown-body, .post-category-badge |
assets/config.css |
.config-panel, RSS list, inputs, .add-btn, .delete-btn, .save-master-btn |
assets/filters.css |
Hashtag chips (Home), .category-bar, .cat-btn (News) |
assets/animations.css |
@keyframes float, slideIn, pulse-subtle |
assets/utils.css |
.reload-btn-large, .error-box, .loading-spinner, .status-msg |
assets/greet.css |
.robot-container, .robot-icon, .bubble |
When adding styles for a new page, create a new file in assets/ and add an @import line to styles.css. There is no CSS framework — vanilla CSS with custom classes.
Build & Dev
# Start dev server (hot reload)
cargo tauri dev
# Build for desktop release
cargo tauri build
# Build for Android (requires Android SDK + NDK)
cargo tauri android build
# Frontend only (no Tauri shell)
trunk serve
The justfile may contain additional shortcuts — check it first.
Dependencies Worth Knowing
| Crate | Used for |
|---|---|
yew |
UI components |
yew-router |
Client-side routing |
wasm-bindgen / wasm-bindgen-futures |
Wasm ↔ JS bridge |
serde-wasm-bindgen |
Serialize/deserialize across the Tauri IPC |
pulldown-cmark |
Markdown → HTML in the frontend |
nostr-sdk |
Nostr protocol (via easy-nostr wrapper) |
reqwest |
HTTP client (rustls, no OpenSSL) |
feed-rs |
RSS/Atom feed parsing |
ron |
Config file serialization |
tauri-plugin-opener |
Open URLs in system browser |
What to Watch Out For
easy-nostris a local path dependency atsrc-tauri/easy-nostr/. It is also a private Git repo cloned by CI. If you need to update it, clone it manually into that path.- No OpenSSL — both
reqwestusages (frontend and backend) userustls-tlswithdefault-features = false. Keep it that way to avoid cross-compilation pain on Android/CI. wasm32-unknown-unknowntarget is required for the frontend.aarch64-linux-androidand friends are required for Android CI.- The
Greetpage (src/pages/greet.rs) appears to be a leftover demo page from the Tauri template. It is still linked in the navbar — decide whether to keep or remove it. - API keys entered by the user are held in
Mutex<String>fields inNewsState. They are lost when the app restarts. There is no persistence for keys by design.