The parts that form an opinion about your day. Flux's rule for everything on this page is that none of it is load-bearing: the calendar, the planner and the context graph all work with no language model attached at all. What follows is addition, never dependency, and each card says plainly how much of it is real.
The one surface here that uses no AI whatsoever. It reads day_rollups, a server-computed table of what you planned and what you actually did, and draws it. If every model in the stack went dark tomorrow, this page would be unchanged.
Resolved today
Planned vs actual
Signal vs. noise ratio today
Resolved today
Planned: 7.0h
Signal vs. noise ratio today
Every stat card has a — state and they fail independently. Signal Average goes dashed the moment nothing today carries a rating, while Time Tracked beside it still reads a number, so a partly-rated day shows a half-populated row rather than an empty one. The card description counts differently in each branch: with rollups it says "Your last 6 days", with none it falls back to 14, so the empty state advertises a window it has no data for. Local-only mode never populates this at all: rollups are computed server-side, so a local session sits on the empty state permanently.
Three places where Flux volunteers an opinion. Two of them wait to be asked and cost a model call; the third arrives unbidden and costs a database query. All three are strictly additive: every one of them can fail and leave the calendar exactly as it was.
Click the button below to analyze your recent activity.
The card behind this toast never changes: the handler returns before setIsLoading(true), so no spinner appears and no request is made.
Your four longest blocks all landed before noon, and every noise item sits after 15:00. Protect the morning as a single container and batch inbox triage into one 20 minute block at the end of the day.
Click the button below to analyze your recent activity.
The error branch is the honest one. setResult(null) runs before the request, and the catch never sets a result, so a failed analysis leaves the card on its idle copy with a toast floating over it. Nothing about the calendar changes and nothing is half-written. Two things worth knowing about the chart: it plots the number of activities, not hours, so four long blocks and four two-minute interruptions weigh the same; and the two headings are coloured text-primary for Signal and text-accent for Noise, which puts the app's positive-status green on the distracting column.
The label is Title Case; the value sent to the model is the lowercase string, so the union is supportive coach, direct captain, neutral assistant.
There is no spinner here and no TimeAwareLoadingIndicator: the entire loading state is the word on the button.
Updated Task: Flux calendar view component: main layout finished, now resolving overlapping task conflicts.
Updated Schedule: 9am: Standup, 10am-12pm: Design Sprint, 12pm-1pm: Calendar view layout, 1pm onwards: Task conflict resolution.
Marked Placeholder because two of its four inputs are fixtures. The model call is genuine and the persona and your typed update really are sent, but taskDetails and previousSchedule are hardcoded string literals in the component, sent identically on every call by every user: "Working on Flux project, calendar view component." and "9am: Standup, 10am-12pm: Design Sprint". So the "Updated Schedule" it hands back is a rewrite of a demo schedule nobody has, and the toast that follows a success claims "Your task and schedule have been updated" when nothing has been written anywhere. The result panel is the only place the answer exists; closing the dialog discards it.
No AI is involved anywhere in this. It is five table reads and a set intersection: open frictions, the people they involve, and today's and tomorrow's entries that either link to that person or list their name in invitees. Two design decisions are worth naming. Got it and Dismiss do the same thing to your screen and differ only in the word written to the audit trail (accepted vs dismissed), and the row disappears optimistically before the write returns. And the scan is check-before-insert against every prior row including dismissed ones, so a warning you dismiss once never returns for that pairing, even next month. The strip paints Tailwind's amber, not a Flux token; local-only sessions skip the scan entirely and never see it.
A model call takes a few seconds and Flux has exactly one thing to show you while it does. It is a clock, which is the right idea for an app about time, and it is drawn wrong in a way that is easy to miss and shown here at full size so you cannot.
The hand does not pivot at the centre of the clock. The ring is centred at 48,48 in a 96px box. The hand is h-1/2 w-1 inside an inset-0 flex items-center justify-center parent, so it is 48px tall and vertically centred, which puts its bottom edge at y=72 and not at y=48. With transformOrigin: 'bottom' it therefore sweeps a circle centred 24px below the ring, and its tip swings clear outside the face at the bottom of every rotation. Reproduced exactly as shipped: watch the loop above and you will see the hand leave the dial. A second, quieter finding: .animate-rotate-hand declares transform-origin: center in globals.css and the component overrides it with an inline style, so the utility's own origin is dead code. Both keyframes are copied verbatim, including the 1.5s linear sweep and the 2s pulse.