chore: 5.2.0 release branch#50
Open
dnviti wants to merge 9 commits into
Open
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Prepares the chore/version-bump-5.2.0 release branch container for v5.2.0 by bumping the package version and creating a placeholder changelog section to collect entries from release-targeted PRs.
Changes:
- Bumped
package.jsonversion from5.1.2to5.2.0. - Bumped
package-lock.jsonversion fields to5.2.0. - Added an Unreleased
[5.2.0]section toCHANGELOG.mdas a placeholder for incoming entries.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| package.json | Updates the package version to 5.2.0 for the release branch. |
| package-lock.json | Keeps the lockfile’s version metadata in sync with 5.2.0. |
| CHANGELOG.md | Adds a new [5.2.0] - Unreleased section to accumulate release notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
* feat(mobile): a phone type scale and touch targets for the chat surface The phone layout was the desktop layout, smaller. Nothing that ran had a viewport small enough to see it: every browser check mounted at 900px or wider, so four minor versions shipped with 10px cost figures and 16px controls two pixels apart. - ui/touch.ts holds the phone scale — type steps, a 44px target floor, an 8px gap — in one place, and publishes it through a context so a copy button five components deep gets the same answer as the header. - The session header is a separate layout on a phone rather than the same row with smaller values: one wrapping row of information at a readable size, one row of controls a finger can hit. It drops the Beta badge and the terminal and theme toggles, all of which the more sheet already has. - The composer, the turn strips, the message bubbles and the stream ribbon follow the same scale. The composer no longer detects "phone" itself — it takes the surface's answer, which is what made it impossible to examine at the size it ships at. - test/browser covers three phone viewports (portrait, short, landscape) and asserts geometry rather than intent: target size, neighbour spacing, type size, the named live figures, and nothing pushed off the side. - test/browser/page.html now loads the app's own stylesheets. Without them every `var(--text-2xs)` resolved to nothing and inherited 16px, so a check measuring type size would have read 16px for text shipping at 10. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(mobile): carry the phone scale into the sheets behind the chat The first pass fixed what a closed phone surface shows. This opens the three sheets a phone actually reaches — the trace rail, the turn index and the model list — and applies the same rules there, because a check only covers what its fixture opens. - Workspace tabs, the activity filter row and its event rows, the turn index and its rows, the tool-call headers and the model list all take the phone type scale and the 44px floor. - The model list anchors to the composer rather than to its chip on a phone: a 200px list hanging off a chip near the right edge of a 390px screen was drawn half off-screen. - Its custom-name field is set at 16px, not 15 — below that, iOS Safari zooms the page as soon as the field takes focus and does not zoom back. The check mounts fresh per state rather than toggling: a second click on the opener left the sheet up and blamed it for every state after. Sideways scrollers and sheet scrims are recognised for what they are — a tab strip that scrolls has a way back to what it hides, and a scrim abuts the sheet on purpose — so neither is reported as crowding. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(mobile): size the shell's own phone surfaces for a thumb The bottom bar, its more sheet and the tab switcher are the app shell's, not the chat surface's, so a ChatView fixture never reaches them. They get their own mount, in an iframe: two of the three are Dialogs, and a Dialog portals to document.body and positions against the viewport — measured in the main page it would be 800px wide whatever the host div said, so the one question being asked would have been answered about a desktop. - The bar's labels were a 10px caption under a 20px glyph; they are now the phone's meta size. It exists only on a phone, so it takes the scale outright rather than asking whether it is on one. - A bottom-placed Dialog is the phone form of a dialog, so its close control is sized for a thumb rather than a cursor — 17x19px of glyph in the corner of the screen was what a finger had to find. - The tab switcher's rows, its per-tab close and its two footer buttons all clear the floor. The terminal's key strip is deliberately untouched and out of the fixture: issue #51 lists the terminal's own on-screen controls as a non-goal. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(mobile): label the phone's controls, and stop the transcript overflowing Two things the geometry checks could not see, found by rendering the phone layout and looking at it. The controls were the right size and still unidentifiable: a `panel-left` glyph does not say "turn index" to anybody, and on a touch screen there is no hover to reveal the title that said so — pressing it and watching what happened was the only way to find out. The header's four controls and the composer's now carry the word as well as the glyph, and a check asserts it, because `title` and `aria-label` both answer the question for nobody holding a phone. And the transcript kept `min-height: 160px`. A flex item cannot shrink below its min-height, so on a screen with less than that to spare the floor did not reserve room — it overflowed the column and painted the conversation over the live ribbon and the composer. A phone in landscape has about 160px for the conversation once the header, ribbon and composer have taken theirs, which is where it showed first. Also here: the message actions move to their own line on a phone (beside the text they were a 44px column that made a two-line message four lines tall), the composer's action row flows as one wrapping row instead of leaving Send alone on a line, and the phone hint line goes — at the phone type size it truncated to half a sentence, and what it said is now written on the two buttons above it. The check learned two things while finding these: a control clipped by a scroller has a box that says nothing about where it is painted, and the model chip falls back to the word "model", so a fixture that never delivered one was measuring the placeholder and passing. Closes #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(mobile): give the shared controls the touch floor, once Adversarial pass. Three things it turned up. The dialogs a phone reaches — chat display settings, the session list — were still drawn for a cursor: 16x19px close, 27px selects, 29px icon buttons. Fixed in `Button`, `Select` and `IconButton` rather than in each dialog, which is what a design system is for: a surface reached from the bottom bar gets the floor without knowing it is on a phone. And the shell publishes the phone answer at its root, so those dialogs — which render beside every conversation rather than inside one — can read it at all. `ui/touch.ts` had three exported helpers and two constants nothing used. A module whose job is to be the one place should hold the rules in force, not a wishlist. `UsageMeter`'s new name was on a plain div, where most screen readers ignore it. It has a role now, so the name lands. The check learned that `getBoundingClientRect()` measures the painted box: a dialog's entrance animation scales it, so a 44px button measured 43.12 for as long as the animation ran — and headless Chrome does not reliably finish one. How big a control is, is a layout question. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(mobile): a floating menu, a chrome that collapses, and the screen back Reworked after seeing the first pass on a real phone. Every rule from it still holds; what changed is how much of the screen the chrome is allowed to hold while holding them. - The bottom bar is gone. Five slots along the bottom edge cost 56px plus the safe-area inset of a surface whose whole point is what is above them, and could only ever show five things — so everything else lived in a sheet reached through it anyway. `FloatingMenu` is one 56px square in the bottom right and a labelled list behind it. The header's four controls join it there, so it is one menu rather than two. - The session header is a strip: what it is doing, what it has cost. Tap it for the runtime, the folder, the branch, the tokens, the context meter and the approvals state. - The composer is the field, More, and send — on one line. The rest is behind More. Its stop is gone from a phone: stopping is possible exactly while the ribbon above it is on screen, and the ribbon has a labelled one. - The ribbon holds one line and drops the elapsed/token figures rather than wrapping to two. The conversation went from 43% of a 390x844 screen to 79%, and the chrome from 215px to 159px at every viewport — asserted, because nothing else here would have noticed it drifting back: every other rule is about the chrome being big enough, which pushes the other way. Three things this turned up that were not about the redesign: - The menu animated in from `opacity: 0`, and headless Chrome never advanced the frame — so every rule silently skipped the panel as invisible and reported the state clean. It rises without fading now, which also means it is there under reduced motion and on a dropped first frame. The check asserts it is actually on screen. - The menu button and its own scrim were both at `--z-overlay`, so which one a tap reached came down to document order. Said outright now, and asserted by what `elementFromPoint` returns at the button's centre. - The browser checks ran in an 800x600 window while mounting a 390x740 fixture, so a third of it was off-viewport and anything asking the viewport a question got the wrong answer. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: fold the two phone entries into one that agrees with itself The redesign superseded half of what the first entry described — the header's control row and the bottom bar it referred to are both gone — so the section described two layouts, neither of which shipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(mobile): the bottom bar comes back as destinations, not a drawer The bar the redesign removed held Sessions, New, Panel, Image and More — a place to go, a thing to make, a panel to toggle, a file to attach and a sheet of everything left over. Five slots of the most valuable chrome in the app, spent on a list with no idea in it. Removing it was the wrong answer to that; rethinking what it is for is the right one. What this app does is run agent sessions, and inside one there are four places worth being — the conversation, what the agent did about it, the files it did that to, and a shell in the same directory — plus the other sessions. `BottomNav` is those five, it paints which one you are on with `aria-current="page"` and a rule that survives monochrome, and pressing one goes there. The verbs stay on the floating button: a control that changes where you are and a control that does something to where you are do not belong in the same row. It hides while the on-screen keyboard is up — the keyboard takes half the screen and the half it leaves is the half being typed into, which is the one moment nobody is navigating. Making the bar able to say where you are meant one answer for the rail rather than two, and that surfaced a real bug behind the local state that had been hiding it: `panelOpen` is persisted, and on a phone the rail *replaces* the conversation, so the stored preference opened every conversation behind a panel. My first fix cleared it on mount — which writes the phone's answer into a shared setting and closes the rail on the desktop that set it open. It is session state held by the shell now, and the preference is not touched. The chrome costs 216px with the bar, against 159 without and 215 before any of this — so the conversation keeps about seven tenths of a phone screen rather than eight, and the bar is worth that. Two more fixture lessons, both the same shape as the last: the browser fixture has to compose the surface the way the shell does, or it measures something the app never renders. It passed `DEFAULT_CHAT_VIEW` straight through with a no-op setter and spent a whole run measuring the workspace panel instead of the conversation; and the shell's own tests shared a store without resetting `chat`, so a case that put a conversation on screen broke the key-strip case three tests later. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(mobile): the jump-to-latest pill kept its own height, under the floor CI caught it on Node 22 and not on Node 24, which is the tell: the pill only exists while the transcript is *not* pinned to the bottom, so whether a run saw it came down to timing. The check now scrolls the conversation back on purpose, which is the state that control lives in. Two ways to defeat a primitive's touch floor from a call site, and this hit both. `height: 34` overrode it outright. Replacing that with `height: isPhone ? undefined : 34` was worse: the style object is spread over the primitive's own, so a present key wins whatever its value is — React drops an undefined one and the pill came out 17px tall. The key is omitted on a phone now. Refs #51 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
An attachment uploaded through a conversation — a screenshot — was swept into a commit by a `git add -A` and merged. It is data written by the running app into whatever project it is pointed at, never source. Removed from tracking and ignored going forward. The blob stays in the history of the merge that carried it; rewriting a pushed release branch to excise one screenshot is the more disruptive of the two options, so that is left as a deliberate call rather than taken here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An entry in the agents list was one row: a name, a status badge, a duration. That is all you got whether it was a sub-agent reading three files or a workflow running a dozen agents across four stages. Any entry now opens into a popup with the file editor's chrome — movable, resizable, fill-the-window — mounted by the rail rather than by the tab that opened it, so looking at the file tree does not close it. A sub-agent (#44) shows its own work. The runtime has been reporting that all along: `task_started` / `task_progress` / `task_updated` alongside messages tagged with a `parent_tool_use_id`. Nothing read the tag, so a sub-agent's own tool calls were emitted as patches keyed by an id no block in the conversation owned, and the reducer filed every one of them into `orphanToolPatches` where nothing would ever render them. They now land on the delegation that made them, as an `AgentRun` on its tool block, fed by two new events — `agent_step`, keyed by parent *and* by step so a result finds the call it belongs to, and `agent_progress` for the run as a whole. A step's failure is shown with the message it failed with, which is the part a badge cannot say. A workflow (#45) shows the stages it narrates. There is no structured progress channel for one, so `parseWorkflowLog` reads whatever heading style the run uses and degrades to a flat log rather than inventing structure that is not there. The parented envelopes are also now barred from the streaming path. They carry the sub-agent's own message and block indices; letting one through would open blocks in the main conversation as though the top-level agent had said them. The adapter tests replay captured traffic from a real sub-agent run (fixtures/chat/claude-subagent.jsonl), not hand-written events. The browser checks drive the whole ChatView, because the composition is where the interesting failures live: mounting the popup inside the panel passed every check while a tab change silently destroyed it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A step the agent spent entirely on tools still drew a row: a glyph, a clock, three action buttons and a work pill with no sentence beside them. A long task put half a dozen of those between one reply and the next. Those steps no longer get a row. The list holds their ids until a message that does speak comes along and hands them to it, so the reply's pill counts the whole stretch and opens the trace at the *start* of it rather than at the reply's own last command. Nothing is hidden — the rail still holds every call, the turn strip still counts them, and a trace row or a search hit aimed at a suppressed step now lands on the reply that speaks for it instead of scrolling to an element that is not there. The carried ids travel as a joined string, and the bubble subscribes to the steps it carries: a tool call can report its duration after its own message has closed, and the pill that inherited it has to hear about it. Both stay inside the per-message subscription tier, so a streaming turn still re-renders one bubble rather than the list. Kept: a reply that has opened and produced nothing yet still shows its caret. That is an answer about to arrive, not machinery. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 tasks
The sidebar groups sessions by calendar day. The fixture built its timestamps from `Date.now()`, so a run in the first two hours of the day put "five minutes ago" and "two hours ago" on *yesterday* — leaving the Today group the assertions look for legitimately empty, and the suite red for reasons that had nothing to do with the code under test. Anchored to midday instead, which keeps every offset on the day it was written to mean whatever time the suite runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…to it (#54) A name the user chose used to live only in the page that typed it. The strip read it out of its own state, nothing stored it, and the next page load — a reload, a second window, a restart — rebuilt every tab from the name the session was created with. The one moment the labels matter, coming back to a set of long-running sessions, was exactly the moment they were gone. The chosen name is now a field of its own on the session record, stored beside the created name rather than over it, so a session nobody renamed still goes through the generated-name rules and shows its folder. A new nullable `custom_name` column carries it across a restart. `PATCH /api/sessions/:id/name` takes the rename and tells every socket the user has open, so a window that is already looking at the same sessions follows without reloading; the page that asked renames optimistically and puts the old label back if the server refuses. The same reload also used to lose the user's place. The selected tab is now remembered in the browser — sessionStorage first, so two windows keep their own tab, with localStorage behind it so a newly opened window still lands somewhere sensible — and a remembered id for a session that is gone falls back to the first tab rather than an empty screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(sessions): a renamed tab stays renamed, and a reload comes back to it (#54)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release container for v5.2.0. Feature and fix PRs for this release target this branch (
chore/version-bump-5.2.0), notmain— same flow as #37 for 5.1.2. Each merged change adds its own entry to the[5.2.0]changelog section; the section gets its date when this PR merges.Delivered in this release
These are implemented on the branch and close when this PR merges.
Still open, not yet in this branch
Linked for tracking only — no closing keyword, so they survive this merge and move to the next release unless they land here first.
Gates before merge
npm test(unit suite) — 1381 passing🤖 Generated with Claude Code