Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
1fd045e
feat(mobile): the observability surface
ardaerzin Aug 12, 2026
e4cc7af
fix(frontend): stamp the body hook on the virtual table
ardaerzin Aug 12, 2026
891569e
fix(frontend): observability reads the pre-extended dayjs
ardaerzin Aug 12, 2026
1bdf77a
fix(mobile): observability is a desktop surface too
ardaerzin Aug 12, 2026
fdb2c39
feat(frontend): an antd-free virtual table, and /m renders the real one
ardaerzin Aug 12, 2026
fe35a92
test(frontend): Storybook stories for the antd-free VirtualTable
ardaerzin Aug 12, 2026
cfb8c27
refactor(frontend): TanStack Table and Virtual drive the table, not h…
ardaerzin Aug 12, 2026
cedacfd
fix(frontend): unbreak Storybook, and the windowing bug it immediatel…
ardaerzin Aug 12, 2026
03afa1f
feat(frontend): give VirtualTable the infinite loading it was missing
ardaerzin Aug 12, 2026
40871be
feat(frontend): translate antd rowSelection into what VirtualTable sp…
ardaerzin Aug 12, 2026
549b755
chore(frontend): regenerate mobile theme tokens
ardaerzin Aug 12, 2026
ec9fa8d
refactor(frontend): move VirtualTable to TanStack Table v9
ardaerzin Aug 12, 2026
4cf8434
refactor(frontend): delete five unreachable table hooks
ardaerzin Aug 12, 2026
9361144
feat(frontend): wire column resizing to TanStack columnResizingFeature
ardaerzin Aug 12, 2026
b08bc07
feat(frontend): port the column space-distribution onto TanStack sizing
ardaerzin Aug 12, 2026
354f70e
feat(frontend): expandable rows on TanStack rowExpandingFeature
ardaerzin Aug 12, 2026
6531e91
feat(frontend): render InfiniteVirtualTable through VirtualTable behi…
ardaerzin Aug 12, 2026
59cb1c8
fix(frontend): fill the container on the tanstack engine, matching antd
ardaerzin Aug 12, 2026
f06afae
fix(frontend): restore row clicks on the tanstack engine, and warn on…
ardaerzin Aug 12, 2026
19a4b27
feat(frontend): close the tanstack engine's remaining gaps
ardaerzin Aug 12, 2026
b558ee3
fix(frontend): let column drags survive auto-layout
ardaerzin Aug 12, 2026
8d26abf
fix(frontend): key the header content, ending the React key warning
ardaerzin Aug 12, 2026
718041b
test(frontend): verify typeChips renders identically on both engines
ardaerzin Aug 12, 2026
1dd7f2a
feat(frontend): support size and bordered, unblocking the observabili…
ardaerzin Aug 13, 2026
f45f595
feat(frontend): render the observability tables through VirtualTable
ardaerzin Aug 13, 2026
5a796c7
fix(frontend): honour loading, style, className and emptyText on the …
ardaerzin Aug 13, 2026
da42dd6
fix(frontend): table chrome, grabbable resize handles, empty state in…
ardaerzin Aug 13, 2026
22b557a
refactor(frontend): share one traces table between /m and web/oss
ardaerzin Aug 13, 2026
7628e20
refactor(frontend): make @agenta/ui runtime antd-free, deleting the a…
ardaerzin Aug 13, 2026
cc4911c
refactor(frontend): /m and web/oss render the same traces table
ardaerzin Aug 13, 2026
b552379
refactor(frontend): /m and web/oss render the same sessions table
ardaerzin Aug 13, 2026
6491c10
fix(frontend): let the shell size the /m tables, restoring their chrome
ardaerzin Aug 13, 2026
0ed4ba4
fix(frontend): move the table's own look into the shared component
ardaerzin Aug 13, 2026
eafdf99
fix(frontend): /m renders the desktop's page chrome, not its own
ardaerzin Aug 13, 2026
9d0ee01
feat(frontend): share the CSV export, and give /m selection
ardaerzin Aug 13, 2026
bfb8b8a
feat(frontend): share Delete and Add, finishing the observability mig…
ardaerzin Aug 13, 2026
f8212de
fix(frontend): address the CodeRabbit findings that survived verifica…
ardaerzin Aug 13, 2026
505d723
fix(frontend): work through the rest of the review, verified item by …
ardaerzin Aug 13, 2026
af1aa2b
fix(frontend): the last three review items
ardaerzin Aug 13, 2026
d999216
refactor(frontend): groupColumns returns the type it actually produces
ardaerzin Aug 13, 2026
7dfe8f9
fix(frontend): make the CSV headers match the rows they label
ardaerzin Aug 13, 2026
84ab81a
docs(frontend): plan for the /evaluations render loop
ardaerzin Aug 13, 2026
7c8a38d
fix(frontend): pin one immer instance so table state can draft Maps
ardaerzin Aug 14, 2026
086ae6c
refactor(frontend): keep tooltip triggers off other triggers' elements
ardaerzin Aug 14, 2026
e08c716
feat(frontend): export evaluator metrics in the traces CSV
ardaerzin Aug 14, 2026
df836c4
refactor(frontend): derive evaluator columns once, for both surfaces
ardaerzin Aug 14, 2026
968d60c
refactor(frontend): move the trace-drawer state layer into @agenta/ob…
ardaerzin Aug 14, 2026
1ca725e
refactor(frontend): move the trace drawer UI into @agenta/observabili…
ardaerzin Aug 14, 2026
6a82203
feat(frontend): let /m open a trace
ardaerzin Aug 14, 2026
fdc3f00
fix(frontend): query the table's own DOM hooks, not antd's
ardaerzin Aug 14, 2026
b5981f5
fix(frontend): review findings from the observability stack
ardaerzin Aug 14, 2026
e370959
fix(frontend): make the day grid keyboard-operable and the span chips…
ardaerzin Aug 15, 2026
7dc23a3
docs(frontend): make the observability dispatch documents agree with …
ardaerzin Aug 15, 2026
d75a0a2
fix(frontend): the tool drawer invalidates every key a connection tou…
ardaerzin Aug 15, 2026
b47f736
fix(frontend): mount the mobile session workspace's two halves exactl…
ardaerzin Aug 15, 2026
a20f698
fix(frontend): keep the UTC designator on every analytics range
ardaerzin Aug 15, 2026
f5bd487
fix(frontend): follow OS appearance changes, and stop feeding click e…
ardaerzin Aug 15, 2026
985a9d5
fix(frontend): ask before deleting a schedule or a subscription
ardaerzin Aug 15, 2026
74f8917
fix(frontend): give the agent config header the OSS commit host adapter
ardaerzin Aug 15, 2026
5d672dc
fix(frontend): stop waiting badges sticking on after the last session…
ardaerzin Aug 15, 2026
f371b8c
fix(frontend): do not clear a composer's attachments when the navigat…
ardaerzin Aug 15, 2026
af6538d
refactor(frontend): move profile, webhooks and API keys onto the Fern…
ardaerzin Aug 15, 2026
f6c64d7
fix(frontend): stop the mobile settings surfaces losing what you typed
ardaerzin Aug 15, 2026
ce36e0f
docs(frontend): triage the CodeRabbit findings on the sessions-UX stack
ardaerzin Aug 15, 2026
99c9427
docs(frontend): record the live verification of the CodeRabbit fixes
ardaerzin Aug 15, 2026
f7d6647
docs(frontend): record the browser verification, and correct a dark-m…
ardaerzin Aug 15, 2026
242ff20
fix(frontend): render one template-detail shell, not both
ardaerzin Aug 15, 2026
8185e24
chore(frontend): make the repo pass its own format and lint checks
ardaerzin Aug 15, 2026
98bf763
fix(frontend): delete the app-layer re-export shims and repair the or…
ardaerzin Aug 15, 2026
bb092d4
fix(ci): copy every workspace package into the mobile image
ardaerzin Aug 15, 2026
fdddd22
docs(frontend): make the sessions-UX runbooks safe to actually follow
ardaerzin Aug 15, 2026
925bf60
fix(frontend): read the SSO flags the way the backend reads them
ardaerzin Aug 16, 2026
973a0d8
fix(frontend): reset the project dialogs on every close, drop a needl…
ardaerzin Aug 16, 2026
72da687
refactor(frontend): move the API-key list onto a scoped query atom
ardaerzin Aug 16, 2026
8c31234
fix(frontend): theme the last fixed colours, gate the disabled settin…
ardaerzin Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
93 changes: 93 additions & 0 deletions docs/design/oauth-callback-settle/handoff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Handoff: make the OAuth callback settle on failure, not just success

**Read [`plan.md`](./plan.md) in this folder first, in full.** It has the root cause, the call-site
table, the exact line numbers, and two wrong theories that were already chased down and killed. Do
not re-derive the diagnosis.

This file is the how. That file is the why.

## One-paragraph context

`_oauth_card()` renders the popup page the provider redirects to after an OAuth grant. Its
`agenta_url` parameter is what enables the page to post `tools:oauth:complete` back to the opener,
show a "Return to Agenta" button, and auto-close. Five of six call sites — every failure path —
never pass it. So a failed authorization leaves a dead popup and an opener that never settles, and
the in-chat flow eventually reports the wrong reason (`"cancelled"`) to the agent.

## Before you touch anything

1. **Work on a branch that contains PR #5915** (the QueryClient host fix), or you cannot trust any
result on `/m`. Package-layer invalidations there hit an orphan client without it, so a perfectly
correct callback will still look broken. Check with:
```bash
grep -q getHostQueryClient web/packages/agenta-entity-ui/src/gatewayTool/hooks/useReconnectToolConnection.ts \
&& echo "fix present" || echo "MISSING — rebase onto #5915 first"
```
2. **Reproduce the bug before fixing it.** Bring up the EE dev stack (`hosting/AGENTS.md`), open
Settings → Tools, start a connect flow, and click **Deny** on the provider consent screen. You
should see an error card with no buttons that never closes, and a row that never settles. If you
cannot reproduce this, stop and find out why before writing code — the premise is wrong.

## The work

Three work packages, in `plan.md` under "Work plan". WP1 is the fix; WP2 and WP3 make the failure
legible instead of merely settled. WP1 is independently shippable and worth landing on its own if
WP2/WP3 grow.

Two decisions already taken, so you do not have to relitigate them:

- **Default `agenta_url` inside `_oauth_card()`** rather than threading it through six call sites.
Five of six authors already forgot it; a parameter that exists only to be passed everywhere is a
parameter that will be forgotten again. Argue for the explicit version only if the default is
genuinely wrong for some caller.
- **A payload with no `success` key must keep meaning "completed, re-read the server."** Older
frontends will run against a newer API. Do not make the key required.

## Verifying it

A green test suite proves very little here — the failure path has no coverage today, and the bug is
in a hand-written HTML template driven by a Python default. Drive it.

- **Failure path**: deny consent. The card should now close itself, and the opener should settle
with the row updated and no page reload.
- **Success path is the regression risk.** It is the only path exercised today, so re-run a real
successful connect and a real reconnect and confirm neither changed.
- **In-chat flow** (`useConnectFlow`): after WP3, a denied grant must report a failure with the
provider's message, and only an actually-closed window should report `"cancelled"`. Those two
cases must stay distinguishable.

Prove "settled without a reload" the way the query-client sweep did — stamp the page before the
mutation and assert the stamp survived:

```js
window.__mark = "x" // before
window.__mark // after the UI settles; if it survived, no reload happened
```

A screenshot cannot show the absence of a reload. The stamp can.

## Gotchas that will cost you an hour each

- **Turbopack caches package barrels.** After editing anything under `web/packages/**`, a recompile
is not enough — the dev container keeps serving the old module graph and reports errors that the
source no longer has. `docker restart agenta-ee-dev-web-mobile-1` and wait for `Ready in`.
- **Backend changes need the API container restarted**, and `_oauth_card` output is only visible in
the popup — read it with "View Source" on the popup, not the main window.
- **Grep `api/` and `services/`, not just `web/`.** The one wrong turn already made in this
investigation was concluding "nothing sends this message" from a frontend-only grep.
- **Check for `-report-only` before blaming a COOP header.** The second wrong turn.
- **This repo may be in GitButler workspace mode, and lanes get rebased under you.** Before any
push, re-verify the branch still contains only your files:
`git diff --name-only <base>..<branch>`. A stack rebase mid-session silently rewrote every SHA
during the query-client work and made an untouched file appear as a reversal.

## Landing it

- Branch off the same base the related work uses, and set the PR base to match — a wrong base turns
the diff into the whole stack.
- API changes: run `ruff format` then `ruff check --fix` in `api/`. Frontend: `pnpm lint-fix` in
`web/`. Both must be clean.
- Title `fix(api): ...` or `fix(frontend): ...` per the area you touched; if you have an issue id,
prefix it. The `write-pr-description` skill has the format.
- Say plainly in the PR which paths were driven live and which were only reasoned about. The
failure path is the point of the change, so if you did not deny a real consent screen, say so.
150 changes: 150 additions & 0 deletions docs/design/oauth-callback-settle/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# The OAuth callback only talks back to Agenta when it succeeds

Status: **diagnosed, not started.** Root cause below is verified against the code and the running
EE dev stack. No fix has been written.

## The symptom

Connect or reconnect a gateway tool (Settings → Tools, Settings → Triggers, or the in-chat connect
flow). If the provider authorization **fails or is denied**:

- The popup shows an error card with no buttons and never closes on its own.
- The opener never learns the flow ended: the row keeps its spinner, nothing is invalidated.
- When the user finally closes the popup by hand, the in-chat flow reports the result to the agent
as **`reason: "cancelled"`** — the user is told they cancelled something they actually attempted,
and the real provider error (which the callback page rendered) is thrown away.

On success the same flow works correctly. That asymmetry is the whole bug.

## Root cause

`_oauth_card()` in `api/oss/src/apis/fastapi/tools/router.py:1508` renders the callback page. Its
`agenta_url` parameter drives three separate things:

```python
agenta_origin = None
if agenta_url: # ← everything below hangs off this
...
agenta_origin = f"{scheme}://{netloc}"
agenta_post_message_origin_js = _json_for_inline_script(agenta_origin)
```

- **the postMessage target origin** — the page only posts when it is set:
`if (window.opener && AGENTA_POST_MESSAGE_ORIGIN) { window.opener.postMessage(...) }` (`:1748`)
- **the "Return to Agenta" button** — `agenta_btn` is `""` unless `safe_agenta_url` (`:1581`)
- **the 5-second auto-close** — `auto_return_html` requires `success and safe_agenta_url` (`:1592`)

There are **six** `_oauth_card()` call sites. Exactly one passes `agenta_url`:

| line | outcome | passes `agenta_url`? |
|---|---|---|
| 919 | `success=False` — provider returned an error | **no** |
| 930 | `success=False` — missing connection identifier | **no** |
| 950 | `success=False` — connection could not be activated | **no** |
| 971 | `success=False` — connection could not be activated | **no** |
| 982 | `success=False` — internal error | **no** |
| 1037 | `success=True` | yes (`agenta_url=env.agenta.web_url`, `:1042`) |

So on every failure path `agenta_origin` is `None`, and the card degrades to: no message, no button,
no auto-close. A dead end.

Note the auto-close is gated on `success and safe_agenta_url`, so passing `agenta_url` alone still
leaves failure cards without the countdown. Both conditions need revisiting — see WP1.

## Why the opener then reports "cancelled"

Every listener falls back to a popup-closed poll, and each treats "closed without a message" as
abandonment. `web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts:221`
(the `reason: "cancelled"` is `:227`; a second one at `:279` handles an explicit abort):

```ts
const poll = window.setInterval(() => {
if (!popupRef.current?.closed || succeeded) return
// Abandon: closed without a success message.
teardown()
if (settleParkedCall) finish({connected: false, integration, slug, reason: "cancelled"})
...
```

The poll is a reasonable fallback for a user who really does close the window. It is the *only*
path on failure, so "the provider rejected the grant" and "the user changed their mind" become
indistinguishable — and the agent is told the wrong one.

The payload itself is also outcome-free: `{"type": "tools:oauth:complete", "slug", "integration"}`
(`:1537`). Even once failure cards start posting, an opener cannot tell success from failure from
the message; it can only re-read server state. WP2 addresses that.

## Affected surface

Six listeners, all with the same fallback shape:

| file | flow |
|---|---|
| `agenta-entity-ui/src/gatewayTool/drawers/ConnectDrawer.tsx:156` | connect a tool |
| `agenta-entity-ui/src/gatewayTool/drawers/ConnectionManagerDrawer.tsx:115` | manage connections |
| `agenta-entity-ui/src/gatewayTool/hooks/useReconnectToolConnection.ts:70` | reconnect a tool |
| `agenta-entity-ui/src/gatewayTrigger/drawers/TriggerConnectDrawer.tsx:191` | connect from Triggers |
| `agenta-settings-ui/src/tools/GatewayToolsSection.tsx:116` | Settings → Tools |
| `oss/…/AgentChatSlice/components/clientTools/useConnectFlow.ts:202` | in-chat connect |

Only the last one reports a `reason` to an agent, so it is where the wrong answer does real damage.
The other five just fail to settle.

## Work plan

### WP1 — every callback outcome talks back

Pass `agenta_url` at all six call sites, or default it inside `_oauth_card()` from
`env.agenta.web_url` so a new call site cannot forget it. Prefer the default: the parameter exists
only to be threaded through, and five of six authors already omitted it.

Then relax `auto_return_html`'s `success and safe_agenta_url` to `safe_agenta_url` so failure cards
also close themselves. Consider a longer countdown on failure, so the error stays readable.

### WP2 — the message carries the outcome

Add `success` (and `error` when present) to `oauth_complete_payload`. Openers can then distinguish a
denied grant from a cancelled window, and `useConnectFlow` can report a truthful `reason` and
surface the provider's message instead of discarding it.

Keep absent keys working: existing openers ignore unknown fields, and a payload with no `success`
key must keep meaning "completed, go re-read the server" so an older frontend against a newer API
does not regress.

### WP3 — the openers use it

`useConnectFlow` distinguishes three cases: message with `success: true` → connected; message with
`success: false` → failed, with the provider error; popup closed with no message → genuinely
cancelled. The other five listeners only need to keep invalidating, which they already do.

## Verification

Reproduce the failure path directly — click **Deny** on the provider consent screen, which routes to
the `:919` call site. Expected today: an error card with no buttons that never closes, and an opener
that never settles. Expected after WP1: the card closes itself and the row settles without a reload.

Success paths must keep working; they are the only ones exercised today, so they are the regression
risk.

## Corrections to earlier claims — do not repeat these

- **"Nothing sends `tools:oauth:complete`."** Wrong, and it nearly became the premise of this
document. It came from grepping only `web/`, where all six hits are listeners. The sender is the
**backend**, in an inline `<script>` built from `_json_for_inline_script`, so the literal string
and the `postMessage` call are on different lines and neither grep alone finds it. Search
`api/` and `services/` too, and grep the constant name, not just the message type.
- **"COOP blocks `popup.closed`, so the poll never fires."** Wrong. Google sends
`cross-origin-opener-policy-report-only`, and report-only reports without enforcing. The console
line "Cross-Origin-Opener-Policy policy **would** block the window.closed call" is the
report-only wording; the poll works. Check for the `-report-only` suffix before blaming COOP.

## Related, and deliberately separate

On `/m` the whole question is moot until the QueryClient host fix lands (**PR #5915**): package-layer
`invalidateQueries` there addresses an orphan client, so even a correct callback message settles
nothing. Test this work on a branch that contains that fix, or the results are meaningless.
Background: `docs/design/query-client-host-divergence/plan.md`.

Also noted while reading this code and **not** part of this work:
`gatewayTool/drawers/ConnectDrawer.tsx`'s local `invalidateConnections` misses
`["triggers", "connections"]` — tracked as WP5 in the query-client plan.
25 changes: 12 additions & 13 deletions docs/design/observability-chrome/KICKOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ first — they are the difference between five agents helping and five agents co

Start **one** new Claude Code session and paste:

> Read
> `/Users/ardaerzin/Documents/GitHub/agenta_open_source/.claude/worktrees/sessions-ux/docs/design/observability-chrome/KICKOFF.md`,
> then
> `/Users/ardaerzin/Documents/GitHub/agenta_open_source/.claude/worktrees/sessions-ux/docs/design/observability-chrome/plan.md`.
> Work in
> `/Users/ardaerzin/Documents/GitHub/agenta_open_source/.claude/worktrees/sessions-ux` on branch
> `obs/wp3-observability-ui`.
> Read `docs/design/observability-chrome/KICKOFF.md`, then
> `docs/design/observability-chrome/plan.md`.
> Work in this worktree, on the branch it already has checked out — confirm both with
> `git rev-parse --show-toplevel` and `git branch --show-current` before you start.
> Execute the chrome conversion. Dispatch the waves in §3 using sub-agents, sending each wave's
> agents in a single message so they run in parallel, using the prompts in
> `/Users/ardaerzin/Documents/GitHub/agenta_open_source/.claude/worktrees/sessions-ux/docs/design/observability-chrome/agent-prompts.md`
> `docs/design/observability-chrome/agent-prompts.md`
> verbatim as their payloads. Report back after each wave before starting the next.

That session is the **orchestrator**. It does not write component code itself — it dispatches,
Expand Down Expand Up @@ -123,8 +120,9 @@ Dispatch P0-A and P0-B together. Both are self-contained and block later tracks.
the conventions of the existing files there (Radix + `cn` from `../../utils/styles`). It
replaces antd `DatePicker` in `web/oss/src/components/Filters/Sort.tsx` — read that file's
custom-range branch first. It must emit `{startTime, endTime}` as ISO strings.
`@agenta/ui` has no date library today; `dayjs` is used repo-wide. Choose, justify in a comment,
and add the dependency explicitly. Do not edit `ui/index.ts`; report the export line instead.*
Take `dayjs` through the shared wrapper (`@agenta/shared/utils/dateTime`) rather than adding a
direct dependency — that is what shipped, and it keeps one date surface across the packages. Do
not edit `ui/index.ts`; report the export line instead.*

**P0-B prompt sketch:** *Decide whether `@agenta/ui/src/components/ui/cascader.tsx` can replace
antd `TreeSelect` at `web/oss/src/components/Filters/Filters.tsx:1395`, which renders
Expand Down Expand Up @@ -160,10 +158,11 @@ cd web
pnpm lint-fix
pnpm --filter @agenta/oss exec tsc --noEmit > /tmp/t.txt 2>&1; echo "exit=$?"
pnpm turbo run build lint --filter=@agenta/<package>
pnpm --filter @agenta/<package> test
pnpm --filter @agenta/<package> test # only where the package defines a test script

# antd gate for both UI packages (must print nothing)
grep -rn 'from "antd"\|from "@ant-design' web/packages/agenta-observability-ui/src
# antd gate for both UI packages (must print nothing).
# Paths are relative to web/ — this block already ran `cd web`.
grep -rn 'from "antd"\|from "@ant-design' packages/agenta-observability-ui/src
```

**Capture `$?` directly into a file. Never pipe tsc to `head`/`grep` and read the exit code** —
Expand Down
6 changes: 4 additions & 2 deletions docs/design/observability-chrome/agent-prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Wave 3 (I, alone). T5 anytime **except** during Wave 3.
**Prepend this preamble to every prompt:**

> You are working in the Agenta monorepo at
> `/Users/ardaerzin/Documents/GitHub/agenta_open_source/.claude/worktrees/sessions-ux`, on
> the repository worktree you were started in, on
> branch `obs/wp3-observability-ui`. Frontend lives under `web/`.
>
> Before you start, read: `docs/design/observability-chrome/plan.md`,
Expand Down Expand Up @@ -162,7 +162,9 @@ Wave 3 (I, alone). T5 anytime **except** during Wave 3.
> pure parts. These replace most of `web/oss/src/components/Filters/Filters.tsx` (1,983 LOC).
> **Read it first.** Do not edit it.
>
> Your scope is the dialog shell and the generic filter row:
> Your scope is the dialog shell, the generic filter row, and `FilterTagsInput.tsx` — the
> multi-VALUE tags input (antd `mode="tags"`) the generic row needs. The export handoff lists
> it under T3, so it is yours; do not leave it unowned:
> - **Shell** (from ~line 840): the popover container, heading, `+ Add`, `Clear`, `Cancel`,
> `Apply`. Use `popover` from `@agenta/ui/ui` and `EnhancedButton`.
> - **Generic row:** field menu (antd `Dropdown` + `MenuProps` → `dropdown-menu` from
Expand Down
Loading
Loading