Files
e4fd491839 feat(notes): templates for the AI summary, and actions that run in chat or on it (#2397)
* feat: add themed empty states across app

* fix(home): fade empty illustration and unblock calendar connections

* feat(chat): match assistant empty state reference

* style(chat): enlarge assistant send button

* style(chat): match sidebar action labels and icons

* feat(dictionary): match empty cards and inline creation

* style(dictionary): soften empty cards and round inputs

* style(dictionary): use regular weight for empty card titles

* fix(dictionary): override global input radius for add fields

* style(notes): match empty transcript reference

* style(notes): match transcript ask bar reference

* style(notes): reduce ask bar size

* fix(chat): expand composers and note chat panel reliably

* fix(chat): keep composers stable and size in-view note chat

* fix(chat): expand note chat from composer and open page composer tall

* fix(chat): expand on focus and refine note panel sizing

* fix(notes): keep in-view chat width and corners stable

* fix(notes): keep in-view chat height stable as content grows

* fix(notes): stabilize in-view chat size and restore compact actions

* feat(notes): refine folder overview ask composer and empty art

* style(notes): reduce folder composer height and fill suggestion pills

* style(notes): remove tinted chat composer footer

* style(notes): match sidebar chat reference

* style(notes): compact sidebar chat controls

* fix(notes): contain long drafts in compact chat input

* fix(notes): center compact chat actions

* test: align UI assertions with updated empty states

* fix(chat): restore voice drafts and composer focus after replies

The Assistant page and the docked note chat lost their mic, and composers
that skip idle focus stopped getting focus back once a reply finished.

* fix(ui): correct empty-state copy and drop unused strings

Translate the snippet examples in the empty card, fix ungrammatical and
title-case English, give the Assistant page a placeholder that isn't
note-specific, update the Home empty description in every locale, and
remove keys and the planning doc nothing uses any more.

* fix(ui): keep user focus after replies and drop leftover dead code

A finished reply no longer pulls focus back from wherever the user moved
it. Also removes the unused dictionary input ref and two orphaned strings,
and stops the empty-state art from being dragged out of the window.

* revert(calendar): ask for System Audio again before connecting a calendar

Restores the permission prompt in Settings and onboarding, and its dialog
strings, which this branch had removed.

* fix(i18n): retranslate empty-state copy rewritten in English

The other locales still carried the old meaning of the Home, calendar,
chat, transcript and dictionary empty states, and a singular Search chats.

* fix(ui): translate the rewritten starters and trim leftover i18n churn

The meeting and shared-notes starters are sent as prompts, so every locale
now asks the same thing as English. Keeps the original search key and
snippets description, drops an empty locale object, and dims the mic
while the ask box is disabled.

* fix(ui): keep loaded content visible after a failed load

A folder's load error no longer sticks after its notes arrive or leaks into
another folder, and a failed chat-list reload keeps the loaded list. Also
translates the to-dos starter as a request, mirrors the dictionary art in
RTL, drops unused draft props, formats a test and restores the Google
OAuth helper, which is out of this PR's scope.

* fix(chat): keep composer focus through replies and drop dead disabled path

The composer is read-only rather than disabled while a reply streams, so
focus stays where the user left it, and the first message of a new chat no
longer remounts the composer. Removes the refocus heuristic, the unused
disabled and askDisabled props and onBeforeSend, simplifies the folder
error flag, and tests that a folder error clears once its notes load.

* fix(chat): keep focus in the composer after a starter prompt

Starter cards and overview chips unmount once their message lands, which
dropped focus to the page. Clicking one now moves focus to the composer
first, so it stays there through the reply.

* feat(home): greet the user above the Home history

Signed-in users with a name on their account see "Welcome Back, <first name>";
otherwise the heading reads "Welcome Back". The name comes from the control
panel's existing auth state.

* fix(snippets): pad the text-to-insert field in the add flow

Drop the p-0 override so the field keeps the shared Textarea padding. The
global textarea rule still draws the field's border (the border-0 utility never
applies), so without padding the text sat flush against it.

* fix(chat): keep the Chat composer collapsed until focused and center the new-chat prompts

The composer no longer auto-focuses on load, so it stays collapsed until the
user focuses it. The logo and starter cards are centered above the composer's
current height instead of reserving room for its fully expanded height. Starter
cards no longer take focus on mousedown, so the composer stays focused and
can't collapse and slide them out from under the pointer mid-click.

* fix(chat): open the expanding composer at half height and grow it with the draft

On focus the composer opens at half its previous height and grows with the
draft up to that previous maximum, then scrolls. In the collapsed composer the
mic and cancel buttons are centered and the right padding matches the top and
bottom gaps. Applies to the Chat page and the notes overview ask composer.

* fix(notes): show the recording consent notice below the note input

The notice moves from the top of the transcript to a footnote under the Ask
input. The footnote fills the space the bar's bottom padding used to take, so
the input stays in place.

* fix(chat): keep starter prompts disabled while a cancelled send is still in flight

New chat cancels the stream at once, which returns the agent state to idle,
but the cancelled send can hold the submission lock until an in-flight tool
returns. The starter cards then looked clickable and a prompt picked in that
window was silently dropped. Disable them while submissionInFlight is set, as
the composer already does.

* style(notes): give the Notes actions setup the full page column and the redesign's cards

The first-run Actions screen now uses the shared page content width instead of
a 420px column, the folder overview's header, rounded-2xl cards and section
labels, regular-size body text, and a pill Get Started button. The built-in and
newly created actions share one list card. Behavior is unchanged.

* style(home): match the upcoming events day cards to the Home design

Day cards use 12px corners and keep the grey hairline all round. Each day's
event list sits on the header colour with its own rounded top, and today's
header is the design's horizontal brand-blue gradient
(--gradient-calendar-today) instead of the button glass.

* style(upload): redesign the Upload page to match the new design language

Every Upload state now sits on the redesign's rounded-2xl card: a dashed drop
zone with the empty-state card's tile and type scale, a pill URL field with a
round brand submit button, a speaker detection card built on the shared Toggle
with the speaker count in its own row, an EmptyStateCard for the no-provider
state, and progress, completion and error cards with pill actions. The batch
queue becomes one card with even rows. Behavior is unchanged.

Supporting changes: ui/surfaces.ts holds the shared card surface and the page
hero icon tile (now also used by the Notes overview and actions setup), Toggle
reports role="switch" with aria-checked and an optional label, and
EmptyStateCard can announce its title as a heading.

* fix(home): keep upcoming events on screen while a calendar sync refreshes them

Every calendar sync refetched the list with isLoading set, which swapped the
whole Coming up column for a spinner and unmounted the day cards (and anything
open inside them). Only the first load now shows as loading; later refreshes
replace the events in place.

* feat(home): touch grass when the day's meetings are done

Today's "Caught a break" card gets a Try now button that swaps the empty state
for a CSS-only lawn at the same height: a dark olive field that grows in over
bare soil, rows of curved blades in perspective, and gusts that roll across it.
Blades near the pointer part and spring back; arrow keys move a virtual hand
and Escape (or Back in the header) leaves. A quiet granular rustle (Web Audio)
follows brushing speed and stops on exit or when the window is hidden.

Only 96 row patches animate, so the compositor moves about a hundred textures
a frame; the tufts inside them are painted once and repainted only while
parted, with plain inline values so a restyle never re-parses var().

* style(home): refresh the empty day card and give today's header its mosaic

The "Caught a break" card follows the design: new description copy in every
locale, the calendar illustration recoloured to the card's greys and blue
hairlines, a larger title and description, and a pill Try now button in the
brand colour. Today's header lays a faint grid of tiles over its gradient,
fading in from the left (.calendar-today-header).

* fix(dictionary): keep Dictionary and Snippets in the shared page column

Both pages widened the shared content column to max-w-7xl, so on wide windows
they ran 256px wider than every other screen. They now use
PAGE_CONTENT_WIDTH_CLASS as is.

* feat(insights): show a locked preview of Your Usage before the first dictation

The empty Your Usage tab was a bare card with an icon and a line of copy. It
now shows the dashboard the user will get: the metric cards and the speaking
activity heatmap, filled with deterministic sample usage, dimmed, blurred and
fading out, under an EmptyStateCard that says how to start. The preview is
hidden from assistive tech and inert, so only the card is announced.

The metric grid and heatmap move into UsageDashboard, shared by the real view
and the preview. Home's "Press <hotkey> to start" hint moves into
ui/DictationHotkeyHint so both pages draw it the same way.

* fix(review): iteration 1 — 8 issues (dead code, focus, reuse)

- Notes overview no longer blanks to "Loading…" while the tree reloads; the
  per-container loaded checks already cover the first load
- Dictionary and Snippets keep focus in the add input after each add
- Note chat panel takes its container from NoteBottomBar instead of walking
  the DOM, and drops a max-height style that never applied
- Remove the unreachable EmptyChatState (and its illustration and string),
  the unused shimmer-slide keyframes and the unused SquarePen icon
- Dictionary, Snippets and the empty upcoming card reuse CARD_SURFACE_CLASS
- Drop test assertions that could never fail

* fix(review): iteration 2 — 5 issues (composer clicks, i18n keys, tests)

- A press on a button or link inside chat messages, or on a notes prompt chip
  or sidebar pill, keeps focus in the composer, so it no longer collapses and
  slides the target away before the click lands
- Chat page and note chats read their placeholders from their own i18n
  namespaces (values swapped in every locale; wording unchanged)
- Correct a ChatView comment whose rationale iteration 1 made false
- Drop test assertions that pinned Tailwind classes, DOM depth and React's
  private props

* fix(review): iteration 3 — 3 issues (selection bar, composer hit area)

- Hide the note summary callout while transcript segments are selected, so
  the z-20 bottom bar no longer covers the SelectionBar's assign and clear
  buttons (with a test that fails without the fix)
- The Chat page composer wrapper passes pointer events through, so clicks,
  wheel scrolling and scrollbar drags beside the composer reach the messages
- Derive the floating chat top clearance from its two constants and drop the
  test that only restated them

* fix(review): iteration 4 — 3 issues (archived view, note list buttons)

- Leave the Archived chat view when a reload finds nothing archived, instead
  of showing an empty state with no toggle back to the active chats; drop the
  now-unused chat.noArchived string
- The notes empty-state actions use the shared Button (default and outline)
  like the other redesigned empty states
- Drop a test that pinned a Tailwind transition string and claimed something
  the code no longer does

* fix(review): iteration 5 — 1 issue (duplicate starter-card guard)

The Chat starter cards render inside ChatMessages, whose button/link
mousedown guard already keeps the composer focused, so drop the cards' own
copy of it.

* fix(review): keep the notes ask composer mounted when a space gets its first note

The ask section rendered from two different slots depending on whether the
container had notes, so a create_note reply landing in an empty space
remounted OverviewAskSection and dropped the draft and focus. Both slots now
share one parent and the section carries a stable key; the rendered DOM is
unchanged. A test pins that it mounts once across 0 → 1 → 0 notes.

* fix(review): keep a note-chat question typed while a reply is still streaming

A question sent from the closed note chat bar while the assistant was still
replying was rejected by the send lock, and ChatInput had already cleared
the draft, so the text was lost. ChatInput now clears the draft before
calling onTextSubmit, and NoteEditor opens the chat and writes the draft
back while a reply is in flight. Nothing visible changes. A test pins that
a draft written back survives submit.

Also drop an allowDeferredFocusRef re-check inside the idle-focus frame,
which the effect's cleanup already cancels, and its test.

* fix(review): drop ChatView's markChatStarted, which no longer changes anything

isNewChat only gates the starter cards on an empty message list, and a
persisted send has already appended the user message, so clearing the flag
from onMessagePersisted had no visible effect in the sent, failed or
cancelled paths. handleNewChat and loadConversation still set it.

* style(home): drop the section labels and tighten the empty-state copy

Home no longer labels its two columns: the "Transcriptions" line above the
empty history and the "Coming up" line above the calendar are gone, along
with their now-unused strings in every locale.

The empty history reads "No dictations yet" (the same translation Insights
already uses in each language), and Japanese now says ディクテーション in
its description too. Today's empty day card reads "No more meetings today",
"Time to touch some grass" and a "Touch Grass" button, in all 11 locales.

* fix(ui): realign Home columns and narrow two chat empty-state edges

- With history on Home, the day cards drop by one date row so the first
  one lines up with the first transcription again, as they did while both
  columns had a label
- The chat messages' mousedown guard only runs while a composer is
  focused, so otherwise links in answers keep their default press and
  their text can be selected or dragged
- The chat list shows its "No chats yet" state only when there are no
  chats at all; with every chat archived it no longer contradicts the
  Archived button above it

* fix(home): rewrite the empty history copy as one translatable shortcut sentence

The empty history now reads "Start your first dictation", "Your dictated
text will appear here." and "Press {{shortcut}} and start speaking." in
all 11 locales.

The shortcut hint was two fragments ("Press" … "to start") glued around
the key caps, which forced English word order: Japanese rendered the key
after 押してください. It is now one sentence with a {{shortcut}} slot,
rendered through the existing BidiInterpolatedText helper, so each
language places the key caps where its grammar needs them.

The hint strings follow the onboarding translations of the same sentence,
except German, which uses du to match the rest of the card, and French,
which says "commencez à parler" for "start speaking". The Insights
preview card shares this hint, so its German body text moves to du too.

* feat(notes): split note actions into templates and actions

Every note action used to overwrite the AI summary, so the built-in
Follow-up email replaced a meeting's summary with an email. Rows in the
actions table now have a kind:

- A template writes the summary from the note's notes, meeting context and
  transcript. It is a context plus ordered sections (heading and
  instruction), compiled around the shared notes rules by the new
  templatePrompts.js. Detailed Notes is now sections that compile to the
  prompt it replaced byte for byte; Generate Notes and every existing
  custom action are templates without sections and send exactly what they
  sent before
- An action has an output: "summary" rewrites the current summary (the
  transcript stays out, a clipped rewrite is refused, and only
  enhanced_content changes), "chat" answers in the note chat. Follow-up
  email becomes a chat action, joined by Make to-dos and Create outline

Rows get a client_id (a built-in's translation key, otherwise a UUID) and
notes record the template that wrote their summary in
enhancement_template_id, which rides the note sync next to
enhancement_prompt. Names, prompts and sections are capped by
NOTE_ACTION_LIMITS, which the API mirrors.

* feat(chat): let a note action ask the note chat without showing its prompt

SendToAIOptions.requestText replaces the visible message for one request,
so a chat action shows its name in the conversation while the model gets
its full prompt. Such a request skips the library search: the note in
context is the subject, and other notes would only add noise.

The note chat's context now includes the note's AI summary, which chat
actions like Make to-dos read from.

* feat(notes): pick a template for the AI summary and run actions from the note

- A template picker beside Share writes or regenerates the summary with
  the note's template, or with another one from its menu
- The "Generate AI Summary" callout runs the note's or last-picked
  template instead of always running Detailed Notes
- The ask-bar picker lists actions only, marked Chat or AI summary. A
  summary action needs a summary to edit; a chat action waits for the
  reply the chat is still writing
- The sidebar chat's quick actions are the note's chat actions, and its
  Generate summary pill writes the summary instead of asking the chat
- Replacing a summary asks first, since edits made to it are lost
- The manager has Templates and Actions tabs, a sections editor and an
  output choice. Notes onboarding introduces templates
- The note chat reads the transcript with the note owner and speaker
  names, as note formatting does, instead of the stored segment JSON

All new strings are translated in the 11 locales.

* fix(notes): keep the note toolbar and template editor readable when space is tight

With the chat docked beside a note, the tabs and the controls next to
them no longer fit on one row, and the controls were drawn over the
tabs. The row now wraps, so the template picker, record and Share
controls move below the tabs instead.

In the template editor, the context box collapsed under the sections
once they overflowed the dialog; the form scrolls as a plain column
again, as it did before sections.

* fix(notes): harden templates and actions after review

- A build that predates Make to-dos and Create outline renames their rows
  to Generate Notes. Launching this build again then seeded them a second
  time with the same client id, failed the unique index and stopped the app
  at startup. A built-in's key now comes back from its client id first.
- A built-in action the user pointed at the summary kept switching back to
  the chat on every launch; its output is now only filled in when missing.
- A summary rewrite the model cut off showed the dictation cleanup error;
  note runs now say the summary was left as it was.
- Saving a template or action that the database refuses no longer looks
  like it worked: the manager and notes onboarding show an error and keep
  what was typed, and onboarding's fields have the same length limits.
- The default template for a note's first summary is the one that last ran,
  so picking a template and then cancelling the replace no longer changes it.
- The manager compares sections the way they are saved, so a heading typed
  with a leading "#" no longer leaves Update enabled after saving.
- Drops the unused useActions and corrects comments and CLAUDE.md.

* fix(notes): word a cut-off note run for every kind of run

The message a note run shows when the model runs out of room said the
summary was left as it was, which is wrong for a note's first summary.
It now says the note was left as it was, true for a template and a
summary action alike since neither writes anything when cut off.

* fix(notes): drop a section heading made only of "#"s when it is saved

Headings lose their leading "#"s when saved, but only the first run of
them, so "# # #" was stored as "#": a heading the API refuses, which
would have failed the sync batch that carried it on every pass. All
leading "#"s and spaces now go, so such a section is dropped on save.

* feat(notes): add starter templates and actions

Templates for the meetings people summarize most, each built from sections
around the shared notes rules and the same owner-tagged Action Items:
- 1:1: updates, challenges and feedback, growth and goals
- Stand-up: updates by person and blockers
- Sales call: customer context, needs, objections, budget and timeline,
  decision process
- User interview: background, workflow, pain points, reactions, quotes,
  follow-ups

Actions:
- Shorten and Add TL;DR edit the summary in place
- Slack update, Prep next meeting and Coach me answer in the note chat, and
  show up among its quick actions with their own icons

They seed on every device like the other built-ins and never sync. All
names, descriptions and calls to action are translated in the 11 locales,
and a test now keeps every built-in named and described in English.

* feat(notes): run actions on the AI summary, or on the transcript without one

Templates already wrote the summary from the note's own material: the notes,
the meeting context and the transcript, never the summary they replace.
Actions now follow one rule, in buildNoteRunInput:

- With an AI summary, an action works from it. A summary action edits it,
  as before; a chat action is told to work from the summary unless its
  instructions ask for the transcript, as Coach me does.
- Without one, a summary action no longer waits for a template: it writes
  concise notes from the transcript, applies itself to them and saves the
  result as the summary. A chat action is told to work from the transcript
  and notes.

The built-in chat and summary prompts no longer name their sources, so the
rule decides. Follow-up email's prompt changes the same way; the prompt
1.10.1 and 1.10.2 shipped is kept as its previous default, so unedited
copies upgrade and edited ones are left alone.

* feat(notes): action chips above the ask bar and a / menu

The ask bar's split button becomes a row of chips above the composer, as in
Granola: the first four actions (Follow-up email, Make to-dos, Shorten, Add
TL;DR) run in one click, and All actions lists every action with where its
output goes, plus Manage Actions. With the chat open (focusing the composer
opens it), they move inside the chat, between its messages and the input,
and keep focus in the input. They hide while recording, like the old picker.

Typing / in the note composer or the docked chat opens the same actions as
a menu. It filters as you type; arrow keys move, Enter or Tab runs the
action and clears the draft, and Escape clears the draft without closing
the chat. A draft no action matches is sent as typed. The menu is an
optional slashCommands prop on ChatInput, anchored to the composer in a
popover so the compact composer can't clip it. One rule in NoteEditor
decides what can run for the chips and the menu: a summary action waits
for a run already writing the summary, a chat action for the reply the
chat is still writing.

Coach me and Prep next meeting are gone from the built-ins, and Create
outline moves after the two summary actions. The unused ChatInput
trailingContent prop and ActionPicker strings are removed.

* fix(notes): run an action's first summary like a template's

A summary action on a note without a summary works from the transcript,
like a template, but it didn't run like one:

- It wasn't given the material in pieces, so on a local model a recording
  too long for one request failed as too long instead of being summarised
  in parts. buildNoteRunInput now returns those pieces whenever the run
  reads the material, so the choice lives with the rule that makes it.
- Every request, parts included, demanded a complete reply, so a verbose
  part failed the whole run as cut off. Only rewriting an existing summary
  can lose content to a clipped reply, so only that run demands one now.
- Only its text was saved, so it never showed as stale. It now records the
  hash of the material it was written from, as a template does.

A summary action that edits an existing summary is unchanged. The
NOTE_ACTION_LIMITS comment no longer claims API schemas that don't exist
yet.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(review): iteration 1 — 3 issues (note errors, headings, built-ins)

- An empty reply to a summary edit toasted the dictation-cleanup message;
  it now shows the notes empty-reply error, as a cut-off rewrite already
  shows the notes truncation error.
- Saving a section heading stripped every leading "#", so "#1 Priority"
  became "1 Priority". Only a run of "#"s followed by a space (or nothing)
  is a Markdown heading marker, so only that is stripped; a heading made
  only of "#"s is still dropped.
- Make to-dos and Create outline use the builtinAction factory like the
  other built-ins, and their key constants, exported only for a test, are
  gone. The built-in list is unchanged.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(review): iteration 2 — 2 issues (summary rewrite room)

- A summary action that rewrites an existing summary had the same 4096
  output tokens as the summary it repeats, and must return complete
  output, so an action that keeps the whole summary (Add TL;DR, a
  translation) was refused as cut off once the summary neared that size.
  Rewrites now get twice the room, which is what selection edits already
  send with a complete-output request on the same routes.
- refuseClippedByWindow no longer lists rewrites: with a complete-output
  request the local route never trims the reply, so the flag did nothing
  for them.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* feat(notes): templates from the AI Summary tab, Auto action outputs, and Undo

- The AI Summary tab names the note's template and carries its chevron,
  which opens the template menu (a check and a regenerate mark on the
  current one, then Manage Templates), so the toolbar loses its separate
  picker. A note without a summary shows the tab as the menu's trigger.
- An action's output is Auto, Chat or AI summary, and new actions start on
  Auto: saving asks the note model whether the prompt edits the summary or
  answers in chat (inferActionOutput, the same route title generation
  takes) and stores that choice, falling back to chat. Sync and the API
  still see chat or summary.
- A template or summary action no longer asks before replacing a summary.
  The runner records the fields it overwrites (summary, its prompt,
  template and staleness hash, and the title when it renamed the note) and
  BackgroundActionToastListener offers Undo for six seconds, wherever the
  user is by then.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* style(notes): action chips share the ask-box pill style

The action chips looked unlike the rest of the app. They now use the
rounded, softly shadowed pill that sits above the folder overview's ask box,
through one shared ASK_PILL_CLASS, and show each action's own icon (mail,
checklist, sparkles…) instead of a boxed "/", via a getActionIcon shared
with the docked chat's pills.

The chips also add a row above the ask bar, so the note editor and the
transcript leave room for it at the bottom and the bar's fade grows while
the chips show, so text no longer peeks between them.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): offer Auto only when creating an action

A saved action already has its output, so its editor shows just Chat and AI
summary; Auto, which asks the note model on save, is offered to new actions
only.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* style(notes): center the action chips over the input

The chips center over the ask box, or start from the left and scroll when
they don't fit (justify-center-safe), so none is cut off.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): keep the notes helpers free of React, and tests on the new tab

- getActionIcon moves to its own actionIcons.ts: in shared.ts it pulled the
  icon components into every plain-Node import of the folder and note
  helpers, and those tests failed with "React is not defined".
- The summary-deletion test clicks the AI Summary tab's label button, now
  that the tab also holds the template chevron.
- The view-switch test stubs inferActionOutput, which would otherwise load
  the reasoning service into its fake DOM and hang the run.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* feat(notes): AI Summary is the default template, as before

The built-in Detailed Notes template is shown as "AI Summary" and listed
first. Its compiled prompt is the one AI summaries always used, so a note's
first summary, from the Generate AI Summary button or the tab's menu, reads
and behaves as it did before templates. A note keeps the template that wrote
its summary; the "last template picked" memory is gone.

Launch re-applies each built-in's name and order along with its kind, so
existing installs list AI Summary first and show its new name while it
runs. Built-ins are always displayed by their translation, so this never
hides a user's edit.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): make Undo and Auto safe at the edges

Review fixes for the Undo toast, Auto outputs and the default template:

- A run that finishes while the app is in the background leaves its Undo
  in the store until the window has focus, so the six seconds aren't spent
  where the user can't see them (the replace confirm is gone, so that was
  the only way back), and a remount of the listener doesn't lose it.
- Undo leaves a note deleted since alone instead of writing to it and
  bringing it back. A run whose write failed reports the failure and offers
  no Undo.
- Auto asks the model without thinking and strips any <think> block a route
  passes through: that text hid the answer and made every action a chat
  action.
- The manager's list is inert while a save runs, so switching to a new
  draft while Auto thinks no longer drops it.
- Launch renames the default template only from its old name, and the
  upgrade to sections no longer rewrites names, so a name the user chose
  stays; the order still settles.
- The Undo snapshot test reads a database that reflects the writes, so a
  snapshot taken after the write would fail it.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): show the template chevron only once a note has a summary

The AI Summary tab and its chevron appear once the note has a summary, as
the tab did before; a first summary still comes from the Generate AI
Summary callout or the docked chat's pill. With the menu reached only from
an existing summary, its regenerate mark always applies, so TemplatePicker
loses its regenerate prop.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): only a note's latest change can be undone

A second run on a note while the first run's Undo was still showing left
both toasts up, and the older Undo would restore what came before both runs,
wiping out the newer result. A new change now retires the note's earlier
Undo toast.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* feat(notes): don't save a summary when there's nothing to summarize

A recording with only greetings or small talk got a summary like "The
transcript contains only greetings and an incomplete statement, with no
substantive content captured." saved over the note's AI summary.

The notes rule for such material now asks for the exact marker
NOTHING_TO_SUMMARIZE instead of a sentence, and a summary action writing a
first summary asks for it too. The runner writes nothing when the reply is
that marker and shows a toast saying the note has nothing to summarize yet,
so the AI summary stays as it was (translated in the 11 locales).

The rule main shipped is frozen as the 1.10.2 version: Generate Notes lists
it as a previous default, so unedited copies upgrade and edited ones stay,
and the Detailed Notes sections now match the 1.10.2 prompt everywhere but
this closing rule.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(review): iteration 1 — 2 issues (overlay name, stale Undo)

Translate a built-in's name for the processing overlay, and retire a note's
Undo when a new run starts on it, since that run would write over the undo.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(review): iteration 2 — 1 issue (typed notes' first summary)

A typed note with no transcript and no summary had no way to run a template
once the ask-bar picker was gone. The Generate AI Summary callout now shows
for any note with material, not only meetings.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(review): iteration 3 — 1 issue (headless section)

Saving a template dropped a section that had an instruction but no heading,
while the editor kept showing it. Save now waits for that section's heading.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(review): iteration 4 — 1 issue (unreachable Undo dismissal)

A new run on a note now retires that note's Undo when it starts, so offerUndo
could never find an earlier Undo to dismiss. Remove that branch and its test.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): keep chat actions in the chat alongside main's connectors

Main now offers connector tools and a filtered attendee block in the note
chat. A one-click chat action (Follow-up email, Slack update) now answers
in the chat as text, using a tool only when its instructions explicitly
ask to send, post or file something, so it never opens a draft or posts on
its own. The chat's meeting context no longer adds its own unfiltered
invited list, and a teammate's recording keeps its stored transcript
instead of labelling their mic lines with the viewer's name.

Claude-Session: https://claude.ai/code/session_01KXixCUu9GS3H6HbhVB92d5

* fix(notes): answer chat actions in the note's language

A chat action sends its English prompt as the user's turn, and the note
chat's system prompt says nothing about language, so a German note's
Make to-dos or Follow-up email came back in English. The docked pills
used to send translated text, which kept replies in the user's language.

* fix(notes): keep an undone first summary from coming back through sync

Undo restored a note that had no summary with enhanced_content null. The
API keeps its stored value when a push sends null (COALESCE), so the cloud
kept the undone summary and the next pull brought it back on this device.
Restore "" instead, as deleting a summary in the editor already does.

* fix(notes): only treat a reply that is the marker as nothing to summarize

The runner discarded any reply containing NOTHING_TO_SUMMARIZE, so a real
summary with one section a small model answered with the marker, or one
that mentions it, was thrown away with the nothing-to-summarize notice.
The marker now counts only as the whole reply, bare or wrapped the way
models send it: fenced, emphasised, or after one short sentence.

* fix(notes): let a local summary rewrite shorten its reply to fit the context window

A summary rewrite asks for 8192 output tokens with requireCompleteOutput,
which on a local model also stopped the preflight from trimming the output
allowance. On a machine whose context ceiling is 16k, Shorten or Add TL;DR
on a long summary was refused before generating, even when the edit would
have fit.

A caller that sets refuseClippedByWindow alongside requireCompleteOutput
now gets the trimmed allowance, and a reply that fills it is still refused
after inference, so a clipped rewrite is never saved. Rewrites set both.
Selection edits and dictation cleanup set only requireCompleteOutput and
are refused up front as before.

---------

Co-authored-by: boseq <307062491+boseq@users.noreply.github.com>
Co-authored-by: Chadpiha <chadpiha23@gmail.com>
2026-10-01 00:14:14 +02:00
..