Skip to content

feat: move the update notice into the title bar - #13

Merged
tranphucbol merged 2 commits into
mainfrom
feature/update-in-the-title-bar
Sep 12, 2026
Merged

tranphucbol merged 2 commits into
mainfrom
feature/update-in-the-title-bar

Conversation

@tranphucbol

@tranphucbol tranphucbol commented Sep 12, 2026 •

Copy link
Copy Markdown
Owner

1. Purpose & Motivation

Description

Decision 054 gave the updater a full-width Banner above the workspace, and to do it the Banner had to grow a third tone, info, meaning "read me, but nothing is wrong". That tone argued with its own component. A release announcement rendered at the same width and in the same shape as a dead engine host, and because Banner is shrink-0 and a sibling of the resizable panel group, postponing the update cost a permanent 40px row off the editor for as long as the answer stayed "not now".

This PR moves the notice into the title bar as a chip beside the Settings gear, deletes the UpdateBanner component, and retires the info tone that existed only to serve it.

Context & Flow

main pushes UpdateStatus → useUpdateStore
                              │
                              ├── [OLD] updateBanner(status)
                              │           │
                              │           ├── available / ready → <Banner tone="info"> below TitleBar
                              │           │                          └── a 40px row off the Group
                              │           └── every other phase → null
                              │
                              └── [NEW] updateChip(status)
                                          │
                                          ├── available    → accent <button> ► downloadUpdate()
                                          ├── downloading  → neutral <span>  ► not pressable
                                          ├── ready        → accent <button> ► installUpdate()
                                          └── idle / checking / current /
                                              unsupported / failed → null

2. Key Changes

Description

updateBanner() in renderer/model/update.ts becomes updateChip(), returning { label, detail, title, action } where action is "download" | "install" | null. It answers for three phases rather than two: downloading is now showable, which is the one thing the move adds rather than relocates. A bar could not report the user's own press back at them; a chip is the control they pressed and can survive the download.

UpdateChip in App.tsx renders in TitleBar's trailing group, before the IconButton. renderer/ui/Banner.tsx drops info from BannerTone, which collapses ICON_BY_TONE to a single WarningIcon and removes the map entirely. InfoIcon leaves renderer/ui/icons.ts, which is the app's icon audit and had no other caller.

Technical Decisions

The chip sits before the gear, not after it. With the chip last, the trailing flex group widens leftwards when it appears, moving the gear once a release. With the chip first, the gear stays against the right edge and the chip grows into the empty run. The first draft of decision 055 argued the opposite and was wrong; the record says so.

Its glyph is ArrowCircleUp, not ImportIcon. ImportIcon is the sidebar's "Import from cURL or grpcurl" tray, four rows below in the same window.

The downloading phase is a <span>, not a disabled <button>. Chromium emits no pointer events from a disabled button, so its tooltip never opens — and a control whose whole content is "wait" needs the tooltip more than the press. design-system.md already states this rule for the gRPC field lead.

The accent stops needing a carve-out. Decision 054 had to argue the banner was the one place the accent appeared without being the thing the user came to press. In the chrome it is that thing, and it is the only accent in the row.

Critical Modules/Files

  • packages/desktop/src/renderer/App.tsx — the ordering inside TitleBar's trailing group is load-bearing, and UpdateChip branches on action === null to pick <span> over <button>
  • packages/desktop/src/renderer/ui/Banner.tsx — narrowing BannerTone is a public-ish change; a future non-warning tone has to reintroduce the icon map, and the docblock says so
  • docs/decisions/055-the-update-lives-in-the-title-bar.md — new, and amends 054's last consequence in place

Recommended Review Order

  1. packages/desktop/src/renderer/model/update.ts — the phase-to-words mapping
  2. packages/desktop/src/renderer/App.tsx — TitleBar and UpdateChip
  3. packages/desktop/src/renderer/ui/Banner.tsx and ui/icons.ts — the deletions
  4. docs/decisions/055-*.md, docs/decisions/054-*.md, docs/design-system.md
  5. test/renderer/settings.test.ts

3. Verification & Testing

Provide reproducible inputs or steps for each test case, followed by the result and supporting evidence.

All four screenshot pairs were taken by driving a built app over CDP against test/fixtures/ws, pushing each UpdateStatus into the store directly. The before shots are the same build with App.tsx, model/update.ts, ui/Banner.tsx and ui/icons.ts checked out at origin/main.

  • With phase: "available", the title bar shows an accent chip reading Update <version> immediately left of the Settings gear, and pressing it starts the download

    Before: the full-width info banner, one row tall, pushing the workspace down. After: a chip in the bar the gear is already in.

    before-available-titlebar.png
    after-available-titlebar.png

  • With phase: "downloading", the chip reads Downloading <percent>% in neutral tone and is not pressable; with no content-length it falls back to the version

    This is the phase the move adds. Before, updateBanner() returned null for downloading, so the window said nothing at all once the user pressed Download — the first shot is an empty title bar. After, the chip reports the percentage it is at.

    before-downloading-titlebar.png
    after-downloading-titlebar.png

  • With phase: "ready", the chip reads Restart to update <version> and pressing it quits and installs

    The glyph changes to RefreshIcon and the label carries the whole verb, so the chip is readable without the tooltip.

    before-ready-titlebar.png
    after-ready-titlebar.png

  • With idle, checking, current, unsupported or failed, no chip renders and the Settings gear sits at the same x as when a chip is present

    The no-chip bar, to compare against the available shot above: the gear is at the same pixel in both. This shot is also byte-identical to the before-downloading one, which is the ordering claim stated another way — with the chip ahead of the gear, its presence changes nothing to the right of it.

    after-none-titlebar.png

  • The Settings pane's Updates section is unchanged — Skip still sits beside Download on available

4. Impact and Scope

Renderer only. No change to packages/core, packages/cli, the engine utility process, the main process, or the preload bridge — UpdateStatus and its four verbs are untouched, so the updater protocol described in decision 054 still holds end to end.

BannerTone narrows from three members to two. Every remaining Banner call site passes danger or warn, so the change is compile-checked; a new info caller will fail typecheck.

The workspace gains back the 40px row the banner occupied while an update was pending. The Settings pane's Updates section is unchanged, and Skip stays there — it is the one update action the chip deliberately does not carry.

A user who never looks at the title bar will not learn about an update. That is accepted in decision 055: an app that replaces itself is not so urgent that it earns a strip across the window.

@tranphucbol
tranphucbol merged commit 819024a into main Sep 12, 2026
3 checks passed
@tranphucbol
tranphucbol deleted the feature/update-in-the-title-bar branch September 12, 2026 08:09
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.

1 participant