docs: add ARCHITECTURE.md and AGENTS.md, split CSS, fix justfile
- 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>
This commit is contained in:
186
AGENTS.md
Normal file
186
AGENTS.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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:
|
||||
1. **Nostr feed** — anonymous browsing of decentralized social posts, filterable by hashtag
|
||||
2. **RSS reader** — fetches and displays articles from a user-configurable list of RSS/Atom feeds
|
||||
3. **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
|
||||
|
||||
1. Create `src/pages/yourpage.rs` as a Yew `#[function_component]`.
|
||||
2. Export it in `src/pages/mod.rs`: `pub mod yourpage;`
|
||||
3. Add a variant to the `Route` enum in `src/app.rs`:
|
||||
```rust
|
||||
#[at("/yourpage")]
|
||||
YourPage,
|
||||
```
|
||||
4. Add a match arm in the `switch` function in `src/app.rs`:
|
||||
```rust
|
||||
Route::YourPage => html! { <YourPage /> },
|
||||
```
|
||||
5. Add a `<Link<Route>>` in `src/navbar/navbar.rs`.
|
||||
|
||||
---
|
||||
|
||||
## How to Add a New Backend Command
|
||||
|
||||
1. Write your async function in the appropriate module (`src-tauri/src/home.rs`, `news.rs`, or a new file).
|
||||
2. Annotate it with `#[tauri::command]`.
|
||||
3. If you need shared state, add a field to `NewsState` in `src-tauri/src/news.rs`, or create a new state struct and call `.manage()` in `lib.rs`.
|
||||
4. Register the command in `src-tauri/src/lib.rs` inside `tauri::generate_handler![...]`.
|
||||
5. 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:
|
||||
|
||||
```rust
|
||||
#[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
|
||||
|
||||
```bash
|
||||
# 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-nostr`** is a local path dependency at `src-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 `reqwest` usages (frontend and backend) use `rustls-tls` with `default-features = false`. Keep it that way to avoid cross-compilation pain on Android/CI.
|
||||
- **`wasm32-unknown-unknown`** target is required for the frontend. `aarch64-linux-android` and friends are required for Android CI.
|
||||
- The `Greet` page (`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 in `NewsState`. They are lost when the app restarts. There is no persistence for keys by design.
|
||||
Reference in New Issue
Block a user