June 1, 2026

Two chat bugs fixed, a bigger rewrite begins

Killing a reload bounce and a lost image in chat, then laying the first bricks of a flatter message-processing core.

Some days are about paying down debt so the next six months of building don’t get more expensive. Today started with two chat bugs that had no business surviving this long, then moved into the first steps of a structural rewrite of how Chalie processes a message end to end.

The first bug was ugly in a specific way: send an image to a vision-capable model, and the image would vanish from your own chat bubble the moment you hit send. The upload preview cleared before the attachment metadata got captured, so the thumbnail never made it into the rendered turn. Fixed by capturing the preview (object URL, filename) before clearing the input, and threading it through into the bubble renderer so both images and document chips actually show up in the message you sent — themed correctly in light and dark. The second bug was worse: if a turn failed for a reason that had nothing to do with your session — a provider hitting a rate limit, a tool erroring out — the frontend treated any non-recoverable error as an auth failure and redirected to the login page, which then bounced you straight back and silently ate the turn. That’s the kind of bug that erodes trust fast, because it looks like the app forgot what you were doing mid-sentence. The fix narrows the auth-redirect trigger to an explicit auth signal only; a real session loss is still caught by the heartbeat, and every other failure now just shows an error inline while the turn finalises normally.

The second pair of fixes were both provider-configuration papercuts. Gemini support already existed for creating and testing a provider, but the model-list endpoint the settings UI calls the moment you enter an API key didn’t know about Gemini and threw “Unsupported platform” — so adding a Gemini provider through the normal flow was broken even though the plumbing underneath worked. That gap is closed with a proper Gemini model-list fetch against their API. Separately, deleting a provider was a soft delete that left the row (and its unique name) sitting in the table, so re-adding a provider under the same name blew up with a raw database constraint error surfaced straight to the user. Deletion is now a real delete, the vestigial soft-delete flag is gone from schema and every read path, and duplicate-name conflicts on create or update now return a clean, friendly error instead of a 500.

With the papercuts cleared, the day’s second half started a rewrite that’s been sitting in the design stage for a while: flattening the message processor into a single, linear control loop instead of the layered structure it grew into over time. The first two steps landed today — a frozen configuration object that captures every per-channel setting up front, so a turn’s behavior is fixed at the start and can’t drift mid-run, and the skeleton of the new processor itself: one entry point, one loop, and explicit stop conditions for cancellation, deadlines, and iteration caps. Providers and tool dispatch are still stubbed behind clean seams for now — this step is purely about proving the control flow is sound and fully covered before real logic gets wired into it, with the whole test suite green throughout. It produces almost nothing visible today and quietly determines how reliable every conversation feels for a long time after.

Fixing what’s user-facing and rebuilding the foundation underneath it in the same day isn’t an accident — it’s the discipline of not letting visible rot sit next to architectural debt. Neither gets to wait for the other.

  • Fixed: uploaded images disappearing from the sender’s own chat bubble on send

  • Fixed: non-auth turn errors incorrectly triggering a login redirect that silently discarded the turn

  • Fixed: Gemini model-list fetch and provider deletion, now a real delete with clean duplicate-name errors

  • Removed a dead conversation-phase tracking service with zero production readers

  • Started a ground-up flattening of the message-processing core: frozen per-channel config plus the new linear control-loop skeleton, fully seamed and tested