commit 90486b5dd24369cd615a0505e916662ae4f4162d
parent 0cd96c2e93ae77a6642c362847dd98dde150907f
Author: Silas Brack <silasbrack@gmail.com>
Date: Sun, 7 Jun 2026 22:47:45 +0200
docs: update README with current stack, principles, and structure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | README.md | | | 132 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- |
1 file changed, 71 insertions(+), 61 deletions(-)
diff --git a/README.md b/README.md
@@ -1,93 +1,103 @@
# Simple Web App
-A news aggregation web application built with Rust, Axum, Askama, and SQLite.
+A link aggregator (HN clone) built with Rust, Axum, Askama, and Trailbase.
+
+Live at [simple.fnarglebeast.com](https://simple.fnarglebeast.com).
## Tech Stack
-- **Axum** - Web framework
-- **Askama** - Type-safe HTML templating
-- **SQLx** - Async SQL toolkit with compile-time checked queries
-- **HTMX** - Frontend interactivity without JavaScript frameworks
-- **PicoCSS** - Minimal CSS framework
+- **Axum** — Web framework
+- **Askama** — Type-safe HTML templating
+- **Trailbase** — Auth, record APIs, and SQLite storage
+- **HTMX** — Voting without full page reloads
+- **FTS5** — Full-text search via a WASM guest component
+
+## Principles
+
+1. **Explicit over clever** — Code reads top-to-bottom. A new reader should understand what a function does without chasing through layers of indirection.
-## Development Guidelines
+2. **Pure functions** — Isolate decision logic from I/O. Functions like `time_ago`, `hot_score`, `extract_domain`, and `parse_tags` take data and return data with no side effects.
-Configuration should be performed via environment variables following the [12-factor app](https://12factor.net/) principles.
+3. **Linear flow** — Handlers read as sequential steps: authenticate, fetch data, build view, respond. No callbacks or deep nesting.
-## Developer Setup
+4. **Minimize shared state** — `AppState` holds only the trailbase URL and an HTTP client. Everything else is passed explicitly.
-### With Nix (recommended)
+5. **Minimize indirection** — No traits, no dependency injection, no "in case we swap implementations later." Direct function calls throughout.
-1. Copy the example environment file:
- ```bash
- cp .env.example .env
- ```
+## Development
+
+```bash
+nix develop
+cargo run # start server
+cargo watch -x run # auto-reload
+```
-2. Enter the development shell:
- ```bash
- nix develop
- ```
+Trailbase runs separately:
-3. Run the server:
- ```bash
- cargo run
- ```
+```bash
+trail run
+```
-4. For auto-reload during development:
- ```bash
- cargo watch -x run
- ```
+## Type Generation
-### Without Nix
+Types are auto-generated from the Trailbase schema:
-1. Install Rust via [rustup](https://rustup.rs/)
+```bash
+./scripts/generate-types.sh
+```
-2. Copy the example environment file:
- ```bash
- cp .env.example .env
- ```
+Requires `trail` and `quicktype` (both in the dev shell).
-3. Run the server:
- ```bash
- cargo run
- ```
+## Seeding
-4. Go to [localhost:8000](http://localhost:8000/) to see the page.
+```bash
+TOKEN=$(trail user mint-token <email>) ./scripts/seed.sh
+```
-## Building for Production
+## Building
```bash
-nix build
+nix build # app binary
+nix build .#trailbase # trailbase binary
```
-Or without Nix:
+The WASM search guest must be built separately:
```bash
-cargo build --release
+cd guests/rust
+cargo build --target wasm32-wasip2 --release
+cp target/wasm32-wasip2/release/search_guest.wasm ../../traildepot/wasm/
```
## Project Structure
```
simple-web-app/
-├── Cargo.toml # Rust dependencies
-├── flake.nix # Nix flake configuration
-├── traildepot/ # TrailBase data directory (migrations, config, data)
-├── static/ # Static assets (CSS)
-├── templates/ # Askama HTML templates
-└── src/
- ├── main.rs # Entry point
- ├── config.rs # Environment configuration
- ├── state.rs # Application state (DB pool)
- ├── error.rs # Error types
- ├── routes.rs # Route definitions
- ├── extractors.rs # Custom Axum extractors
- ├── templates.rs # Askama template structs
- ├── handlers/ # Route handlers
- │ ├── home.rs
- │ ├── search.rs
- │ └── settings.rs
- └── db/ # Database layer
- ├── models.rs
- └── queries.rs
+├── src/
+│ ├── main.rs # Entry point
+│ ├── config.rs # Environment configuration
+│ ├── state.rs # AppState (trailbase URL, HTTP client)
+│ ├── error.rs # Error types
+│ ├── routes.rs # Route definitions + static file serving
+│ ├── templates.rs # Askama template structs
+│ ├── trailbase.rs # Trailbase API client functions
+│ ├── trailbase_types.rs # Auto-generated types (quicktype)
+│ ├── db/mod.rs # View models (StoryWithMeta, CommentWithMeta)
+│ └── handlers/ # Route handlers
+│ ├── auth.rs # Login, register, verification
+│ ├── feed.rs # Hot/new/top feeds
+│ ├── story.rs # Story detail, submit, edit
+│ ├── comment.rs # Comments, replies
+│ ├── vote.rs # HTMX voting endpoints
+│ ├── search.rs # FTS5 search (via WASM guest)
+│ ├── user.rs # User profiles
+│ └── tag.rs # Tag pages
+├── templates/ # Askama HTML templates
+├── static/ # CSS, htmx.min.js, preload.min.js
+├── guests/rust/ # WASM guest for FTS5 search
+├── traildepot/ # Trailbase config, migrations, WASM components
+├── schemas/ # JSON schemas for type generation
+├── scripts/ # seed.sh, generate-types.sh
+├── trailbase.nix # Nix derivation for trailbase binary
+└── flake.nix # Nix flake (build, dev shell, checks)
```