-
April 4, 2026
Date: 2026-04-04 Admin user system Added an is_admin boolean to the users table. Only admin users can create sub-accounts (the multi-account feature). This gates what was previously open to any authenticated user. The check is a single IsAdmin(userID) call in the AddAccount handler — non-admins get a 403 FORBIDDEN . On the Flutter side the "Add account" menu item is…
-
April 4, 2026
Date: 2026-04-04 In-app audio recording The audio picker originally only offered file selection. Added a "Record" button alongside it that uses the record package to capture audio from the microphone. The UX is simple: tap Record, see a red dot + timer ticking up, tap Stop. The recording saves as recording.m4a . On iOS Safari the record package uses the…
-
April 4, 2026
Date: 2026-04-04 Stale comments on post navigation PostProvider is a single app-level instance. Navigating from post A to post B without clearing state first meant post A's comments briefly appeared under post B, and a race condition could attach a new comment to the wrong post. Fixed by calling provider.clear() at the start of PostDetailScreen.initState() before loading the new post.…
-
April 4, 2026
Date: 2026-04-04 What changed Added a GitHub Actions workflow that runs tests before deploying. The deploy job only starts if both test jobs pass — a bad push can't reach production. Two parallel test jobs: flutter-test — runs flutter test on every push go-test — spins up a Postgres 17 service container and runs go test ./... against a real…
-
April 4, 2026
Date: 2026-04-04 Added a lightweight gate to account creation while the app is in early access, before email verification exists. Set INVITE_PASSPHRASE=<phrase> in the backend environment. When set: POST /auth/signup checks that invite_code in the request body matches the passphrase, returning 403 if it doesn't GET /v1/config returns {"invite_required": true} so the client knows to show the field The Flutter…
-
April 4, 2026
Date: 2026-04-04 iOS HEIC filename mismatch Images uploaded from iOS Safari were failing silently. The photo library on iOS reports a .HEIC filename even when the browser has already transcoded the image to JPEG for the upload. The backend extension allowlist doesn't know about .heic , so it rejected the upload. The fix is a _normalizeFilename() helper in PostService that…
-
April 4, 2026
Date: 2026-04-04 Double navigation on mobile swipe On iOS Safari, the browser's back-swipe gesture was firing at the same time as go_router's own back navigation. The result: tapping back on a post detail screen popped the route AND navigated the browser back — landing two screens back instead of one. The fix was wrapping the gesture-sensitive parts of the screen…
-
April 4, 2026
Date: 2026-04-04 Nested comments Added one level of replies to comments. The backend enforces a depth limit of 1 — you can reply to a top-level comment, but not to a reply. Attempting to do so returns a 400. The schema change is simple: ALTER TABLE comments ADD COLUMN parent_id INTEGER REFERENCES comments(id) ON DELETE CASCADE; The repository fetches top-level…
-
April 4, 2026
Date: 2026-04-04 Did a code-level security review of the backend and fixed the most impactful issues. Rate limiting on auth endpoints POST /auth/login and POST /auth/signup had no rate limiting — an attacker could brute-force passwords or credential-stuff at full network speed. Added per-IP token bucket rate limiting using golang.org/x/time/rate : Login: 5 requests per minute, burst of 5 Signup:…
-
April 4, 2026
Date: 2026-04-04 The redesign Replaced the original design with what I've been calling "soft laser cyber optimism" — dark backgrounds, violet-to-pink gradients, glow effects, the Outfit typeface. The goal was something that feels like it belongs in the same aesthetic neighborhood as Bluesky or Linear but with a bit more edge. Post cards got gradient accent bars unique to each…
-
April 4, 2026
Date: 2026-04-04 Flat post cards with pastel gradients Replaced the Material card style (elevation, shadow, rounded border) with a flat design. Posts and comments are now separated by a single thin Divider line. Each post card gets a pastel gradient background, cycling through six palettes by post ID so adjacent posts always differ: Indigo → pink Green → sky Amber…
-
April 4, 2026
Date: 2026-04-04 MOV video support for iOS iOS records video as QuickTime MOV files. The backend extension allowlist only had .mp4 for video — .mov wasn't accepted at all. Adding it was more involved than just appending to the list. Go's http.DetectContentType uses the MIME sniffing spec, which doesn't recognize the QuickTime ftyp brand. It returns application/octet-stream for valid MOV…
-
April 3, 2026
Date: 2026-04-03 What changed The original schema had one users row per person, with email and password hash sitting directly on the user. This made it impossible to have multiple accounts (usernames) under one login — you'd need a separate email for each, which is awkward for dev/testing and wrong architecturally anyway. Split the schema into two tables: credentials —…
-
April 3, 2026
Date: 2026-04-03 What changed Got a proper local dev loop running after some time away from the project. The stack: PostgreSQL 17 running locally on port 5433, Go backend, Flutter web in Chrome. The backend .env was already wired correctly. Flutter needed ApiConfig.baseUrl updated to point at localhost:8080 in dev builds — it was using relative URLs which only work…
-
April 3, 2026
Date: 2026-04-03 The tension The current specs frame Curiate as a creator monetization platform — true fans, 1000 true fans, direct payments. But the name encodes something different. Curiate comes from curia (Roman assembly — deliberate collective judgment). To curate is to exercise discernment, not just preference. Three directions are in play and haven't been resolved yet: Direction A —…
-
April 3, 2026
Date: 2026-04-03 The problem: daily forced re-login Users were being kicked to the login screen every day. The JWT tokens were set to expire after 24 hours. When the token expired the app would redirect to login with no explanation — no pre-filled email, no "your session expired" message, just a blank login form. Extended the JWT expiry to 30…
-
April 3, 2026
Date: 2026-04-03 Upload failing on prod Video uploads worked locally but failed silently on the production server. The Go backend was already configured to accept up to 100MB multipart forms and allowed .mp4 / .webm MIME types — so the backend wasn't the problem. The culprit: Nginx's default client_max_body_size is 1MB. Nginx was rejecting the upload before the request ever…