Add an explanation timeline for every reminder - #1
Conversation
The gauge said how tense a moment was but never why, and it fused the acoustic and language signals into a single number before anyone could see them. This adds a timeline that keeps the two families apart and states, for each reminder, the rule it met and the arithmetic behind it. - fusion.ts now produces a per-tick SignalBreakdown and blends it through a single shared fuseScore, so the chart is drawn from the same numbers the score is made of rather than from a second implementation. - explain.ts attributes the score above the 30-point baseline to loudness and pace, the local lexicon, and the model's tone read. The blend is linear, so the three terms reconcile exactly; the rules side is scaled when it clamps, and rounding remainders fold into the largest term. - The session machine records each breathing pause and rewrite offer with the tick that justified it, captured at the transition so a later, calmer reading can never rewrite the explanation. - The demo script now declares canned inputs and runs the real fusion math instead of hardcoding scores, which keeps its explanation cards honest. Its opening tone rationale was English in both locales; it is localized now, and the dashboard no longer claims the AI is resting while feeding tone reads. Both locales carry the full string set, and the timeline section shows no untranslated text in either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0e5e9daea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| score: state.score, | ||
| signals: latestSignals(state), |
There was a problem hiding this comment.
Capture rewrite evidence when the request is triggered
When /api/rewrite takes longer than the two-second scoring interval, this records state.score and latestSignals(state) from the time the response arrives, not from the hostile score or keyword that initiated the request. If the speaker has calmed down meanwhile, the reminder card can claim that a low, unrelated tick triggered the rewrite; snapshot the triggering score/signals when requestRewrite starts and carry them in the event.
Useful? React with 👍 / 👎.
| reminders: withReminder(state, { | ||
| at: event.moment.at, | ||
| kind: 'rewrite', | ||
| score: state.score, | ||
| signals: latestSignals(state), | ||
| quote: event.moment.quote, |
There was a problem hiding this comment.
Record fallback rewrite reminders in the timeline
When the rewrite endpoint is unavailable, blocked by its breaker/budget, or rejects, ToneSuggestion still presents the local SUGGESTION_MAP rewrite, but no REWRITE_OFFERED event is dispatched. Because reminders are appended only through this event path, those user-visible rewrite reminders never appear in the new explanation timeline; emit a reminder when the fallback suggestion is actually shown as well.
Useful? React with 👍 / 👎.
What
The gauge told you how tense a moment was, never why — and it fused the acoustic and language signals into one number before anyone could look at them. This adds a timeline that keeps the two families on separate tracks and states, for each reminder, which rule it met and how many points each signal contributed.
Two stacked tracks over the session:
Reminder markers sit across both. Every marker has a card giving the rule it satisfied (
score ≥ 70 held 5s, or a high-risk phrase), the dominant driver, and the point contribution of each signal.How the numbers hold up
The blend is linear, so the score above the 30-point baseline splits into exactly three additive terms:
Those are the terms the card prints. The rules side is scaled when it clamps at 100, and rounding remainders fold into the largest term, so the column adds up to the score it explains. A floored tick is left alone and labelled — its score was overridden, not blended.
Changes
fusion.tsproduces a per-tickSignalBreakdownand blends it through a single sharedfuseScore. The chart is drawn from the same numbers the score is made of, not from a second implementation. Scoring is bit-compatible — the existing 14 fusion tests pass untouched.explain.tsis the new pure attribution module: contributions, dominant driver, evidence shares, track heights.--signal-acousticbecomes a real theme token; the tone gauge's hardcoded volume-ring hex now uses it.Demo
/demopreviously hardcoded its scores, which would have made its own explanation cards contradict themselves. It now declares canned inputs and runs the real fusion math. The arc is preserved (calm → hostile peak → intervention → recovery); peak moves 88 → 94 and the recap series follows.Two bilingual fixes surfaced along the way: the opening tone rationale was English in both locales, and the dashboard claimed "AI resting · rules mode" while the script fed it tone reads on every beat.
Validation
30 + 19 + 36 + 9 = 94), and the timeline section shows no untranslated text in either language.🤖 Generated with Claude Code