Skip to content

Agent: dock the out-of-usage notice in the composer and make status banners neutral - #1510

Merged
AnthonyRonning merged 7 commits into
masterfrom
ux/usage-notice-redesign
Nov 11, 2026
Merged

AnthonyRonning merged 7 commits into
masterfrom
ux/usage-notice-redesign

Conversation

@matthewrball

@matthewrball matthewrball commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

Current review stack

Native stack #1535: master → #1510 → #1514.

Bottom layer: the out-of-usage notice and neutral banners. Base: master. Independent of the Settings stack (#1534) and the effort slider (#1520).

Refreshed onto master af5c3165, including merged #1508, #1505 (Tools) and #1506 (Web switch), by merging master in; no commits were rewritten. Whichever of this stack and #1534 lands second takes the seven hunks listed under Merge order below; I'll make that update. Local app tests on this head: 577 passed, 0 failed.

Current head: ec7eb275.

The original scope and validation are retained below.


Summary

Running out of usage put a solid coral card with white text into the transcript: the loudest surface on screen, with a grey Open Billing pill sitting on the coral. widgets::banner painted 13 other errors and warnings the same way, a solid status color behind white text. In dark, white on status_error is 2.97:1 and white on status_warning is 2.14:1, under the 4.5:1 that body text needs. The other 4 banners were already neutral grey.

  • Out of usage moves to the composer. When a task's latest turn ended out of usage, a panel docked inside the composer, above the input, reads "Out of usage · See your options in Billing so you can keep working." and offers Open Billing → as an inverted pill. The field stays editable. The panel goes as soon as a message is on its way, a run is active, or any later row arrives. While the loaded window does not hold the task's tail (paged back into history, or live rows past its budget), it can't tell what came last, so it shows nothing.
  • A one-line marker stays in the transcript. The turn itself leaves a quiet row: a gauge icon, "Out of usage" and a hairline. The row keeps its alert role, label and full description for screen readers, and its visible title uses the same key as that label. Rows stored before error labels existed are still recognized by the fixed message, as before.
  • Status banners are neutral. widgets::banner(tone, message) takes a Tone (error, warning, info) and draws a bg_elevated card with a hairline border. The tone shows only as its icon (circle-alert, triangle-alert, info) on a small tile tinted with the status hue, so the message keeps text_primary. All 18 call sites move:
    • errors (7): transcript errors, the runtime error above the transcript and on the new-task screen, unsaved work above the composer, sign-in, feedback, Local API;
    • warnings (7): Shortcuts (invalid overrides, compatibility, notice, reset confirmation), ACP, MCP catalog, the account-deletion notice at sign-in;
    • info (4): Billing, API credits, Account, the plan gate.
  • Shortcuts. The Reset all confirmation uses the high-contrast default_button, so a coral button no longer sits on an amber banner. Reset and Cancel now have accessible names. Shortcut notices carry their own tone: applied and cancelled are info, rejected keys and navigation hints are warnings, and failures are errors. Before, every outcome got one warning fill.
  • One inverted pill. widgets::default_button with bg_default_button / _hover / _active and text_on_default_button, held to 7:1 by the contrast test. It is the same definition the Settings regroup (Agent: regroup Settings into Workspace, Connections, and Account & System #1507) adds, so the two PRs share it rather than carrying two pills.
  • Tokens and icons. bg_composer_tray, and accent_ink (Maple 800 on light; the panel's gauge is 3.83:1 on its coral tile, held to 3:1). Lucide gauge, triangle-alert, info and arrow-right.
  • Strings. chat-usage-limit-title and chat-usage-limit-body in English and Spanish; translation-revisions/es.txt stamped. USAGE_LIMIT_MESSAGE and stored timeline text do not change.
  • Docs. docs/brand.md gains the token rows, default_button in Buttons, and a "Status banners" section: a banner never paints a status color behind its text. It also corrects the light status_warning value.

Not in this PR: primary_button (33 call sites) and danger_button (3) still put white labels on coral (1.98:1) and red (2.97:1). A follow-up PR takes those.

Screenshots

Rendered from the app's own views with gpui's offscreen Metal renderer and sample state, then cropped to each banner and the screen around it. Before is master at e1fcca57; after is this branch at 39cbf8da. Dark comes first; light is under each group.

Out of usage

Before After
Out of usage, before, dark Out of usage, after, dark
Out of usage, light
Before After
Out of usage, before, light Out of usage, after, light

Chat

Failed reply in the transcript (Error, ui/chat/transcript.rs). The row a turn leaves when its request fails.

Before After
Failed reply in the transcript, before, dark Failed reply in the transcript, after, dark

Task error under the transcript (Error, ui/chat/transcript.rs). A problem with the task itself; here the local store is busy.

Before After
Task error under the transcript, before, dark Task error under the transcript, after, dark

Task error on a new task (Error, ui/chat/mod.rs). The same error before the first message, under the composer.

Before After
Task error on a new task, before, dark Task error on a new task, after, dark

Unsaved changes (Error, ui/chat/mod.rs). Saving to the local store failed. Retry saving lines up at the end.

Before After
Unsaved changes, before, dark Unsaved changes, after, dark

Feedback dialog (Error, ui/chat/feedback.rs). Send pressed with nothing typed.

Before After
Feedback dialog, before, dark Feedback dialog, after, dark
Chat, light

Failed reply in the transcript

Before After
Failed reply in the transcript, before, light Failed reply in the transcript, after, light

Task error under the transcript

Before After
Task error under the transcript, before, light Task error under the transcript, after, light

Task error on a new task

Before After
Task error on a new task, before, light Task error on a new task, after, light

Unsaved changes

Before After
Unsaved changes, before, light Unsaved changes, after, light

Feedback dialog

Before After
Feedback dialog, before, light Feedback dialog, after, light

Sign-in and plan

Sign-in error (Error, ui/login.rs). Wrong email or password.

Before After
Sign-in error, before, dark Sign-in error, after, dark

Account deletion pending (Warning, ui/login.rs). Shown when the outcome of an account deletion is unknown.

Before After
Account deletion pending, before, dark Account deletion pending, after, dark

Plan check (Info, ui/plan_gate.rs). Check again found the account still on the Free plan.

Before After
Plan check, before, dark Plan check, after, dark
Sign-in and plan, light

Sign-in error

Before After
Sign-in error, before, light Sign-in error, after, light

Account deletion pending

Before After
Account deletion pending, before, light Account deletion pending, after, light

Plan check

Before After
Plan check, before, light Plan check, after, light

Settings

Keyboard Shortcuts: warnings and notices (Warning, Info, ui/settings.rs). Saved shortcuts that were rejected, a saved shortcut this build doesn't have, and the notice after recording. That notice now takes its tone from the outcome: applied or cancelled is Info, a key that can't be used is Warning, and a failed save is Error.

Before After
Keyboard Shortcuts: warnings and notices, before, dark Keyboard Shortcuts: warnings and notices, after, dark

Keyboard Shortcuts: Reset all (Warning, ui/settings.rs). Confirmation before resetting changed shortcuts. Reset is the neutral default button.

Before After
Keyboard Shortcuts: Reset all, before, dark Keyboard Shortcuts: Reset all, after, dark

ACP: running from a temporary location (Warning, ui/settings/acp.rs). Maple was opened from a disk image or another temporary location.

Before After
ACP: running from a temporary location, before, dark ACP: running from a temporary location, after, dark

Integrations: adding a custom server (Warning, ui/settings/catalog.rs). Add pressed without a server name and URL.

Before After
Integrations: adding a custom server, before, dark Integrations: adding a custom server, after, dark

API: the Local API couldn't start (Error, ui/settings/local_api.rs). The chosen port is already in use.

Before After
API: the Local API couldn't start, before, dark API: the Local API couldn't start, after, dark

Billing: subscription portal opened (Info, ui/settings/billing.rs). After Manage subscription opens the browser.

Before After
Billing: subscription portal opened, before, dark Billing: subscription portal opened, after, dark

API credits: checkout opened (Info, ui/settings/api_credits.rs). After Pay with card opens the browser.

Before After
API credits: checkout opened, before, dark API credits: checkout opened, after, dark

Account: verification email sent (Info, ui/settings/account.rs). After Resend email on an unverified account.

Before After
Account: verification email sent, before, dark Account: verification email sent, after, dark
Settings, light

Keyboard Shortcuts: warnings and notices

Before After
Keyboard Shortcuts: warnings and notices, before, light Keyboard Shortcuts: warnings and notices, after, light

Keyboard Shortcuts: Reset all

Before After
Keyboard Shortcuts: Reset all, before, light Keyboard Shortcuts: Reset all, after, light

ACP: running from a temporary location

Before After
ACP: running from a temporary location, before, light ACP: running from a temporary location, after, light

Integrations: adding a custom server

Before After
Integrations: adding a custom server, before, light Integrations: adding a custom server, after, light

API: the Local API couldn't start

Before After
API: the Local API couldn't start, before, light API: the Local API couldn't start, after, light

Billing: subscription portal opened

Before After
Billing: subscription portal opened, before, light Billing: subscription portal opened, after, light

API credits: checkout opened

Before After
API credits: checkout opened, before, light API credits: checkout opened, after, light

Account: verification email sent

Before After
Account: verification email sent, before, light Account: verification email sent, after, light

Validation

  • Tests:
    • out_of_usage_offers_billing_from_the_composer_until_a_later_turn replaces out_of_usage_banner_opens_billing. When the latest turn ran out of usage, the composer offers Open Billing, pressing it asks once for Settings → Billing, and the transcript keeps the marker row.
    • The offer goes away for a message sent since, for a next turn that failed for another reason, while a run is active, while a message is on its way, with live rows past the window, and on a history page.
    • It comes back for a refusal that arrives live, labelled by the runtime rather than matched by its text.
    • text_roles_read_on_every_surface now also covers bg_composer_tray, default_button at 7:1 (rest, hover, press) and accent_ink on accent_container at 3:1, in both palettes.
  • CI on 39cbf8da: ci-ok, agent-lint, agent-test, agent-macos and agent-windows pass.
  • This Mac has no Nix, so just ci did not run on the pinned toolchain. Everything below ran at 39cbf8da on rustc 1.96, through the component's just environment on the shared Agent cache:
    • cargo test -p maple-agent-app --bin maple-agent: 1,341 passed, 0 failed, 3 ignored. That includes the i18n catalog tests (every_source_message_is_used_by_production_code, translations_were_reviewed_against_the_current_english) and the contrast test.
    • Clippy with -D warnings passes on the workspace (all targets) and on acp,proxy (all targets). On 762527c0 it also passed on acp and on proxy, and the later commits are UI-only. It allows two lints this rustc raises in code this PR does not touch: unexpected_cfgs from the objc macros in desktop/native_quit.rs and desktop/native_window.rs, and clippy::nonminimal_bool.
    • just fmt-check passes; just git-metadata-test passed at 762527c0.
  • Reviewed by an independent reviewer and three advisors: Codex, then Grok in place of Gemini, which was out of credits, then Claude interrogating the first two. Their confirmed findings are fixed in 39cbf8da: the window-budget check, the light gauge contrast, per-outcome shortcut tones, the accessible names, the test cases above and the doc wording.
  • The screenshots above are the app's own views rendered by gpui's offscreen Metal renderer, using a local harness that is not part of this PR, and cropped to each banner. They cover all 18 call sites and the out-of-usage panel in 17 states, in both themes, before and after. The after set is from 39cbf8da, and each was checked by eye.

Notes

  • UI only. No persisted values, protocol strings or host error codes change. USAGE_LIMIT_MESSAGE stays the runtime's fixed message, and rows stored before error labels are still recognized by it.
  • The transcript's open-billing-<item> button is gone. The composer's button is composer-open-billing.
  • The panel appears only in the task whose last turn ran out of usage. The app does not know the account's usage ahead of a turn, so a new or different task shows nothing until its own turn runs out.
  • Remote tasks: the panel reads the same timeline a paired computer sends. Open Billing opens this computer's Settings → Billing, as the transcript button did.
  • Known, not changed here:
    • Billing, API credits, Account and the plan gate keep one Info tone for success and failure alike, as their grey pill had no tone at all. Storing a tone per outcome there belongs with the Settings regroup, which already turns Billing's notice into a dismissible notice.
    • A banner's text is not announced as an alert; it was not before either, since the card has no id or role.

Merge order

Whichever of an overlapping pair lands second merges master into its branch with git merge origin/master, not a rebase, and resolves the hunks below. The choices are the same in either direction. If this PR is last, its own update takes all of them, and the PRs ahead of it need nothing for it.

After resolving, run rg 'widgets::banner\(theme::|shortcut_notice = Some\(UiText' apps/maple-agent/app/src until it prints nothing, then just ci. Git merges those lines silently, but they stop compiling against this PR's Tone and (Tone, UiText). Anything that lands first can add more of them. The real check is that one merge after the first two batches land; the trial results below are only a reference.

…atus banners neutral

Running out of usage put a solid coral card with white text into the
transcript. widgets::banner painted 13 other errors and warnings the
same way; in dark, white on status_error is 2.97:1 and on status_warning
2.14:1, under the 4.5:1 body text needs.

- When a task's latest turn ended out of usage, a panel docked inside
  the composer offers Billing until another turn starts. The transcript
  keeps a one-line "Out of usage" marker of the turn.
- widgets::banner takes a Tone (error, warning, info) and draws a
  neutral card with the tone's icon on a tinted tile. All 18 call sites
  move; the Shortcuts reset confirmation's Reset is a secondary pill.
- Tokens bg_composer_tray, bg_inverse(_hover), on_inverse and
  accent_ink, held by the contrast test; widgets::inverse_button; Lucide
  gauge, triangle-alert, info and arrow-right.
- Strings chat-usage-limit-title and chat-usage-limit-body (en, es).
  USAGE_LIMIT_MESSAGE and stored timeline text are unchanged.
…panel and Reset

The out-of-usage panel's Open Billing and the Shortcuts Reset now use
widgets::default_button with the bg_default_button tokens, held to 7:1
at rest, hover and press, instead of a second inverted pill
(inverse_button, bg_inverse, on_inverse). The Settings regroup adds the
same pill, so the two branches share one definition.

The out-of-usage test also covers a refusal that arrives live and a
history window paged back from the end. docs/brand.md scopes its rule
to banners: primary_button and danger_button keep their labels until
the buttons' follow-up.
- The composer panel also stays hidden while live rows sit past the
  loaded window (pages.live_has_more), not only when paged back.
- Light accent_ink is Maple 800: the panel's gauge reads 3.83:1 on its
  coral tile, and the contrast test now holds that pair to 3:1.
- Shortcut notices carry their own tone: applied and cancelled are
  info, rejected keys and navigation hints are warnings, failures are
  errors, instead of one warning tone for every outcome.
- The transcript marker shows the row's own title key, so its text
  matches its accessible name; Reset, Cancel and the unsaved-work lines
  are exposed through gpui::text!.
- The out-of-usage test covers a labelled refusal arriving live, an
  active run, a message in flight, live rows past the window and a
  history page, and checks the marker row stays.
- docs/brand.md: the composer panel leaves the field editable, and the
  light status_warning value matches theme.rs.
matthewrball added a commit to matthewrball/Maple that referenced this pull request Nov 8, 2026
matthewrball added a commit to matthewrball/Maple that referenced this pull request Nov 8, 2026
@matthewrball
matthewrball marked this pull request as ready for review November 8, 2026 21:30
@matthewrball

Copy link
Copy Markdown
Collaborator Author

@doowta this is the one I mentioned in the Design channel. Since you're working on related things, I'd love your eyes on it.

@matthewrball
matthewrball removed this pull request from stack #1519 November 10, 2026 03:16
@matthewrball
matthewrball changed the base branch from ux/settings-redesign to master November 10, 2026 03:16
matthewrball added a commit that referenced this pull request Nov 10, 2026
@matthewrball
matthewrball force-pushed the ux/usage-notice-redesign branch from b113f21 to ec7eb27 Compare November 10, 2026 03:16
@matthewrball
matthewrball added this pull request to stack #1535 November 10, 2026 03:16
Master now has #1507, #1512 and #1513. Same resolutions as before:
#1510's toned banners (including #1507's two MCP notices), #1507's
dismissible billing notice, guest-only checkout note, catalog, icons and
link marks.
matthewrball added a commit that referenced this pull request Nov 10, 2026
marksftw
marksftw previously approved these changes Nov 11, 2026

@marksftw marksftw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready for code review.

AnthonyRonning and others added 2 commits November 10, 2026 17:38
Master gained the German catalog (#1529) after this branch was cut, and
every shipped catalog must hold the same keys.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@AnthonyRonning
AnthonyRonning merged commit a202671 into master Nov 11, 2026
23 checks passed
@AnthonyRonning
AnthonyRonning deleted the ux/usage-notice-redesign branch November 11, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants