max / makenotwork
- Co-Authored-By
- Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69 files changed,
+2142 insertions,
-1770 deletions
| @@ -6,15 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | 7 | **No Emoji.** The diamond mark (Young Serif period glyph) is the only graphic element. No emoji anywhere. | |
| 8 | 8 | ||
| 9 | - | **Typography (Three-Tier System):** | |
| 10 | - | - **H1**: Young Serif (wordmark, page/section headings) | |
| 11 | - | - **H2/H3/meta**: IBM Plex Mono (subheadings, taglines, footer) | |
| 12 | - | - **Body**: Lato (paragraphs, lists, table content) | |
| 13 | - | ||
| 14 | - | **Colors:** | |
| 15 | - | - Background: warm beige `#ede8e1` — never pure white | |
| 16 | - | - Text: dark charcoal-brown `#3d3530` — never pure black | |
| 17 | - | - Accent: violet `#6c5ce7` — diamond mark only, used sparingly | |
| 9 | + | **Typography and colors:** See `_meta/docs/brand.md` for the three-tier type system (Young Serif / IBM Plex Mono / Lato), color palette (warm beige / charcoal-brown / violet), and the diamond mark rule. | |
| 18 | 10 | ||
| 19 | 11 | **Platform Principles:** | |
| 20 | 12 | - **0% platform fee** — Stripe's ~3% processing fee is the only cost |
| @@ -6,7 +6,6 @@ | |||
| 6 | 6 | ||
| 7 | 7 | - Rust (stable) | |
| 8 | 8 | - PostgreSQL | |
| 9 | - | - Redis / Valkey | |
| 10 | 9 | ||
| 11 | 10 | ## Build & Run | |
| 12 | 11 |
| @@ -450,8 +450,7 @@ | |||
| 450 | 450 | - **Rust 2024 edition** (Rust 1.85+). Uses `gen` keyword restrictions and other 2024 features. | |
| 451 | 451 | - No `.unwrap()` in production code. Use `?`, `.ok_or()`, or `unwrap_or_default()`. | |
| 452 | 452 | - Prefer `Option::and_then`/`map` over `if let Some`/`match` for simple transforms. | |
| 453 | - | - Keep route files under 500 lines. Split into directory modules when they grow beyond that. | |
| 454 | - | - Files with 500+ lines of branching logic should be split. Flat lists (SQL queries, type conversions, static data) are exempt. | |
| 453 | + | - File size guideline per root `CONTRIBUTING.md`: 500-line limit on branching logic, flat lists exempt. Route files follow the same rule — split into directory modules when they grow beyond 500 lines. | |
| 455 | 454 | ||
| 456 | 455 | ## Dependencies | |
| 457 | 456 |
| @@ -4,21 +4,15 @@ | |||
| 4 | 4 | ||
| 5 | 5 | ## Design System | |
| 6 | 6 | ||
| 7 | - | ### Typography (Three Tiers) | |
| 7 | + | Typography and brand colors are defined in `_meta/docs/brand.md` (three-tier type system, base palette, diamond mark rule). The server extends the base palette with CSS custom properties: | |
| 8 | 8 | ||
| 9 | - | | Tier | Font | Usage | | |
| 10 | - | |------|------|-------| | |
| 11 | - | | H1 | Young Serif | Wordmark, page/section headings | | |
| 12 | - | | H2/H3/meta | IBM Plex Mono | Subheadings, taglines, footer, code | | |
| 13 | - | | Body | Lato | Paragraphs, lists, table content, buttons | | |
| 14 | - | ||
| 15 | - | ### Colors | |
| 9 | + | ### Extended Colors (CSS Variables) | |
| 16 | 10 | ||
| 17 | 11 | | Variable | Value | Usage | | |
| 18 | 12 | |----------|-------|-------| | |
| 19 | - | | `--background` | `#ede8e1` (warm beige) | Page background. Never pure white. | | |
| 20 | - | | `--detail` | `#3d3530` (charcoal-brown) | Body text. Never pure black. | | |
| 21 | - | | `--highlight` | `#6c5ce7` (violet) | Diamond mark only. Used sparingly. | | |
| 13 | + | | `--background` | `#ede8e1` | Page background (brand beige) | | |
| 14 | + | | `--detail` | `#3d3530` | Body text (brand charcoal-brown) | | |
| 15 | + | | `--highlight` | `#6c5ce7` | Diamond mark only (brand violet) | | |
| 22 | 16 | | `--light-background` | `#f4f0eb` | Cards, elevated surfaces | | |
| 23 | 17 | | `--surface-muted` | `#ddd7c5` | Secondary buttons, status boxes | | |
| 24 | 18 | | `--border` | `#d0cbb8` | Borders, dividers | | |
| @@ -30,7 +24,6 @@ | |||
| 30 | 24 | ||
| 31 | 25 | ### Rules | |
| 32 | 26 | ||
| 33 | - | - **No emoji.** The diamond mark (`.dot` class, Young Serif period glyph) is the only graphic element. | |
| 34 | 27 | - **No pure white/black.** Use `--background` and `--detail` instead. | |
| 35 | 28 | - **Accent color is for the dot only.** Do not use `--highlight` for buttons, links, or borders. | |
| 36 | 29 |
| @@ -79,48 +79,7 @@ | |||
| 79 | 79 | ||
| 80 | 80 | ## HTMX Dual-Response Pattern | |
| 81 | 81 | ||
| 82 | - | Routes detect HTMX requests and return different formats. Page routes return HTML fragments; API clients get JSON. | |
| 83 | - | ||
| 84 | - | ```rust | |
| 85 | - | if is_htmx_request(&headers) { | |
| 86 | - | // Return HX-Redirect header | |
| 87 | - | let mut response = Response::new(Body::empty()); | |
| 88 | - | response.headers_mut().insert( | |
| 89 | - | "HX-Redirect", | |
| 90 | - | format!("/dashboard/item/{}", item.id).parse().expect("valid path"), | |
| 91 | - | ); | |
| 92 | - | return Ok(response); | |
| 93 | - | } | |
| 94 | - | ||
| 95 | - | Ok(Json(ItemResponse { /* ... */ }).into_response()) | |
| 96 | - | ``` | |
| 97 | - | ||
| 98 | - | ### Response patterns by action | |
| 99 | - | ||
| 100 | - | | Action | HTMX response | Non-HTMX response | | |
| 101 | - | |--------|---------------|-------------------| | |
| 102 | - | | Create + redirect | `HX-Redirect` header | `Json(ItemResponse)` | | |
| 103 | - | | Delete with feedback | `htmx_toast_response("Deleted", "success")` | `StatusCode::NO_CONTENT` | | |
| 104 | - | | Save with status | `Html(SaveStatusTemplate { success, message })` | `Json(UpdateTextResponse)` | | |
| 105 | - | | Inline edit | HTML fragment (re-rendered partial) | `Json(...)` | | |
| 106 | - | ||
| 107 | - | ### Helper functions | |
| 108 | - | ||
| 109 | - | ```rust | |
| 110 | - | // src/helpers.rs | |
| 111 | - | pub fn is_htmx_request(headers: &HeaderMap) -> bool { | |
| 112 | - | headers.get("HX-Request").is_some() | |
| 113 | - | } | |
| 114 | - | ||
| 115 | - | pub fn htmx_toast_response(message: &str, toast_type: &str) -> impl IntoResponse { | |
| 116 | - | // Returns HX-Trigger header with showToast JSON event | |
| 117 | - | // Frontend JS listens for this and renders a toast notification | |
| 118 | - | } | |
| 119 | - | ||
| 120 | - | pub fn hx_toast(message: &str, toast_type: &str) -> HeaderValue { | |
| 121 | - | // Serializes: { "showToast": { "message": "...", "type": "..." } } | |
| 122 | - | } | |
| 123 | - | ``` | |
| 82 | + | See `CONTRIBUTING.md` § HTMX Responses for the full pattern (dual-format handlers, response table, helper functions). | |
| 124 | 83 | ||
| 125 | 84 | ## `ListResponse<T>` Envelope | |
| 126 | 85 | ||
| @@ -143,55 +102,7 @@ | |||
| 143 | 102 | ||
| 144 | 103 | ## Error Handling | |
| 145 | 104 | ||
| 146 | - | **Location:** `src/error.rs` | |
| 147 | - | ||
| 148 | - | ### AppError enum | |
| 149 | - | ||
| 150 | - | ```rust | |
| 151 | - | pub enum AppError { | |
| 152 | - | NotFound, | |
| 153 | - | Unauthorized, | |
| 154 | - | Forbidden, | |
| 155 | - | BadRequest(String), | |
| 156 | - | Validation(String), | |
| 157 | - | Database(sqlx::Error), | |
| 158 | - | Internal(anyhow::Error), | |
| 159 | - | Storage(String), | |
| 160 | - | InvalidFileType(String), | |
| 161 | - | FileTooLarge(String), | |
| 162 | - | MalwareDetected(String), | |
| 163 | - | ServiceUnavailable(String), | |
| 164 | - | } | |
| 165 | - | ``` | |
| 166 | - | ||
| 167 | - | ### HTTP status mapping | |
| 168 | - | ||
| 169 | - | | Variant | Status code | | |
| 170 | - | |---------|------------| | |
| 171 | - | | `NotFound` | 404 | | |
| 172 | - | | `Unauthorized` | 401 | | |
| 173 | - | | `Forbidden` | 403 | | |
| 174 | - | | `BadRequest` | 400 | | |
| 175 | - | | `Validation` | 422 | | |
| 176 | - | | `Database`, `Internal`, `Storage` | 500 | | |
| 177 | - | | `InvalidFileType` | 400 | | |
| 178 | - | | `FileTooLarge` | 413 | | |
| 179 | - | | `MalwareDetected` | 422 | | |
| 180 | - | | `ServiceUnavailable` | 503 | | |
| 181 | - | ||
| 182 | - | ### User-safe messages | |
| 183 | - | ||
| 184 | - | Internal errors (`Database`, `Internal`, `Storage`) return generic "Something went wrong" to the client. `Validation` and `BadRequest` messages pass through since they describe user input problems. | |
| 185 | - | ||
| 186 | - | ### JSON error middleware | |
| 187 | - | ||
| 188 | - | API routes use a middleware layer (`json_error_layer` in `routes/api/mod.rs`) that converts `AppError` HTML responses to JSON `{"error": "..."}` responses. Page routes render an error template. | |
| 189 | - | ||
| 190 | - | ### Result alias | |
| 191 | - | ||
| 192 | - | ```rust | |
| 193 | - | pub type Result<T> = std::result::Result<T, AppError>; | |
| 194 | - | ``` | |
| 105 | + | See `CONTRIBUTING.md` § Error Handling for the `AppError` enum, HTTP status mapping, user-safe message rules, and JSON error middleware. Source: `src/error.rs`. | |
| 195 | 106 | ||
| 196 | 107 | ## Rate Limiting | |
| 197 | 108 |
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ## Status | |
| 4 | 4 | Done: All pre-beta phases, UX audit remediation, creator trust audit remediation. Active: Creator setup (Stripe), manual testing. Next: Soft launch. | |
| 5 | 5 | ||
| 6 | - | v0.4.10 deployed 2026-05-04. Audit grade A (Run 20, 2026-05-04). ~83K LOC, 1,214+ test annotations, 0 cargo warnings, 2 cold spots. CI on astra operational. Mutation kill rate 99.4%. Property-based testing active (proptest). `cargo test --features fast-tests` for fast runs. | |
| 6 | + | v0.4.10 deployed 2026-05-04. Audit grade A (Run 20, 2026-05-04). ~83K LOC, 1,214+ test annotations, 0 cargo warnings, 2 cold spots. CI on astra operational. Mutation kill rate 99.4%. Property-based testing active (proptest). `cargo test --features fast-tests` for fast runs. Doc fuzz (2026-05-06): deleted stale database_schema.md, fixed MT README, updated SyncKit version in docs. | |
| 7 | 7 | ||
| 8 | 8 | Human tasks (manual testing, outreach, legal, infrastructure) moved to `human_todo.md`. | |
| 9 | 9 | Completed items moved to `todo_done.md`. | |
| @@ -20,6 +20,8 @@ | |||
| 20 | 20 | ### Deferred Code Quality | |
| 21 | 21 | - [ ] Remove `async-trait` in favor of Rust 2024 native async traits (chronic) | |
| 22 | 22 | - [ ] Add README.md to server/ | |
| 23 | + | - [x] Delete stale `database_schema.md` (50 migrations) — superseded by `schema.md` (57 migrations) | |
| 24 | + | - [x] Fix MT README prerequisites: removed Redis/Valkey (sessions are PostgreSQL-backed) | |
| 23 | 25 | - [ ] Split oversized route files: exports.rs (737), license_keys.rs (741), health.rs (844), tabs/user.rs (707) | |
| 24 | 26 | - [ ] Monitor scheduler.rs (1249), git/mod.rs (624) for growth | |
| 25 | 27 | - [x] [rust-fuzz] Replace `.unwrap()` with `.expect("tier passed is_none guard")` in db/creator_tiers.rs:607 | |
| @@ -27,7 +29,7 @@ | |||
| 27 | 29 | ||
| 28 | 30 | ### Dashboard Usability — Remaining (2026-05-05) | |
| 29 | 31 | ||
| 30 | - | Dashboard restructure complete (Phases 1-6 in todo_done.md). Tab layout: Projects, Payments, Analytics, Profile, Account, Plan + overflow. Fan/creator progressive disclosure implemented. Labels removed. Jargon renamed. | |
| 32 | + | Dashboard restructure complete (Phases 1-6 in todo_done.md). Tab layout: Projects, Payments, Analytics, Profile, Account, Plan + overflow. Fan/creator progressive disclosure implemented. Labels removed. Jargon renamed. Discoverability items complete. | |
| 31 | 33 | ||
| 32 | 34 | #### Performance | |
| 33 | 35 | - [x] Add performance philosophy doc (`docs/performance_philosophy.md`) — Tufte/McMaster-Carr principles applied to MNW | |
| @@ -45,18 +47,23 @@ | |||
| 45 | 47 | ||
| 46 | 48 | Unify audio and video playback into one shared component. Video gets custom controls, insertions, chapters, speed, volume, progress persistence — matching the audio experience. Plan at `~/.claude/plans/eager-ancient-salmon.md`. | |
| 47 | 49 | ||
| 48 | - | - [ ] **Phase 1:** Extract media-player.js + media-player.css from audio_player.html (no behavior change, just extraction) | |
| 49 | - | - [ ] **Phase 2:** Make state machine media-type aware (audio: dual-element gapless; video: single-element with segment advancement) | |
| 50 | - | - [ ] **Phase 3:** Create unified media_player.html template + /watch/{slug} route for video items | |
| 51 | - | - [ ] **Phase 4:** Update item.html video section — poster + "Watch" button linking to /watch/ (matches /listen/ pattern) | |
| 52 | - | - [ ] **Phase 5:** Add keyboard shortcuts (Space, arrows, M, F, S) to unified player | |
| 50 | + | - [x] **Phase 1:** Extract media-player.js + media-player.css from audio_player.html — CSS renamed to media-* classes, JS reads config from `<script type="application/json">` bridge, keyboard shortcuts (Space/arrows/M/F/S) included. Template: 1035 → 206 lines | |
| 51 | + | - [x] **Phase 2:** Make state machine media-type aware — video uses single element (no dual gapless), all mediaB references guarded with null checks | |
| 52 | + | - [x] **Phase 3:** VideoPlayerTemplate + video_player.html template + video item handler in item.rs. Video items get dedicated player page (like audio). build_segments_json handles Video content type | |
| 53 | + | - [x] **Phase 4:** Video items now route to dedicated player page (early return in item handler, matching audio pattern) — item.html video section is dead code | |
| 54 | + | - [x] **Phase 5:** Keyboard shortcuts included in Phase 1 (Space, arrows, M, F, S) | |
| 55 | + | - [x] **Code fuzz fixes:** null standbyEl crashes in chapter nav + position restore, play-before-seek race in single-element advance, integer cast overflow guards in build_segments_json | |
| 53 | 56 | ||
| 54 | - | #### Discoverability | |
| 55 | - | - [ ] Add Media Library access from content editors — "Insert Image" button in blog/item editors | |
| 56 | - | - [ ] Add content search/filter to project Content tab — search by title, filter by status/type | |
| 57 | - | - [ ] Add "Embed & Share" quick action on Item Overview | |
| 58 | - | - [ ] Add bulk operations hint on Content tab | |
| 59 | - | - [ ] Add contextual next-step suggestions after key actions | |
| 57 | + | #### Remaining (from code fuzz 2026-05-05) | |
| 58 | + | - [ ] **Video URL fallback on S3 presign failure** — audio handler falls back to `audio_url` (CDN URL stored in DB), but video handler falls back to `None` because `ContentData::Video` has no `video_url` field. Add `video_url` column to items table (migration), populate from CDN base + S3 key on upload, fall back to it in video handler when presign fails. Matches audio pattern. | |
| 59 | + | - [ ] **Arrow key seek should use virtual timeline** — arrow keys currently set `el.currentTime` directly, bypassing segment boundaries. In segment mode this can seek into content from adjacent segments or past boundaries. Fix: route arrow seek through the progress bar seek logic (find target segment from virtual time, load correct segment, seek to offset). Affects both audio and video. | |
| 60 | + | ||
| 61 | + | #### Discoverability — DONE | |
| 62 | + | - [x] Add Media Library access from content editors — "Insert Image" button in blog/item editors, section editors. Shared `media-picker.js` modal fetches `/api/media`, inserts markdown ref at cursor | |
| 63 | + | - [x] Add content search/filter to project Content tab — client-side search by title, filter by status + type dropdowns | |
| 64 | + | - [x] Add "Embed & Share" quick action on Item Overview — button navigates to Embed tab | |
| 65 | + | - [x] Add bulk operations hint on Content tab — form-hint explaining checkbox selection | |
| 66 | + | - [x] Add contextual next-step suggestions after key actions — empty state flow hint, draft/published guidance on item overview | |
| 60 | 67 | ||
| 61 | 68 | #### Feature Completeness | |
| 62 | 69 | - [ ] Add download count analytics per item | |
| @@ -179,6 +186,12 @@ | |||
| 179 | 186 | - [ ] Full spec in git history. MVP: 22A + 22B + 22E + 22C + 22H | |
| 180 | 187 | - [ ] Trigger: first Everything tier creator subscribes | |
| 181 | 188 | ||
| 189 | + | ### Chargeback Protection Fund | |
| 190 | + | - [ ] Mutual pool: creators contribute ~0.75% of sales, pool reimburses chargeback losses (all types, not just fraud) | |
| 191 | + | - [ ] Requires: dispute webhooks on connected accounts, pool ledger, per-creator caps, experience rating | |
| 192 | + | - [ ] Full plan: `docs/internal/business/chargeback_protection_fund.md` | |
| 193 | + | - [ ] Prerequisite: 50+ participating creators, 3-month reserve buildup before payouts activate | |
| 194 | + | ||
| 182 | 195 | ### Phase 24: Payment Independence | |
| 183 | 196 | - [ ] Stablecoin checkout, reduce creator-side fees, Stripe dependency mitigation, international expansion | |
| 184 | 197 |
| @@ -4,6 +4,30 @@ | |||
| 4 | 4 | ||
| 5 | 5 | --- | |
| 6 | 6 | ||
| 7 | + | ## Unified Media Player — Phase 1 (2026-05-05) | |
| 8 | + | ||
| 9 | + | - [x] Extract `static/media-player.js` (~450 lines) — full state machine: simple mode, segment mode (dual-element gapless), insertions, chapters, seek, speed, volume, progress persistence. Keyboard shortcuts: Space (play/pause), arrows (seek/volume), M (mute), F (fullscreen/video), S (skip insertion) | |
| 10 | + | - [x] Extract `static/media-player.css` (~370 lines) — all player styles with `media-*` class names (renamed from `audio-*`) | |
| 11 | + | - [x] Slim `audio_player.html` from 1,035 to 206 lines — inline CSS/JS removed, uses `<link>` + `<script src>` + JSON config bridge (`<script type="application/json">`) | |
| 12 | + | - [x] Data bridge: template passes segments, mediaType, itemId via JSON script tag; JS reads on init | |
| 13 | + | ||
| 14 | + | Phase 2-4 (2026-05-05): | |
| 15 | + | - [x] **Phase 2:** media-player.js handles video — single `<video>` element (no dual-element gapless), all `mediaB` references null-guarded | |
| 16 | + | - [x] **Phase 3:** `VideoPlayerTemplate` + `video_player.html` — custom controls, insertions, chapters, speed, volume, progress persistence for video. Video item handler in `item.rs` with presigned S3 URL, segment building. `build_segments_json` updated for `ContentData::Video` | |
| 17 | + | - [x] **Phase 4:** Video items now route to dedicated player page via early return (matching audio pattern). `item.html` inline video player is dead code | |
| 18 | + | ||
| 19 | + | --- | |
| 20 | + | ||
| 21 | + | ## Discoverability Improvements (2026-05-05) | |
| 22 | + | ||
| 23 | + | - [x] Add Media Library access from content editors — shared `media-picker.js` modal with search/folder filter, inserts markdown ref at cursor. Added to blog editor, item text editor, section editors (new + edit) | |
| 24 | + | - [x] Add content search/filter to project Content tab — client-side search by title + status/type dropdown filters above items table | |
| 25 | + | - [x] Add "Embed & Share" quick action on Item Overview — button navigates to existing Embed tab | |
| 26 | + | - [x] Add bulk operations hint on Content tab — form-hint text explaining checkbox bulk actions | |
| 27 | + | - [x] Add contextual next-step suggestions — empty state create/pricing/publish flow hint, draft vs published guidance on item overview | |
| 28 | + | ||
| 29 | + | --- | |
| 30 | + | ||
| 7 | 31 | ## Dashboard Restructure + Usability (2026-05-05) | |
| 8 | 32 | ||
| 9 | 33 | Dashboard: |