-
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…
-
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…
-
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.…
-
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…
-
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…
-
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…
-
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…
-
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…
-
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:…
-
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…
-
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…
-
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…
-
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 —…
-
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…
-
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 —…
-
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…
-
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…
-
2026-03-31 What happened I wired up the devsnap tooling directly into this project — the script and Claude command now live in the repo so running a devlog snapshot is just /devsnap . The bigger story this session was the session dashboard getting an estimated kWh energy display: using published 2025 per-token energy benchmarks (decode ~1.0 J/token, prefill ~11x cheaper,…
-
2026-03-27 What happened initial state: two-deck MIDI sequencer with fixed playhead, scrolling piano roll, crossfader, and clip layering Files touched .claude/commands/devsnap.md .project.toml devlog/assets/.gitkeep scripts/devlog-preview.sh scripts/devpublish.sh scripts/devsnap.sh scripts/install-hooks.sh Tweet draft commit: c0d15e7 · screenshot: chrome failed
-
2026-03-27 What happened Started a new MIDI sequencer project from scratch with a core idea I've been sitting on: what if the playhead didn't move, and the clip scrolled under it instead — like a record under a needle? That inversion unlocks a whole DJ-deck mental model where you stand at the mixer and the music comes to you rather…
-
2026-03-26 What happened One of the challenges of navigating a 16×16×16 voxel cube is that the front layers block everything behind them as you zoom in. We already had near-plane culling — cubes vanish when their center crosses a plane in front of the camera — but the pop-off was abrupt. One frame there, next frame gone. Today's change adds…
-
2026-03-25 What happened setting up the devlog system itself Files touched index.html Tweet draft commit: a22e024 · screenshot: captured
-
2026-03-24 What happened Reworked the keyboard scheme significantly. The original qwerty-row layout mapped keys linearly to steps 1–16, which is intuitive but puts beats 1, 5, 9, 13 scattered across two rows. The new layout groups by beat position: asdf / ASDF hit beats 1,5,9,13 and 3,7,11,15; qwer / QWER hit beats 2,6,10,14 and 4,8,12,16. You can now drop a…
-
2026-03-24 What happened The pattern bank buttons (8 slots) now show 8 tiny dots — one per row — that light up when that row has any steps set. Teal when the pattern is just populated, amber when it's also the currently-edited pattern. Updates on every step toggle, clear, and macro apply. At a glance you can see which patterns…
-
2026-03-24 What happened Each row now has a sound selector: midi (default) or one of seven built-in drum voices — kick, snare, closed hi-hat, open hi-hat, clap, tom, rim. Drum rows trigger Tone.js synths (MembraneSynth for kick/tom, NoiseSynth for snare/chh/clap, MetalSynth for ohh/rim) at the scheduler's precise Web Audio timestamps. Tone.js syncs to the existing AudioContext on first play. MIDI…
-
2026-03-24 What happened Added a design section to PLAN.md about cue routing — the idea of routing individual rows to a secondary output (a separate MIDI port or reserved MIDI channels) that only the performer hears, like a DJ headphone cue bus. The core insight: in a live set, every edit is currently public. With a cue channel, you get…
-
2026-03-23 What happened Found the old PEEQ tarball from 2003 and brought it into git. The Pure Data source is remarkably intact — peeq.pd , row.pd , rowlogic.pd — three files, 999 lines total, and the whole architecture is legible. Clock driven by metro , pattern data in float arrays, 8 patterns × 8 rows × 16 steps addressed by…
-
2026-03-23 What happened Ported PEEQ to the browser as a single index.html . Web MIDI API for note output, AudioContext + setTimeout -based scheduler for the clock, CSS grid for the step buttons. The original keyboard layout is preserved: qwerty/asdf rows for steps, [ / ] for row focus, number macros, numpad + / - for pattern switching with the…
-
2026-03-19 What happened Spent the day porting fast-vj to WebAssembly via Emscripten. It works — video clips play, shaders run, OSC arrives over a WebSocket bridge — but there's an unresolved slowdown over time that I never tracked down. The port lives on the wasm-port branch and isn't merged into main. Phase 1 — build scaffolding : The CMakeLists.txt gained…
-
2026-03-18 What happened Hardening for the Pi 4 target: Texture rescaling : The Pi 4 GPU caps texture dimensions at 4096×4096. Images wider or taller than that upload silently as blank. Added a check at clip load time — if either dimension exceeds the GPU limit, stb_image_resize2 downscales the image to fit before upload, with a log message showing the…
-
2026-03-17 What happened First working build. The core render loop is up: Sokol window, OpenGL context, vsync'd frame callback. Media clips scan from a directory at startup and are indexed alphabetically by type — audio .wav files, .png / .jpg images, and video either as .avi (MJPEG, mmap'd) or a directory of sequentially numbered JPEGs. OSC listener runs on a…
-
2026-03-17 What happened Added LuaJIT (with Lua 5.4 fallback). A patch is a plain .lua file loaded at startup with -s patch.lua . The engine calls on_frame(dt) every render frame and on_osc(addr, arg) for each incoming OSC event. The vj.* API exposed to Lua covers everything: vj.audio(i) , vj.image(i) , vj.video(i) , vj.gain(f) , vj.stop() for playback; vj.sample(i) and vj.fft(i)…
-
2026-03-17 What happened Pulled shader logic into its own module ( shaders.c ). Shaders load from a directory at startup, indexed alphabetically. Each file is just a void main() body — the engine prepends a standard header that injects the uniforms: image_tex , audio_tex , fft_tex , uv , u_time , and u_p[15] for user parameters. Three initial shaders beyond…
-
2026-03-17 What happened Three additions in the late session: Mic input ( -m [device] flag): ALSA capture thread feeds live microphone audio into the same waveform and FFT ring buffer as clip playback. This means every audio-reactive patch works with either a playing clip or a live mic — no code change needed. The -m flag accepts an optional ALSA…
-
2026-02-19 What happened Built a hardware-level simulator that runs the unmodified firmware binary. Rather than porting the decompiled C, this approach uses jarikomppa/emu8051 as the CPU core and implements the MMT-8 peripheral hardware in C around it. SDL2 renders the LCD, buttons, and LEDs. The key insight with emu8051 is that it exposes function-pointer callbacks for external memory reads/writes and…
-
2026-02-18 What happened Started by dumping the EPROM from the MMT-8 — 32KB 27C256, straight binary read. Used dis51 to get an initial assembly listing (29,208 lines), then brought it into Ghidra 12.0 for decompilation and annotation. Wrote three Ghidra Jython scripts to automate the tedious parts: ghidra_setup.py seeds the disassembly from known entry points (reset vector, interrupt vectors), ghidra_annotate.py…
-
2026-02-18 What happened Wrote up a detailed plan for porting the decompiled firmware to compilable C running on Linux. The target is a working emulator with ncurses LCD display — not cycle-accurate hardware simulation, just the firmware logic running natively. The plan splits into four layers: HAL ( mmt8_hal.h/c ) — replaces 8051 hardware with C arrays and macros. XDATA[65536]…