NexBoard Build Journal — Mon 08 Jun – Sun 14 Jun 2026

What shipped

  • Reworked the meeting countdown into a two-phase alert: a Python snapshot computes the countdown and fires the Telegram alert in under a second, then LLM enrichment runs async and edits the context in afterward.
  • Rebuilt the three Chief-of-Staff Telegram briefings — morning, end-of-day, and topics. The end-of-day brief was firing as three separate messages; it’s now one. Topics gained clickable headlines, human labels, and red/green status dots.
  • Ran a multi-phase security pass over the self-hosted stack. The headline finding: the reverse-proxy auth layer wasn’t actually gating any app route. Locked the internal data services to localhost and sequenced the auth rollout instead of flipping it on blind.
  • Polished the dashboard in place: an “eyeline strip” with a live ticking meeting countdown, gradient progress bars on goals, colour-tinted kanban columns, and summation badges on the wins and loops panels.
  • Killed two silent-failure bugs and decommissioned OpenClaw, the old dispatch path — every agent now runs through a direct subprocess or the CoS handler.

The interesting problem

The morning meeting alert kept not showing up. No error, no message, nothing — maybe one morning in three.

The countdown was a single Claude call: work out the next meeting, compute the time-to-go, write a nice message, send it. The send was the last step, and the whole thing ran under a 60-second timeout. When enrichment ran long, the job hit the timeout and died before it ever sent — so a slow model swallowed the entire notification.

The fix was to split the job. Phase one is pure Python and deterministic:

event = next_meeting(snapshot)
mins  = minutes_until(event, now_acst)
send_telegram(f"{event.title} in {mins} min")   # <1s, no LLM

Phase two is the enrichment — prep notes, attendees, what to read first — and it edits the already-sent message. If it times out now, you still got your alert; you just miss the extra colour. The lesson generalises past this one job: keep anything the user actually depends on on a fast deterministic path, and treat the LLM as best-effort decoration on top of it.

Decision of the week

I built two full dashboard redesigns — a calmer editorial layout and a bento-grid version — served them up, looked at them for a day, and threw both away. I’m polishing the existing layout instead.

The redesigns were nicer to look at, but they moved widgets around and cost data density, and this is a tool I stare at all day, not a landing page. The tradeoff I’m taking: less visual ambition in exchange for zero functionality loss and every number staying exactly where my eye already expects it. The rule I wrote down for next time is mockups first, respect the existing widget geometry, and no greeting or narrative fluff.

What surprised me

The end-of-day briefing was sending itself three times every night, and I never connected it to the prompt. The instruction said “wins first, then what’s open, then the first move for tomorrow.” The model read that literally as three messages. It wasn’t a loop or a cron misfire — it was me describing three sections and the model deciding each one deserved its own send. One line telling it to send exactly one message fixed it.

What’s next

Finish the security sequence — deploy the auth outpost, fix the misrouted proxy backends, then put the auth middleware in front of the apps. Clean up the dead OpenClaw references still scattered through the dispatch code. And keep tuning the briefing tone now that the format has settled.


Building NexBoard — a self-hosted AI Chief of Staff, built entirely with Claude Code.