Skip to content

[feat] Call tools "integrations" in the mobile app - #6739

Open
ashrafchowdury wants to merge 171 commits into
release/v0.115.4from
feat/mobile-integrations-copy
Open

[feat] Call tools "integrations" in the mobile app#6739
ashrafchowdury wants to merge 171 commits into
release/v0.115.4from
feat/mobile-integrations-copy

Conversation

@ashrafchowdury

@ashrafchowdury ashrafchowdury commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Context

The mobile app used one word for two things. "Tools" named the Settings page where you connect Gmail or Slack, and it also named the tool calls a transcript shows while an agent runs. In this app the connected-app concept is now called Integrations, so the two stop colliding.

oss/ee keep saying "Tools", so this is a copy layer over the shared components rather than a rename of the concept everywhere.

Changes

web/mobile/src/lib/integrationsCopy.ts holds every string this app renames, in one place. Three surfaces read it:

  • The Settings tab: its label in the page header, the settings rail and the nested tab rail, plus the page description and the docs link. "Tools" / "Configure integrations your agents can use." / "About tools" become "Integrations" / "Connect the integrations your agents can use." / "About integrations".
  • The connections table on that page: the Integration column, "Search integrations", "Connect integration", "Run action", and both empty states ("No integrations connected yet", "Connect an integration to let your agents call it.").
  • The agent overview's config card: the row now reads "Integrations", and its empty action reads "Add integrations".

Two shared components carry the strings the mobile app overrides, so both grew an optional copy prop that defaults to today's wording:

// oss/ee, unchanged
<GatewayToolsSection confirm={AlertPopup} />        // "Search tools", "Connect tool"

// mobile
<GatewayToolsSection confirm={confirm} copy={INTEGRATIONS_SECTION_COPY} />

AgentConfigSummaryCard gained the same prop, forwarded from AgentOverviewBody as configCopy. Nothing about the desktop renders differently.

Three places still say "tool" on purpose, because they mean tool calls and not connected apps: the transcript's tool rows and the rewind dialog, the drill-in config reference part that dumps the raw tools config key, and the composer's slash-command group (which is behind SHOW_TOOLS = false and does not render).

Tests

  • pnpm types:check in web/mobile and pnpm lint-fix at the web root both pass.
  • No behaviour changed, only strings, so there are no new unit tests. The shared components keep their old defaults, which is what oss/ee assert against by rendering.

What to QA

  • Open Settings on /m. The rail and the page header say Integrations, and the description reads "Connect the integrations your agents can use."
  • On that page with no connections, the empty state says "No integrations connected yet" and the button says Connect integration. Search placeholder reads "Search integrations", and a search that matches nothing says "No integrations match ...".
  • Connect one, then open the row menu. The first action reads "Run action". The table's second column header reads Integration.
  • Open an agent overview on /m. The config card's row reads Integrations, with "Add integrations" when none are enabled.
  • Regression: open the same Settings page and the same agent overview on the desktop app. Both still say Tools, "Search tools", "Connect tool" and "Add tools".

Adds the model, hooks, route shells and nav entry the Automations screens
build on. A cron schedule and an event subscription are separate rows on
separate endpoints, but a reader sees one list of things that run an agent,
so automationModel maps both onto a single Automation type and every screen
reads that instead of branching per entity.

Delivery outcomes read status.message rather than the shared
deliveryStatusColor helper, which reads a status.type the backend never
sets and so returns neutral for every row.
Renders both trigger kinds in one table — name, status, when it runs, and
the agent it runs — over the shared DataTable the settings trigger sections
already use, with search across name, description and agent.

Gives StatusTag a failed tone so a broken automation reads red. attention
maps to the accent, which asks for a look rather than reporting a fault, and
the two must not collapse once run outcomes land.

The empty state is plain markup rather than EmptyState: its image slot is
sized for a 184x152 illustration, and opting a 48px tile out of that took
more overrides than the markup it replaced.
Config lives on the automation's own page rather than in a drawer, so
creating and editing one later share a single surface. The name renames in
place, ActiveToggle owns start/stop, and the three fields reuse the shared
controls: ScheduleBuilderField for the cadence and MessageComposer for the
instruction.

Both trigger endpoints take a full PUT, so buildAutomationEdit copies every
stored field off the source row and overlays only what changed - a rename
must not drop the cron, the message or the agent binding.

Neither field repeats the helper line its shared control already prints,
and an unbound automation reads Pick an agent rather than naming one that
isn't there.
…ents

Both screens were built on default control sizing, which left every heading,
row and gap a few pixels off. Sizes, weights, letter-spacing, padding, radii
and the table's column ratios now come from the design.

The list drops DataTable for a plain CSS grid. The shared table lays out a
real table with a fixed-pixel colgroup, so it cannot express the design's
minmax()/fr columns without being fought at every cell; one GRID constant
now feeds the header, the rows and the skeleton so they cannot drift.

Status in a column of its own is a dot and a coloured word, not a pill -
the pill exists to separate a status from a sentence, and there is no
sentence here.
Both pickers open as a popover on a wide screen and a bottom sheet on a
phone, sharing one overlay helper - an anchored popover is the wrong
affordance at phone width, and the design was drawn wide.

The event side composes the package's own SourceBrowsePage and SchemaForm
rather than reimplementing a catalog browser and a filter form; the two
symbols it needed are added to the export block already kept for hosts
outside the drawer. Agent rebinding goes through buildTriggerReferences so
a pick writes a resolvable reference rather than an artifact-only one.

A binding whose id is missing from the agent roster now reads Unknown
agent. It used to read as unbound, which both misreported the automation
and offered a rebind that would have replaced something real.
New automation and the template cards routed to /automations/new, which
had no route - the dynamic segment caught it, read "new" as an id, and
rendered "This automation no longer exists". A static route wins over the
dynamic one and now renders the draft.

Create and edit are the same surface, as the design draws them: the draft
is the detail screen minus what a draft has not earned - no meta row, no
failure banner, no run history - plus a footer whose Create stays disabled
until an agent is bound.

The create payload sits beside buildAutomationEdit so both PUT and POST
shapes are read in one place, and the blank draft seeds its weekday cron in
the comma form: cronToBuilder parses a plain int list, so 1-5 comes back
unrepresentable and the field would open on a raw expression.
…story card

The meta row states whether an automation is on, so it takes a switch,
which shows that at rest. ActiveToggle is a play/pause button - right in a
row of actions, wrong in a status line - so the pending guard and the
messages it owned move here with it.

The run history card is drawn two lines tall, a title over a run count. It
collapses to one row until that count is wired, because a box sized for two
lines holding one reads as padding around nothing.
The design opens Runs when on a choice between a schedule and an app event.
Without it a schedule can never become an event trigger, which is the point
of that control.

ScheduleBuilderField is split into useScheduleBuilder + ScheduleBuilderPanel
so its panel can sit inside a different popover, with the field itself
rebuilt on the same pair - one implementation, and the existing drawer keeps
its behaviour. Kind stays switchable only on a draft: the two kinds are
separate rows on separate endpoints, so converting a saved one is a create,
a delete and a redirect, not a PUT.

The agent field is a real Select rather than a hand-rolled list, so it
closes on pick, matches the schedule control's height, and drops a heading
the design never had. Both overlays open at their trigger's width.

Unknown agent now waits for the roster to answer. Reading it mid-load
flashed a resolvable binding as broken.
Every button this feature added had picked up its own size or weight - sm
here, a font override there - so no two agreed. They all sit at the default
size now, with the normal weight the design draws, and carry no per-call
geometry of their own.
24px was loud for a name that sits under a back link and above a short
field stack. The rename input moves with it so the swap does not resize.
The rename field was a shadcn Input overridden to imitate the heading it
replaced, which only made the swap a resize. It is the stock input now.

The heading's tap target drops the shared FOCUS_RING for the soft ring the
inputs use: a hard 2px outline in the near-black ring colour reads as a
border around the title rather than as focus.
The panel was rendering the subscription drawer's integration catalog - a
marketplace of ninety apps with descriptions, categories and action counts.
That answers how to connect an app, not which event should run this
automation, so it is replaced by what the design asks for: the apps this
workspace has already connected, that app's events, and a search across
them. Connect another app... is now the only route to the catalog, and it
opens the real drawer.

Apps are named and marked from the integrations catalog rather than the
connection: a connection is named for the account it authorises, so the rail
was reading gmail-main where it meant Gmail. Every event in a list wears its
app's mark, and a mark that has not loaded is a skeleton, not a letter tile
that looks like a broken icon.
It read All Googlecalendar events - the integration key made readable,
because the catalog's display name was only wired into the rail. Same
lookup, same answer in both places: All Google Calendar events.
…mark

A leaf field's label row now reads at 13px/500 with a plain info icon and
the word Required, rather than a bordered question mark and an asterisk
borrowed from the Field chrome. FormItem stops asking for that asterisk so
the two markers cannot both appear.

The three required-rule messages were template boilerplate - "<label> is
required" - and now read "Needed before this can run". The array rule keeps
its own wording: an empty list and a missing value are different faults.

Styling stays on the node SchemaForm owns. The shared Input and Field
primitives carry the product-wide antd control geometry and are untouched.
Picking an event replaced the whole picker - rail, search and Connect
another app all vanished - so changing your mind about the app meant going
back first. The rail now stays and only the right pane swaps, which is what
the design draws.

Required reads before the help icon: the word qualifies the field, the icon
explains it. The optional collapse takes its wording from the host, since
SchemaForm also renders tool arguments and connection forms where filters
would be the wrong noun; the event picker asks for the design's phrasing and
everything else keeps Optional (N).
Radix does not bound a popover's height, so an app with a long event list
pushed the panel past the bottom of the window and took Connect another app
with it. The content now caps at the room left below the trigger and the
panes flex inside that, instead of each capping itself at a guessed 420px -
a guess that was both too tall in a short window and too short in a tall
one.
Capping at the available height put the panel flush against the window
edge, which reads as clipped even though nothing is cut off. It now stops
16px short.
A pencil parked beside the name permanently reads as part of the title. It
fades in on hover, and on keyboard focus too - that button no longer draws a
focus ring, so the icon is the only thing telling a keyboard user the name
is editable.
Every button the feature adds - New automation, Cancel, Create, the trigger
kind chips, Done, load more, retry - is small now, which sits better beside
the 30px controls the fields already use than the default did. Normal weight
throughout, as before.
A column of identical robots identifies nothing. AgentGlyph already resolves
an agent's chosen icon and colour from its id, so the list and the agent
field use it, and an agent that has picked no icon still falls back to the
robot rather than to a gap.
The bordered wrapper is the control; the input inside only carries the text.
It kept shadcn's dark:bg-input/30 though, so in dark mode a lighter
rectangle sat inside the border.
Run history is a day-grouped list beside the run's own conversation. The
conversation is the playground's LiveConversation, mounted embedded rather
than a transcript of our own - a finished run stays continuable, with the
same composer, attachments and slash commands the agent has everywhere else.

Rows read a time of day because the day heading already states the day. A
run that never produced a session - a test capture, or a failure before the
invoke - says so instead of mounting an empty conversation.

Duration is derived from created_at to updated_at; deliveries carry no
duration, and equal timestamps drop the clause rather than claim 0s.

The pane column carries min-w-0: without it a long transcript set the flex
row's width and pushed the close button off the screen.
The runs view sat inside the page's 1180px gutter, which left the column
rule floating with margin on both sides. The gutter moves onto the two
columns so the rule meets the top and bottom of the frame, and the
conversation gets the width back.

The pane's close button goes with it: the list is beside it in this layout
and the back link is above, so a third exit only added chrome.
Did it work is the question that brings you here, so the history opens that
run's conversation rather than a list waiting to be clicked. A run the user
picks wins; only an unresolved selection falls through to the newest, and
the narrow layout's back keeps meaning no run - without a dismissed flag the
fallback would re-select on the next render.

The list column also aligns with the back link above it now: it was
centring itself while the header sat at the page's left edge.
The back link, the heading, the day label and the status dots each sat on
their own line. They share one now: the row keeps a 10px inset so its
selected fill stays rounded and clear of the column, and everything else
moves in to meet it.

Rows are shorter, 2px apart, and the per-group count goes - the runs under
a day heading are countable on sight, and the number only competed with the
label.
…act on it

The three config fields - agent, runs-when, instruction - now edit a draft
and commit together on Save, with Discard beside it and a confirm before
navigating away dirty. The name and the on/off switch keep saving on the
spot: those read as actions, not as edits waiting to be applied. A
background refetch moves the baseline but never overwrites a typed field.

Test run opens a fresh session for the bound agent with the instruction
already in the composer, so it can be read and changed before it is sent.
It is not the shared RunInPlaygroundButton - that sets an atom only the oss
playground reads, so on mobile it would have looked like a broken button.
It is disabled while the draft is dirty, since testing an unsaved config
would show a run of the old one.

The kebab duplicates (inactive - a copy that inherited is_active would
start firing on its own), copies the id, and deletes behind a confirm.

buildAutomationEdit carries the whole config now rather than each field
sending its own body, which is also why the agent binding moved out of the
field and into the save.
They sat in the pinned page header, beside the back link, which reads as
chrome for the page rather than actions on this automation. On the title's
line they belong to the thing they act on, and the header goes back to
carrying only the way out.
It carries a title, a count and a chevron, and was padded like a panel. The
padding, the icon tile and the corner all come in a step so it reads as a
row at the end of the form.
Three things the split layout got wrong on a phone:

Run history auto-selected the newest run, which on a narrow frame hides the
list - you landed inside a conversation with no sign there was a history to
browse. It only auto-selects where the list survives the selection.

The page title truncated to Autom... because New automation spent the width
on a label. Below sm the button is its plus icon and the title fits.

The meta row's divider wrapped alone, dangling after On/Off or leading the
agent line. It only separates when both halves share a line.
The mic is the same VoiceInputButton and useVoiceComposer the chat
composer already runs, with the recording takeover and the permission
notice that go with it — not a second voice affordance.

One difference on Home: a take is always an ATTACHMENT, never a message
of its own. There is no conversation here to send it into, so both the
attach and the send-outright paths park it in the tray and the composer's
text is still what gets sent.

The attach button becomes a plus, which reads as "add something" rather
than naming one kind of thing. It is the app's single attach control, so
this changes it in chat and the playground too.
Recording a voice message has always had a waveform; dictation had no
signal at all — words simply appeared. The obstacle was the source: the
Web Speech API hands back transcripts and no audio, so there was nothing
in that pipeline to draw.

`useDictationAnalyser` opens a stream of its own purely to be measured —
an analyser, no MediaRecorder, nothing captured or kept — and the
existing RecordingWaveform draws it. The cost is a second microphone
consumer while dictating, and with it a second OS mic indicator; the
stream is held only for the length of a dictation, because one kept open
between sessions would look like the app is always listening.

Failure is silent: a refused stream leaves the wave absent and dictation
untouched. The recogniser owns the permission conversation and has its
own error channel, so a second message about a decoration is noise.

The wave lands in the row the shortcut hints already vacate while
dictating, so nothing shifts. RichChatInput takes it as a ReactNode
rather than an analyser — the audio belongs to whoever owns the mic, and
@agenta/ui sits below @agenta/chat and cannot reach the waveform anyway.

`getAudioContextCtor` moves out of useAudioRecorder to be shared: two
features build this graph now, and a second copy of the webkit prefix
dance is a second place for Safari to be forgotten.
The hints were faded out for dictation but still mounted, and a mounted
row of hints still holds its width — so the wave drew in whatever was
left over instead of across the composer. They are unmounted while it
runs; the reason to keep them mounted (never reflowing the row) does not
apply when something else is taking their place.

The attach button goes too. The row belongs to the wave while a voice is
coming in, and attaching a file mid-utterance is not a thing anyone is
doing.
Bar thickness fell out of the slice count and a fixed 2px gap, so it
grew with the strip — wide bars on a wide composer. It is stated now,
and the leftover width becomes the gaps, so the bars read as fine lines
at any size while the strip still spans the full row.

Shared with the recording takeover's waveform, which gets the same.
…trol

The button throbbed for the whole of a dictation. With the waveform now
beside it saying a voice is coming in, a second thing pulsing made the
stop control read as a status light rather than something to press.

`highlighted` still tints the icon; only the animation goes.
The editor's ceiling was hardcoded at max-h-40, so a long draft on Home
grew the composer until the list below it went past the fold. Home caps
at 112px instead.

A prop rather than a smaller default, mirroring the minHeightClassName
already there: a chat dock has nothing under it to push and keeps the
taller ceiling, while a surface with a page behind the composer wants a
lower one.
The dock names the bound agent, which is exactly what the hold does not
know yet — and on a first run there is no agent to name, so the bar
promised a row that never arrives. The composer block stands alone.
… row

The marks each carried a ring in `colorBgContainer` to fake a gap, but
they sit on a transparent row over the page — and over the hover fill on
top of that — so the ring was a bright halo in both themes, taking a
bite out of its neighbour. It is gone; the overlap alone separates them.

That overlap was a fixed 4px tuned for a 16px mark, which is a quarter
of the 13px the row now draws. It scales with the mark instead.

The row's marks also drop to 13px — the template CARD keeps 16, which is
what that size was chosen for, so it is a prop rather than a new default.

And a selected agent or template gets the check the agent picker already
uses: a tinted row on its own reads as hover on whatever you are
pointing at. The tabs close 4px toward the composer while we are here.
`selectedAgentId` fell back to `agents[0]` whenever nothing was picked,
so selecting a template put a check on the first agent — a selection
nobody made, contradicting the dock right above it. Creating binds no
agent, blank or from a template, so no row is marked.
The list arrived in whatever order the query returned, so an agent made
months ago sat above one created a minute earlier. Sorted on created_at.

This also moves what the composer binds by default, since that is the
head of the same list — the agent you just made is now the one it is
aimed at.
Morning ran from midnight, so the small hours greeted someone at the end
of a long day as if they were starting one. 05:00-11:59 morning,
12:00-16:59 afternoon, 17:00-04:59 evening.
A username is a handle — `ashraf_chowdury99` greeted somebody by their
login. Separators become spaces and digits go, so it reads as "ashraf
chowdury".

An email is dropped rather than cleaned: the local part of one is not a
name, and greeting somebody by their address reads as a mail merge. A
handle that cleans down to nothing ("_99_") drops the eyebrow too, the
same as no profile.
HomeTaskComposer has a second host — the agent overview's AgentComposer —
and Home's additions had been welded into it, so that page silently grew
a microphone, a recording takeover and Home's 112px editor ceiling. Voice
is a `voice` prop now, off by default, and the ceiling is the prop it was
always meant to be. The agent page is back to a plain composer.

The composer's binding was three pieces of state — `creating`, `agentId`,
`template` — encoding one fact. A bound template with `creating` false was
representable and meaningless, and every handler reset two or three of
them. One `Binding` union replaces them, so the invalid states cannot be
written.

"Which agent is bound" was resolved twice, in HomeFocus and again in the
composer, with the same fallback written two ways — which is exactly how
the list came to check an agent nobody had chosen. HomeFocus resolves it
once and hands the answer to both.

Also: the dock's two branches were the same row with a different tile, so
they are one `DockLabel`; `composerExtraPrefix` and `onStartError` had no
caller and are gone (the latter was swallowing rejections silently); the
templates catalogue arrives as a prop like the agents beside it rather
than being imported into the component; and the dock comment, four prop
docs and two `[[HomeComposer]]` links all described code that no longer
exists.

The mic and plus tighten to one cluster while we are here.
Back to a paperclip from the plus, but not the diagonal default —
`PaperclipHorizontal` turned a right angle stands exactly vertical, where
rotating the diagonal one only approximates it.
Rotated +90 the clip's hook sat at the foot; -90 puts it at the head,
which is the way round a paperclip is drawn.
PaperclipHorizontal is drawn as a flat wide clip; standing it up made a
stretched pill rather than a paperclip. The diagonal one is the shape
that icon is designed to be read at.
A project with no agents got a separate first-run hero: its own page,
its own composer, its own copy, teaching a layout you then never saw
again. Home does that job with one fewer page to learn. It opens on the
templates tab with the composer already describing an agent, and "Your
agents" is not offered until there is one.

So FirstRunScreen, FirstRunComposer, their copy and their states are
deleted, and `homeSurface` moves to the home feature it now serves: it
only decides whether we know enough to draw Home yet. Its two real rules
survive — an error is never read as emptiness, and an empty list that is
still resolving holds rather than flashing the wrong tab.

The tab and the composer's mode are read once at mount, which is when the
answer is known: Home does not render until the list has resolved.
…oster

Binding an agent is half the sentence, so the caret goes where the other
half is typed. Templates and "+ New agent" already did this; agent rows
did not, which made them the one row in the list that left you nowhere.

Reading the roster once at mount was also wrong. The list can arrive
empty and fill a moment later, and the snapshot then left the page stuck
on templates with a full roster sitting behind the other tab — which is
exactly what it did on a cold load. The tab and the binding DERIVE from
the roster now, with an explicit choice outliving it: null means follow
the data.
Two of these were mine to begin with.

Hiding "Your agents" for an empty roster also hid the tab the loading,
empty and error slots render in, so a FAILED fetch showed the templates
list with no message and no retry — the one case `resolveHomeSurface`
keeps on Home precisely so it can be retried. The tab now exists whenever
the host has something to say there.

And the review pass that deleted `onStartError` as dead API took the
try/catch with it. `RichChatInput` neither awaits nor catches `onSubmit`,
so a rejecting host became an unhandled rejection.

The rest CodeRabbit found on its own:

- A template's instruction was dropped when it was picked before the lazy
  `RichChatInput` chunk resolved: one frame was enough on a warm load and
  not on a cold one, and create mode then submitted empty text with only
  the template's name. The seed retries until the handle exists, bounded.
- A cancelled navigation RESOLVES false rather than throwing, so catching
  alone cleared the attachments while the task sat unplayed in the stash.
  The same hazard `useNewAgentAction` already documents.
- 64 bars at 2.5px with a 1px gap need 223px, and the floors pushed the
  last bar off the right edge below that. Both minimums go.
- An analyser that cannot be built leaves the microphone open for the
  length of the dictation with nothing measuring it.
- `outline-none` with no replacement left the rows, tabs, New agent and
  the retry button with no visible focus for a keyboard.
…esign

Brings the rail to option 1b of the Claude Design "Sidebar Reorg" project, and
moves three things that were sitting in the wrong place.

Density. Rows are 26px at 13px where they were 28px at 14px, the brand row is
32px with an 80x18 wordmark instead of 48px, and session titles drop to 12px
with 10px status glyphs, so the session pane reads as a list under the nav
rather than as more nav.

Session rows line up with the nav. The 22px indent and the vertical guide line
are gone for groups that carry no collapse control, which is what pushed every
status dot out of the column the Home, Agents and Sessions icons share. Keyed
on alwaysOpen rather than on the Sessions key: a group you cannot collapse is a
section of the rail, and its rows are rail rows. The Evaluation group, which you
do open, keeps its indent and its line.

Agents is a plain row. Its dynamic child list is gone; an agent list in the rail
was a second, shorter answer to a question the Agents page already answers in
full, and the caret it needed made the row look like the Sessions group, which
is the rail's only real list. That removed the last caller of
sidebarAgentRanksAtomFamily, so it goes too, along with withManualAgentRanks and
the 'agents' drag zone behind it. The agent headings under Sessions keep their
own zone and their own counts.

Releases leave the banner queue. They read as a "What's new?" list inside the
help menu now. A release is news, not a task: it does not need clearing, and
eleven of them queued two at a time made it feel like one. The queue keeps
star-repo and the trial/upgrade banners EE injects, which are tasks.

Help becomes an icon button on the switcher row, drawing the same flyout the
collapsed rail draws. Book a call comes out and Keyboard shortcuts takes its
place, which gives the shortcuts sheet a visible way in; it had been reachable
only through a bare '?' hotkey that teaches nobody.

Session search. A magnifier beside the filter opens a palette backed by a
debounced server search. Filtering the rail's own fifty-row window would search
the top of the list and call everything else missing, which is the one case a
search exists for.

The rail's filter uses the shared menu. SessionFilterMenu went from 220 lines to
a section map over @agenta/ui/filter-menu, the same panel the automations list
opens, and the superseded presentational/filter/FilterMenu is deleted; this was
its last caller.

Dark selection stops shouting. The selected row painted an olive wash with
accent-yellow text and no ring, the loudest thing on a rail whose job is to sit
still, and it disagreed with light. One rule in both themes now: a raised
surface, a hairline, and the text left alone. Dark is #242424 on the #101010
rail with a #292929 ring, changed at the source in palette.ts with the tokens
regenerated.

Verified on the running mobile app throughout, measuring geometry in the DOM:
session glyphs and nav icons both at left 20px, brand row exactly 32px,
collapsed icons 28px with 4px between them, and the three bottom controls
stepping evenly. tsc clean on oss and mobile, eslint clean, 82 navigation unit
tests pass (down from 86: the four withManualAgentRanks cases described a helper
that no longer exists).
The gateway-tool surface is a list of connected apps (Gmail, Slack), and "tool"
also names the tool CALLS a transcript shows — two things under one word. This
app now says Integrations for the connected-app concept: the settings tab, its
nav entries, the connections table, and the agent overview's config row.

oss/ee keep their wording, so the rename is a mobile copy layer
(web/mobile/src/lib/integrationsCopy.ts) over shared components that now take an
optional copy prop and default to the old strings.
Repo rule (web/AGENTS.md): at most one short line per comment.
@ashrafchowdury
ashrafchowdury force-pushed the feat/mobile-integrations-copy branch from ce21c96 to facb884 Compare September 10, 2026 12:59
@ashrafchowdury
ashrafchowdury removed this pull request from stack #6736 September 10, 2026 13:00
@ashrafchowdury
ashrafchowdury changed the base branch from feat/sidebar-reorg-v2 to release/v0.115.4 September 10, 2026 18:05
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