Skip to content

[fix] Close the regressions the mobile-extraction lane introduced - #6112

Merged
ardaerzin merged 537 commits into
lane/mobile-extracted-packagesfrom
fix/post-112-reconcile
Aug 20, 2026
Merged

ardaerzin merged 537 commits into
lane/mobile-extracted-packagesfrom
fix/post-112-reconcile

Conversation

@ardaerzin

@ardaerzin ardaerzin commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

Context

#6065 moved the desktop UI into the @agenta/* packages. A number of features did not survive the move, and they were not removed by any commit. They were dropped in merge resolutions, which is why git log -S <symbol> finds only the commit that ADDED them. Every one leaves the same fingerprint: a symbol that still exists, still type-checks, and has zero consumers.

They were found by putting local EE next to deployed v0.112.1, surface by surface, and diffing screenshots per page strip.

What a user had lost:

  • Typing / in the agent composer did nothing. The whole palette was unwired.
  • Hiding the agent config panel was a one way trip. Nothing brought it back.
  • Files opened as an overlay drawer. Production docks it as a resizable pane.
  • Clicking a trace in Observability never opened the trace drawer.
  • Sticky table headers were translucent, so pinned columns showed the columns scrolling underneath them.

This branch also merges release/v0.112.2, which the lane does not have. Part of the diff is that merge.

Changes

Features the merge dropped, restored as 112.2 ships them

What Where it went
/ command palette useChatSlashCommands had no consumer. AgentComposerDock lost the hook, both picker panels and the slashCommands prop. The shared ChatComposer also did not forward the prop, so it takes one now.
Config panel reveal (») SessionTagBar lost its leftExtra slot when the strip moved to @agenta/sessions-ui, leaving ShowConfigPanelButton unrendered.
Docked Files pane AgentChatPanel lost RightPanelSplit + SessionFilesPane, and AgentConversation had gained a SessionFilesDrawer that 112.2 does not have.
iconPosition="end" The EnhancedButton facade listed it as "rare / unused" and dropped it. Five call sites pass it, so every trailing icon rendered leading. Learn More → had become → Learn More.

The trace drawer

Clicking a trace did nothing. Three separate read-modify-write races on the URL, each of which had to be fixed before the next became visible:

  1. The click wrote trace and span as two navigations. requestNavigationAtom is a single slot, so the second command replaced the first, and each Router.replace built its query from one the other had not committed. Router.replace returned false. Now one patchQuery({trace, span}).
  2. A selectedTraceId effect fired 50ms later with a stale traceParam and cancelled that navigation. It now skips the selection the click already queued.
  3. The drawer's own query seams rebuilt the whole query from router.query, a render time snapshot, so they pushed a query that predated the click and wiped it. They read the live URL now, and return early when the URL would not change. That last guard matters: without it a no-op write pushes an identical URL, which re-renders, which calls the seam again.

Closing had its own problem. The params were cleared 320ms after the slide started, and syncTraceStateFromUrl reopens a closed drawer while ?trace= is still present, so those 320ms were a window in which the drawer reopened itself. Clearing now happens in the same action that closes, and the sync opens on a param CHANGE rather than re-asserting on every sync.

Virtual table

Header cells were painted with colorFillQuaternary, a 4% white wash. Fine on a static header, wrong on a sticky one: scrolled sideways, the pinned Name header showed Inputs straight through it and both were legible at once. Every header cell is opaque now, same tone, painted as a gradient layer over colorBgContainer.

The selection and expand columns were not treated as columns. They never got border-r, so the row select column ran into Name with no rule between them, and the table had no outer border at all. They also stacked in the opposite order in the header and the body.

Panel motion

Collapsing the config panel did not animate. The transition latch watched chatPanelMaximizedAtom, but the « control writes configPanelCollapsedAtom, so the only reachable control was the one that did not ease. The RightPanelSplit never animated either, because it detected the flip with a render phase setState that React collapses before commit.

Animating the width also reflowed the contents: tiles reflowed into fewer columns, labels rewrapped, the column outgrew the pane. During the slide the content now leaves the flow at a fixed pixel width, pinned to the edge the box collapses towards, so the shrink reads as a translation. It goes back to responsive the moment the slide ends, because during a drag the reflow is the feedback.

Two closed right hand panels were also painting 18px of gutter against nothing, because RightPanelSplit never passed barHidden.

Earlier in the branch

Settings and sidebar details the extraction dropped, the v0.112 drift remediation, and ports of #5927, #5942, #5943, #5946 and #5974 into the lane's idiom.

Tests / notes

  • pnpm lint-fix, tsc for oss and mobile, and the @agenta/ui and @agenta/sessions-ui suites are green.
  • Each visual fix was measured, not eyeballed: ink bands, computed styles, and transitionstart / transitionend elapsedTime for the motion. Sampling requestAnimationFrame is useless on the dev build, which starves it badly enough to make a working transition look like a snap.
  • docs/design/sessions-ux-stack/ee-vs-112.1-diff-inventory.md carries the full record, including the findings that turned out to be wrong and why.
  • Every fix in this list was driven in a browser, open and close paths included.
  • Worth a reviewer's attention: three of these bugs live in shared primitives (@agenta/ui Button, the virtual table, the drawer facade), so their blast radius is wider than the surface each was found on.
  • Untouched and still unexamined by this pass: @agenta/entity-ui (86 changed files against 112.2), agent Home and the onboarding canvas, auth, and the shared drawers.

What to QA

  • Agent playground, composer: type /. The palette opens with /model, /permissions and /new. Enter on /model docks the model picker to the composer. /new adds a session tab.
  • Agent playground: click « in the Configuration header. The panel slides shut, and » appears at the left of the session bar. Click it. The panel slides back. The contents must not reflow while it moves.
  • Agent playground, session bar: click « at the right. Files docks as a resizable pane and pushes the chat aside, rather than opening a drawer over it.
  • Observability: click a trace row. The drawer opens and STAYS open, and the URL gains ?trace= and ?span=. Close it. The params go and the drawer stays shut.
  • Observability: scroll the table sideways. The pinned Name column hides the columns passing under it, the row select column has a rule against Name, and the table has an outer border.
  • Evaluations, empty state: the secondary button reads Learn More →, arrow last. Same for the agent Home composer and the template strip actions.
  • Regression to watch: the session tabs in the playground. They were restyled to match production (filled chip, hairline dividers), so check rename, close and drag to reorder still work.

mmabrouk and others added 30 commits August 13, 2026 16:34
A hand-added model id absent from Pi's registry is registered into the
per-run models.json with its base model's real metadata, so it runs as
a first-class model; the operator's mounted Pi folder is never written.
The Pi probe also names which provider logins the mount holds (OAuth
entries only, family names only), the service passes them through a
closed allow-list, and subscription pairs derive from the live answer,
which is how a ChatGPT login in Pi becomes a visible pair.
The connect gate opens the providers drawer directly, and a connection
created from onboarding auto-switches the agent to its first model and
commits, leaving the user typing; a second connection added mid-session
never touches the current pick. The dead duplicate gate hook is deleted
rather than synced. The settings table matches its siblings (Provider
and Name columns, Created, an actions menu), Preferences reads Feature
flags with a Developer mode toggle describing exactly what it gates,
the eval stepper's all-rows hover highlight is fixed at its CSS root,
and the dev api gains the graceful-shutdown bound that ends the reload
deadlock.
The onboarding auto-switch pulses the config section through the shared
draft signal; the new origin keeps it out of the approval dock's Undo.
The runner gains an authenticated GET /subscription-status that inspects
the mounted login location per harness (codex, claude, pi_core,
pi_agenta) and returns only state words. The Python agent service proxies
it at POST /runtime/subscription-status behind the same auth as invoke,
mapping network failure to unavailable and an old runner to incompatible.
The self-managed credential card shows the status for the selected
harness, polls while visible, and offers Check again. The status never
claims provider access; a run remains the proof.
…check by provider

The fake secrets DAO discarded project_id and organization_id, so every test shared one
namespace: provider numbering and saved-policy carry-over could cross a project boundary and
the naming test only passed because of it. Records now carry their scope and every read
filters on it, with two cases pinning the boundary.

The catalog subset check unioned every provider's catalog ids, so a default under one provider
could pass on an id that only exists under another. Scoped to the entry's own provider.

Also shortens a two-line comment in the configure-provider modal to one, per the repo rule.
… and provider

The public status proxy allow-listed state words and the `providers` list, but two
runner-controlled strings still passed through untouched: the harness map's KEY, which becomes
an object key in the browser's JSON, and the singular `provider`. A future or faulty runner
could put an account name, a path, or a credential in either.

Both are now closed sets. Keys must name a known harness (`HarnessKind`) and unknown ones are
dropped, which also caps the map — a runner cannot push more entries than there are harnesses.
`provider` gets the same treatment `providers` already had: a family the card cannot render is
dropped rather than failing the entry, since the state word is still good.

Both sets already match what the runner sends (its own SUBSCRIPTION_HARNESSES and its constant
provider values), so this narrows the contract without changing any real response.
…el spelling

Five review findings on the provider-connections surface:

- The vault shadow filter compared provider kinds raw, so MISTRALAI_API_KEY survived next to a
  stored `mistral` connection and — locals coming first in the combined list — then won the
  resolver's tiebreak, beating the key the user saved. Both sides now normalize through a
  shared `normalize_provider_kind`, moved to `sdk/utils/providers.py` because the manager
  already imports the vault middleware and could not be imported back.

- The family tiebreak fell through to the first record, which could be one the user narrowed
  away from the requested model while a later list-less record still offered it. A record with
  no saved list follows Agenta's defaults, so it now wins over a narrowed one; an explicit `[]`
  still means "offer nothing" and wins nothing.

- The probe applied its 10s budget per httpx request, and OpenRouter issues two, so the
  worst case behind the button was double. The budget now covers the whole probe; running out
  of it maps to `unknown` + `failed`, which is what it honestly proved.

- The playground picker offered a credential-set connection its bare saved slugs when it
  carried both spellings, but the SDK matches those connections on `model_keys` only, so the
  pick resolved to no provider settings.

- `together_ai/moonshotai/Kimi-K2-Instruct` is gone from Together's serverless API and from the
  agent catalog; replaced with `Kimi-K2.6`, which the agent catalog carries.

Plus a doc comment that stated the opposite of the save rule, and a catalog call moved inside
its own try so an exception degrades to an empty set instead of failing module collection.
…del-plan

docs: plan provider connection model lists
…tus-plan

docs: plan runner subscription status path
The failed-drive state rendered a full antd Alert — a bordered amber box across a
~350px pane, and the only Alert in the Drives surface. It now uses the same
centred icon + two lines as the empty state beside it, with only the glyph
carrying the warning tone.

The search/filter toolbar is hidden in both terminal states: there is nothing to
search or filter, and its bottom border made an empty pane read as broken
chrome. It stays during loading, where content is still coming.
RightPanelSplit nests — the Inspector split sits inside the Files split's chat
column — so a collapsed panel kept painting its hairline ~1px from the open
one's, reading as a double border. A collapsed panel now hides its bar, via
visibility so the open panel's slide is undisturbed, and holds it until the
close slide finishes so the line leaves with the pane.
Each id item carried its raw uuid on a second line, making every item two lines
tall and stretching the menu to uuid width. The value still goes to the
clipboard, which is the item's whole job, so the label alone is enough.

The ids are a debugging affordance, so they now ride the same playground-inspector
switch as the session inspector and the turn inspector. With it off the menu is
just "Download all".
feat(web): the AI providers experience - settings, picker, and run-path wiring
feat(agents): runner subscription status on the credential card
feat(vault): provider connections carry models, harnesses, and stable slugs
fix(frontend): tidy the files pane chrome in the agent playground
The banner used --ag-c-F5F7FA, which the warm recolor repointed at the same
value as the rail ground (#f6f5f3), so the card had no edge at all. Give it
the elevated surface plus the shell hairline, the same language the rail's
selected nav pill already uses.

The container was also a fixed 215px centred box, so it drifted further from
the nav rows the wider the resizable rail got. Make it track the rail instead.
The menu was a fixed 220px panel, so on a narrow rail it hung out over the
content area and on a wide one it sat well short of the trigger. antd pins the
popup root's min-width to the trigger, so w-full makes it exactly as wide as
the button at any rail width; collapsed keeps a readable fixed width.

box-border because preflight is off here — w-full plus a border overflowed 2px.
The trigger's own px-1.5 stacked on the wrapper padding and left the avatar 6px
inside the nav icon column; px-3 puts it on the column, and trimming the panel
padding does the same for the menu's own rows.
…button

Review follow-up. The dismiss button washed itself with bg-black/5 on hover and
focus, which is invisible against the elevated surface the card now uses in dark
mode. Swap it for colorFillTertiary, and take the title and icon colour from
colorText rather than the gray-900 scale.

Also make the banner wrapper's box-border explicit. The app only gets border-box
from antd's '.ant-layout *' reset, and that does not reach portalled trees, so a
w-full element with padding should not rely on inheriting it.
fix(observability): report failure rate as a percentage
fix(frontend): Make the sidebar banner and switcher menu fit the rail
docs: add iamhaseebn as a contributor for code
…tests

test(runner): platform-guidance tests learn the always-on file-citation sentence
Prioritised WP-3 by lane churn: the agenta-ui diff against 112.2 is dominated by
files the lane MOVED into the package, and date-range-picker (633 lines) was the
biggest interactive one.

The Observability range panel gave no indication of which range was applied.
Prod paints the active row with the primary fill; local painted nothing, and the
first row showed the browser's blue focus ring instead of the app's. 112.2
renders this from Filters/Sort.tsx, which sets popupSelectedItem when sort
matches, so the extraction dropped it.

The state was never wrong - aria-checked reads true at runtime and the JSX
already branches on it. The bug is one layer down: @agenta/ui/styles exports a cn
that is a plain concatenator while @agenta/ui/components/ui/utils exports one
built on extendTailwindMerge. rowClassName set bg-transparent unconditionally and
bg-secondary conditionally, which only works under tailwind-merge; with the
concatenator both classes land and CSS order left every row transparent.

Fixed by making the base conditional rather than swapping cn, which would start
deduping every other class in the file and needs its own verification. Also gave
the rows the kit focus treatment, since they are real buttons where prod's were
inert divs and preflight is off.

Verified live. Checked the blast radius too: 35 files import the non-merging cn,
three combine conditional and unconditional colour classes, and all three are
safe on inspection - so this was the only live instance.
…ct still uncompared

CheckboxTree powers an Edit columns control prod does not have, so it is an
addition rather than drift and the useful test is whether it works. Driven: the
popover opens with the full tree and unchecking Cost removes it from the header
set. Then had to undo my own trap - that toggle persists to
observability-table-columns, the key 4h warns about, and my restore click missed,
leaving local a column short until I cleared the keys. Anything that writes a
table preference must be cleaned up in the same breath.

Root/LLM/All is the DriveExplorer pattern again: prod renders labels, local
buttons, same control and local more accessible. Same for the unlabelled
ant-switch vs a labelled Auto-refresh button.

The filter panel is at parity - same box, same structure - and prod is only
taller because it carries a saved filter. But I did not manage to open the
TreeSelect dropdown itself: several attempts hit the wrong control or dismissed
the panel, so the 645-line file this was meant to exercise is still uncompared.
Recorded as uncompared rather than implied covered.

Cross-check worth keeping: prod's condition dropdown highlights its selected
option with the olive primary fill, which is the convention U-01 restored.
TreeSelect opened properly once the press was scoped to the panel and done as
one open-panel/open-field/read sequence; the earlier failure was my
coordinate-guessing, not the component. Both trees render the same thirteen
entries, icons, submenu chevrons and hover treatment, same y and height. Prod's
panel is 180 wide against local's 148 and nothing truncates on either, so the
32px is cosmetic.

Agent Home first read 5.20% across 221 regions, almost all of it because prod
had two pending approvals and local none, so only prod rendered the waiting
badge, the WAITING ON YOU group and the Approval chips. Instead of writing that
off as data I drove an approval on local; it then renders the same badge, group,
chip, RECENT divider and row layout. Both builds carry the same five sections.

Method note kept: the first read would have supported either 'local dropped the
waiting group' or 'it is just data'. Producing the state on the quiet build is
what settled it, and it exercises the code besides.
Opened the drawer on both from the first traces row. Same 1200px width and the
same control set in the same order, and the right panel matches section for
section - Annotations with its empty text, Trace info with Type and Status. The
4.55% is the trace payloads, which are different traces.

Left inconclusive rather than filed: local's right panel scrolls with a 6px
classic scrollbar where prod shows none. But prod has seven overflow-y auto
containers and none is currently overflowing, because its trace carries less
content - so 'prod hides its bar' and 'prod had nothing to scroll' are
indistinguishable here. Worth resolving since 112.2 moved the config pane onto
ag-scroll-no-bar; if that is the convention a classic 6px bar is inconsistent.
Needs a prod trace of comparable size.
…xed build

The variant/version dropdown is effectively pixel-identical - same box, same
Search field, same default group with v3/v2/v1, 0.21% over the panel.

D-22 was previously pinned only by a render test. TestcaseDrawer needed a
testset and neither project had one, so I created qa112-parity on both and
opened the drawer: prod's header carries NINE buttons including a bare Close on
top of the drawer's own Close drawer, local's carries eight. Prod renders two
close affordances stacked, which is exactly what closeIcon={null} exists to
prevent and exactly what the deferred (rare / unused) docstring cost. Drawer
width is 736 on both and every other control matches.

Worth the detour: a render test cannot tell you the real drawer stacks two X's.
Re-measured live after a day of churn: avatar slot 32, code lines each block,
markdown h1/h2/body 16/14/14 with gaps matching prod's, range picker's active
row painted. D-21 and D-22 were both re-verified today with two-build controls
and D-23 by unit test.

Records one harness trap that cost several turns: an unsaved testcase installs a
beforeunload guard and both builds then refuse every goto.sh, because the daemon
auto-dismisses the dialog and cancels the navigation while goto.sh just reports
MISS. In-app clicks are blocked too while the drawer is open. Escape is browse
dialog-accept before setting location.href.

The trace-drawer scrollbar question stays inconclusive: it needs a prod trace
large enough to overflow and prod's observability lists no rows under any range
I could set.
Auth was the last unexamined area and did not need a sign-out - /auth renders
while authenticated, so both pages were captured with the sessions intact.

The page reads 2.93%, which is one difference plus its knock-on shift: prod
shows a DATA RESIDENCY block with an EU/US toggle that local lacks, so every
control below sits higher. Host-gated rather than drift, confirmed in source:
shouldShowRegionSelector returns true only when classifyHost matches a
recognised cloud host, and localhost is not one. Everything else matches.

The guard itself had a blind spot and blocked this comparison outright, claiming
one tab was shot twice when shot.sh had logged two different hosts. It compares
the rail's version stamp and the auth screens have no rail, so both crops were
flat background and compared equal. A blank patch is not evidence of sameness:
same_environment now returns False on a uniform crop, verified both ways - still
catches a genuine duplicate on a stamped page, no longer fires on /auth. Also
fixed arr.ptp(), removed in NumPy 2.

Second false-PASS-adjacent bug in the harness's own safety net after the tab pin.
Guards need their own controls, in both directions.
Both had been left open for the same bad reason - the state did not exist and I
treated that as a blocker instead of producing it. Each took one action.

Tools/Skills expansion: added a tool to both. The picker is byte-identical and
Tool definition needs no external connection, so it works on a self-hosted build.
The editor reads 0.10% over its own box and the populated Tools section is
identical - icon, count, TOOL DEFINITIONS, and the card with its New and
definition chips.

Trace drawer scrollbar: generating the same 1200-word-essay trace on both shows
the same container at overflowBy 0 and barPx 0 on each, stable across three
samples. The earlier 6px bar was local caught mid-load - the async-surface trap
again, and the first time it reached the inventory instead of being caught in
the moment.

Prod's trace list was empty because nothing recent had run, not because traces
were unavailable. Recording an item as blocked on missing state, when the state
is one action away, is a slower way of not doing the work.
The get_weather draft tool is removed from both agents. It was uncommitted, so
no revision was created on either; both are back to Saved with Tools: None.

The qa112-parity testsets never existed. I had reported leaving one on each
project; both report Testsets 0, because the create flow lands in an unsaved
editor and navigating away discards it. The D-22 verification worked anyway -
the testcase drawer opens from the unsaved editor's first row and needs no
persisted data, which is worth knowing for anyone re-running it.

Both projects are otherwise back to their prior state, apart from the sessions
and traces the live runs produced.
…its behind

Two premises this inventory rested on were wrong, both now checked rather than
assumed.

Prod is not v0.112.1. The rail stamps v0.112.2 and has done for every capture
from 14:02 onward, compared pixel-wise across six prod shots. That does not
invalidate the comparisons - it makes most of them stronger, since comparing
against prod was comparing against 112.2 - and it resolves the places I wrote
'112.2 has X, prod lacks it, so prod is older': prod runs a 112.2 build, not
necessarily the tip.

The same-environment guard's tell died with that premise. With both builds on
112.2 the version stamp no longer discriminates; on three real pairs it was only
working on incidental sidebar pixels. shot.sh now writes the source URL beside
each capture and vrt.py compares hosts, verified both ways. A host does not
quietly converge; a version string does.

The functional gap: this branch is 113 commits behind 112.2 and the lane is 445.
None of the seven files I fixed were touched by those commits, so nothing
conflicts. But among them is a tool-activity naming and summary workstream, and
112.2's toolDisplay.ts is 665 lines where this branch's is 26.

That answers 5e's open lead. Prod showed 'Reading a file failed' plus the
filename where local showed a bare Read, and I could not find the phrase in any
source because I was grepping this branch and the 112.2 ref at my merge base. It
was never drift - the branch is missing feature work.

Visual parity is closed; functional parity cannot be judged from this branch
until 112.2 is merged into the lane.
Visual parity is closed; the functional half is not. This branch is 113 commits
behind release/v0.112.2 and the lane is 445, and one thing filed as a visual
difference turned out to be missing feature work - 112.2's toolDisplay.ts is 665
lines against this branch's 26, because a tool-activity naming and summary
workstream landed after our merge base.

The plan records the real conflict set from a dry-run merge-tree - 18 conflicts,
grouped into the chat slice (the actual work), three relocations where 112.2
edited files the lane moved, and five small self-contained ones. It also lists
the seven fixes that must survive with their files, the gates including the
83-tests-across-11-files check that catches a vitest config regression, and
exactly which comparisons to re-run versus leave alone.

The prompt tells the next session to merge into this branch rather than the
lane, to take 112.2's tool-summary system whole rather than hand-merging it, to
work continuously instead of checkpointing after each unit, and to ask what the
right behaviour is rather than which build to match.
The branch was 113 commits behind 112.2, and one thing filed in the diff
inventory as a visual difference turned out to be missing feature work: the
tool-activity naming/summary workstream (~10 commits) plus the observed
startup states and the #6042 first-message dispatch fixes.

Every one of the 18 conflicts had the same shape — the lane moved code into
packages, 112.2 kept editing it at the old path — so the resolution is
almost always 112.2's substance re-applied where the lane put the code.

- toolDisplay: 112.2 grew the 147-line resolver to 685; the lane had copied
  it into @agenta/chat/skin. The system moves to skin/registry.ts, with
  112.2's BY_TOOL_NAME as built-in defaults a registered entry merges onto.
  A verbatim port dropped the skin contract's label/source override, which
  the package's own pins caught; restored. 112.2's toolDisplay.test.ts (74)
  and toolRow.test.ts (29) pass unmodified against the package resolver.
- ToolActivity rebuilt from 112.2's structure with the lane's package
  imports and de-antd'd markup; its Build-mode source-chip suppression is
  now a prop on 112.2's ToolSource.
- The approvals copy changes land on @agenta/chat's ApprovalCard.
- Auth: 112.2's attempt sync READS a host tag that its PasswordlessAuth
  WROTE. The lane had already extracted that form, so the write now happens
  in @agenta/auth's requestEmailCode — otherwise the resume path is dead.
- AgentOverviewBody grows automationSessionsHref; navigation-ui's EntryList
  takes 112.2's 7-row cap; the sessions barrel exports the new handoff list
  atoms and pendingHandoffs.test.ts points at the package.
- The relocated organization api test is ported to axios under tests/unit/,
  where that package's vitest config actually collects.

Gates: lint-fix clean, oss and mobile tsc 0 errors, @agenta/ui 85/12 (above
the 83/11 floor; the extra file is 112.2's own), all package suites green.
Also fixes a pre-existing red unrelated to the merge: sessions.delete's
mock omitted sessionRowsFromPages, so that suite never imported.

The lane (PR #6065) is still 445 commits behind 112.2. That is real and it
is a separate job.
@CLAassistant

CLAassistant commented Aug 20, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Driving the merged build against prod found the ask reading two different
ways: with the chat panel maximized it said "The agent needs your approval
before writing a file", and with the config pane open it fell back to the
raw wire name plus "The agent wants to run this tool before it can keep
going".

That split is not 112.2's. The merge-base dock already used one humanized
sentence in both modes and branched on whether a registry body is present;
the extraction into @agenta/chat's ApprovalCard reintroduced a friendly-
gated raw-name row, friendly being chatPanelMaximizedAtom. It stayed
invisible while the two sentences happened to match, and 112.2 changing the
copy pulled them apart.

The card now renders one sentence either way and drops the raw-name row —
the wire name stays reachable via the title attribute and the payload
expander. So a registered body that brings no headline of its own still
gets the generic ask, the dock passes it explicitly rather than relying on
undefined, which cannot tell that case from "no body at all". `friendly`
now only chooses between "Details" and "Payload".

Verified live: local reads the same sentence prod does in both modes.

Also records the post-merge comparison in the inventory: the tool rows are
at parity (the point of the whole exercise), session open/start, auth and
elicitation likewise, and the seven fixes on this branch all still hold.
@ardaerzin
ardaerzin merged commit 7d18982 into lane/mobile-extracted-packages Aug 20, 2026
36 of 42 checks passed

This branch was successfully deployed

1 active deployment
Preview — 7d189820 Deployed Aug 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.