Skip to content

feat(reef): make the providers rail vertical tabs with an All providers entry - #2552

Merged
AlbertQM merged 4 commits into
mainfrom
reef/providers-vertical-tabs
Oct 20, 2026
Merged

AlbertQM merged 4 commits into
mainfrom
reef/providers-vertical-tabs

Conversation

@AlbertQM

@AlbertQM AlbertQM commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The providers rail becomes a set of tabs. The table shows one provider, or every provider under All providers. This reverses the single scrolling table from v1 (#2321).

 <CatalogRail "installed providers">
+  <CatalogRailItem "All providers">     → /workspaces/:w/providers
   <CatalogRailItem provider>            → /workspaces/:w/providers/:provider
-    <Button "Show only …">              (rail filter = exactFilter(name))
     <Link "… settings">
 <Table.Container "Operations">
-  rows: every provider the rail filter keeps
+  rows: the tab's provider, or every provider
-  onPinnedGroupChange → replace URL with the scrolled-to provider
-  <Table.VirtualTail>
tab
  provider route        → that provider ("" route → All providers)
  operation route       → keep the current tab if it lists the operation (All providers or its provider);
                          otherwise, the operation's provider
  change of tab         → scroll the table to its top; keep the selection
  rail filter           → narrows the rail only, not the table
  "Show selected"       → opens All providers

Retired with the old rail: usePinnedProviderAddress, exactFilter (and its AGENTS.md rule), and Wax Table.VirtualTail / tailRoom.

Evidence

  • Before: workspace-providers-page.dom.test.tsx asserted no tabs, and that the rail filter and Show only narrowed the table. Reel catalog/navigation asserted that scrolling rewrote the URL and that a rail click scrolled a short last provider under the head.
    After:
    • Vitest: each rail entry is a tab, and all providers lists every one. The test covers aria-current, the URL, selection that survives a change of tab, and the rail filter narrowing the rail only.
    • Story: Tree Operation Selected (arrival opens the operation's provider tab). The undeclared-hints check moved to Tree Operation Without Description.
    • Reel providers/catalog/navigation: a tab shows only its provider, selection survives a change of tab, All providers is the bare address, and a new tab opens at its top.
    • Reel providers/catalog/selection: the provider tab replaces the rail-filter workaround.
  • Ran: npm run check, typecheck, npm test (657 pass), and build. test:server has 15 pass and 1 fail, in scripts/bundle-server.test.mjs, which this PR does not touch. The Reel providers/ folder ran on main-sqlite; results are posted in a comment below.

Merge Danger

Door: two-way

Only UI and tests change. No data, API, or URL shape changes. Old provider links still open that provider.

Blast Radius: providers-page

Shared links to a provider now open only that provider's tab, not the whole list scrolled to it. Wax consumers lose Table.VirtualTail; nothing else used it.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/reef/app/views/providers/workspace-providers-page.dom.test.tsx
Comment thread apps/reef/app/wax/components/table/virtual.tsx
Comment thread apps/reef/app/views/providers/workspace-providers-page.tsx
Comment thread apps/reef/app/views/providers/workspace-providers-page.tsx Outdated
@AlbertQM

AlbertQM commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Reel providers/ folder, main suite, SQLite: 72 of 74 passed on the first run. Two scenarios failed:

  • catalog/navigation read aria-current before the new tab rendered. It now polls (a4276b9).
  • setup/oauth-configuration timed out during sign-in, before it reached the providers page.

A rerun of both now passes (5 of 5). The Postgres variant has not run.

@AlbertQM
AlbertQM force-pushed the reef/providers-vertical-tabs branch from 266f690 to 7f877fd Compare October 20, 2026 13:03
@AlbertQM
AlbertQM merged commit 08d007a into main Oct 20, 2026
25 checks passed
@AlbertQM
AlbertQM deleted the reef/providers-vertical-tabs branch October 20, 2026 13:16
iaptsiauri added a commit that referenced this pull request Oct 21, 2026
…one (#2580)

## Summary

An agent could call `createProvider` with an OAuth Provider
Configuration that names no OAuth application, for a service Coral
cannot register one with (no Dynamic Client Registration or Client ID
Metadata Document, such as Google), or for device authorization. Coral
returned `needs_sign_in`. The person's sign-in then failed, and Reef
showed only "Coral is not available."

`createProvider` now refuses the call, and the agent can correct it:

```diff
 createProvider
   prepare Setup, confirm Provider Configuration
   SetupPublishability
     Coral  → publish
+    Person, and the sign-in has no OAuth application
+      → end the Setup
+      → invalid_input at `authentication.client`:
+        "Coral cannot register an OAuth application with this service;
+         register one with the service and give its `client_id`"
     Person → needs_sign_in + Reef link
```

```text
has_oauth_application(authentication)
  oauth_authorization_code without client → can Coral set one up for this Setup?
  oauth_device_code without client        → no (Coral never sets one up)
  anything else                           → yes
```

An older Coral server can still confirm such a configuration. Reef
handles that case:

```diff
 attemptProgress
   stage
+    confirmed configuration, OAuth sign-in not started, no application,
+    and Coral cannot set one up                    → configuration
 <AccountStep>
-  <Button "Sign in with …" disabled={pending}>
+  <Button "Sign in with …" disabled={pending || no application}>
 <ManagementErrorBoundary>
-  any other status → "Coral is unavailable. Reload the page to try again."
+  400 / 409 with a body → Reef's reason
+  other 4xx             → "Coral cannot complete this request. Go back to the previous page."
+  5xx                   → "Coral is unavailable. Reload the page to try again."
```

The configuration hint now reads "Enter the client ID of your OAuth
application in configuration before you sign in."

`isOAuthMethod` and `automaticApplicationAllowed` replace four inline
copies of those checks in Reef. `TestModules::accounts()` replaces four
copies of the filesystem-vault setup in the `coral-run` tests.

## Evidence

The new tests ran against `main`'s source (tests from this branch), then
against this branch:

- **Before:** `cargo test -p coral-run --lib
an_invalid_argument_names_what_to_correct` fails. The OAuth case with no
client returns `{"status": "needs_sign_in", "url":
"…/providers/new?setup=…"}`, not `invalid_input`.
**After:** passes for both the authorization-code case and the
device-code case. Each denial also asserts it published nothing and
canceled every Setup it prepared.
- **Before:** Reef Vitest fails 9 cases:
- `model.test.ts`: the agent's link for an MCP, API or device sign-in
Coral cannot set up opens `discovery` / `operations`, not
`configuration`
- `account-step.test.tsx`: "Sign in with Gmail" is enabled for the
shared account and for your own account
- `management-error.test.tsx`: a 400 or 409 shows "Coral is unavailable"
  - `oauth-application.test.tsx`: the old hint copy

  **After:** all 66 cases in those four files pass.
- Gates on this branch, rebased on `main`:
  - Rust: `make rust-checks` passes, 1469 of 1469 (6 skipped).
- Reef: `npm run check`, `typecheck`, `npm test` (1644 + 661 pass) and
`build` pass. `test:server` has 15 pass and 1 fail, in
`scripts/bundle-server.test.mjs` (`'hello bundle'` vs `'hello staged'`).
This PR does not touch that file. #2552 reported the same failure.
  - Not run: `make postgres-tests`. No repository code changes.

## Merge Danger

**Door:** two-way

No schema, stored data or API shape changes. Revert restores the old
`needs_sign_in` answer.

**Blast Radius:** provider-setup

Agents that call `createProvider` with OAuth and no `client` for a
service without automatic registration now get `invalid_input`, not a
Reef link. That link led to a sign-in that could not start. Management
pages that end in a 4xx other than 401, 403 or 404 now say the request
failed, not that Coral is unavailable.

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
simonwhitaker pushed a commit that referenced this pull request Oct 22, 2026
🤖 I have created a release *beep* *boop*
---


## [1.2.0](v1.1.0...v1.2.0)
(2026-10-22)


### Features

* **activity:** record authored source locations for calls and outputs
([#2535](#2535))
([2d735ca](2d735ca))
* **activity:** show persistent Program source beside selected records
([#2569](#2569))
([f403819](f403819))
* add the Claude recipe and read gzipped OpenAPI documents
([#2513](#2513))
([07c27a2](07c27a2))
* **catalog:** share named TypeScript types across a Provider
([#2482](#2482))
([256e37d](256e37d))
* **desktop:** run the runtime on a Postgres database named in
desktop.json ([#2485](#2485))
([96465a0](96465a0))
* **functions:** type coral.providers, coral.workspace and
coral.functions at Saved Function save
([#2526](#2526))
([a465850](a465850))
* **provider:** create the Coral Built-in Provider at coral.workspace in
every new Workspace
([#2349](#2349))
([06e65ca](06e65ca))
* **provider:** store an agent's createProvider request on its Setup
([#2593](#2593))
([92847d2](92847d2))
* **provider:** update every Workspace's Built-in Provider catalog at
boot ([#2355](#2355))
([24615d9](24615d9))
* **reef:** add NextDNS provider recipe
([#2509](#2509))
([e44880e](e44880e))
* **reef:** calmer providers table and a flush app shell
([#2462](#2462))
([ffe279c](ffe279c))
* **reef:** improve the UI on small screens
([#2531](#2531))
([75e020a](75e020a))
* **reef:** make the providers rail vertical tabs with an All providers
entry ([#2552](#2552))
([08d007a](08d007a))
* **reef:** providers page UI tweaks
([#2562](#2562))
([7ca218e](7ca218e))
* **reef:** put the list pages' main action back in the top bar as a
primary button ([#2546](#2546))
([761ea6d](761ea6d))
* **reef:** read ^, $ and * in every filter field
([#2520](#2520))
([d322356](d322356))
* **reef:** refuse a provider display name another provider uses
([#2518](#2518))
([fdcd473](fdcd473))
* **reef:** review provider configuration changes in a dialog
([#2579](#2579))
([24417e9](24417e9))
* **reef:** show search indexing failures on a Provider and its
Operations ([#2504](#2504))
([6cffed0](6cffed0))
* **reef:** show your recent activity summary on the workspace overview
([#2574](#2574))
([0d1dba5](0d1dba5))
* **reef:** title list pages in their toolbar, with the main action
beside it ([#2525](#2525))
([fd44b72](fd44b72))
* **reef:** UI tweaks to provider's page
([#2566](#2566))
([62204ca](62204ca))
* **reef:** unify Activity detail panels and action reviews
([#2544](#2544))
([409d062](409d062))
* return only declared successes from Operation calls
([#2517](#2517))
([0270bf1](0270bf1))
* **run:** enforce the Fan-out Limit inside the Run Owner
([#2571](#2571))
([cb53419](cb53419))
* **run:** introduce the Run Owner for Program requests
([#2524](#2524))
([245d19d](245d19d))
* **run:** let coral.workspace.createProvider configure and publish the
Provider itself
([#2352](#2352))
([088faaf](088faaf))
* **run:** let createProvider configure sign-in and link to Reef's
sign-in step ([#2353](#2353))
([8d5495c](8d5495c))
* **run:** list a workspace's providers with
coral.workspace.listProviders
([#2374](#2374))
([179c43a](179c43a))
* **run:** make the Run Owner survive uncertain commits and lost leases
([#2541](#2541))
([3a1fad1](3a1fad1))
* **run:** name Tasks and Runs with four-word identifiers
([#2558](#2558))
([c82259a](c82259a))
* **run:** prepare a Provider from a Program with
coral.workspace.createProvider
([#2351](#2351))
([e7e56fd](e7e56fd))
* **run:** reshape the Dispatch Fence into one batched check per chunk
commit ([#2550](#2550))
([120c9d3](120c9d3))
* **run:** route approval decisions and cancellation through the Run
Owner ([#2577](#2577))
([77bcbf5](77bcbf5))
* **run:** summarize your recent activity in a Workspace
([#2573](#2573))
([bf4c75e](bf4c75e))
* **search:** show a Saved Function's result fields in its signature
line ([#2522](#2522))
([41365f6](41365f6))
* **search:** trace each reconcile pass and its steps
([#2456](#2456))
([0594094](0594094))
* **ui:** add option to create a correctly-configured Slack app during
Slack provider setup
([#2585](#2585))
([8c3a883](8c3a883))
* **ui:** move Add provider to the providers toolbar
([#2591](#2591))
([e1ee4cb](e1ee4cb))


### Bug Fixes

* **app:** sweep device authorization leases every 15 s
([#2452](#2452))
([1a4e4ae](1a4e4ae))
* **auth:** drop the unverified client name note from the approval page
([#2578](#2578))
([ddf5aa4](ddf5aa4))
* **catalog:** derive each Built-in Operation's contract from the types
Coral parses ([#2582](#2582))
([534e636](534e636))
* **catalog:** import OpenAPI Providers whose Operations request
different OAuth scopes
([#2401](#2401))
([3acfe55](3acfe55))
* **catalog:** keep keyword names in Operation Call Paths
([#2506](#2506))
([b20795b](b20795b))
* **catalog:** never make an Operation unavailable for search material
([#2465](#2465))
([f83c909](f83c909))
* **catalog:** stop rejecting createProvider request headers by name
([#2589](#2589))
([39e54b9](39e54b9))
* **provider:** ask for an OAuth client ID when Coral cannot register
one ([#2580](#2580))
([baefe9e](baefe9e))
* **provider:** bind each Setup to its starter and hide Coral from
Members ([#2480](#2480))
([6011a64](6011a64))
* **reef:** align the catalog table with the first rail entry
([#2484](#2484))
([fb59758](fb59758))
* **reef:** calm the Activity badge and keep it while the stream
reconnects ([#2468](#2468))
([1b66639](1b66639))
* **reef:** frame the providers table again and lower the top bar
([#2472](#2472))
([87e41ee](87e41ee))
* **reef:** pin third-party recipe OpenAPI documents to a commit
([#2510](#2510))
([b4e9280](b4e9280))
* **reef:** replace loose help text with info tips and banners
([#2461](#2461))
([695500a](695500a))
* **reef:** round the page's corners beside the sidebar
([#2503](#2503))
([a1fef64](a1fef64))
* **reef:** shorten OAuth scope chips that share a URL prefix
([#2455](#2455))
([5ae67ec](5ae67ec))
* **reef:** suggest a provider's namespace from its document name, not
its host ([#2565](#2565))
([8d77893](8d77893))
* **run:** name every argument to correct in an invalid_input failure
([#2354](#2354))
([07bd727](07bd727))
* **run:** reject a credential beside createProvider's bearer method and
report unreachable sources as upstream_incomplete
([#2542](#2542))
([590eab7](590eab7))
* **run:** report an MCP JSON-RPC rejection as invalid_input or
upstream_rejected
([#2402](#2402))
([749770b](749770b))
* **search:** load each Generation's schema graph once per search
([#2448](#2448))
([445d945](445d945))
* **search:** retry a failed Saved Function only after one interval
([#2460](#2460))
([a61ab3a](a61ab3a))
* **search:** return no hits for a query without a letter or digit
([#2561](#2561))
([bb0a4f0](bb0a4f0))
* **search:** start the write limit at the lock and build rows before
the write ([#2575](#2575))
([06ad55d](06ad55d))
* **search:** stop a self-referential Promise result from overflowing
the stack ([#2543](#2543))
([1d4f257](1d4f257))
* **search:** stop retrying an index write that fails the same way every
time ([#2548](#2548))
([e3b6d15](e3b6d15))


### Performance Improvements

* **catalog:** dedup credential destinations once per build, not per
Operation ([#2555](#2555))
([0afbcf5](0afbcf5))
* **catalog:** project each named TypeScript type once per Provider
([#2443](#2443))
([153c273](153c273))
* **search:** drop schema text from the phrase test
([#2559](#2559))
([cfdbafa](cfdbafa))
* **search:** index each word of schema text once
([#2547](#2547))
([d1123fb](d1123fb))
* **search:** prepare search entries in the indexer after commit
([#2467](#2467))
([1ab6765](1ab6765))
* **search:** recount only Workspaces whose rows changed
([#2457](#2457))
([777cfb3](777cfb3))
* **search:** skip unchanged rows when an index write runs again
([#2459](#2459))
([43b5188](43b5188))
* **search:** write only the statistics rows that changed
([#2458](#2458))
([8e2832c](8e2832c))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: coral-release-bot[bot] <269256696+coral-release-bot[bot]@users.noreply.github.com>
Co-authored-by: release-please[bot] <release-please[bot]@users.noreply.github.com>
AlbertQM added a commit that referenced this pull request Oct 22, 2026
## Summary

```diff
 <Table.Container layout="fixed"> (apps/reef/app/wax/components/table/container.tsx)
-  maxHeight?: number      # pixels, heading included
+  maxRows?: number        # rows beneath the heading; the rest scroll there
+                          # with maxRows, groupHeadings takes only 'plain'
   --wax-table-rows-max-height
-    maxHeight - HEADING_HEIGHT_PX[density]
+    maxRows * CELL_HEIGHT_PX[density]
```

A caller now names how many rows show, not how many pixels they take, so
a cap follows the density's row height. `CodeBlock` already caps by
`maxLines` in the same way. A prominent group heading is 8px taller than
a row and would cut the last row, so the props reject it with `maxRows`.

```diff
 <PermissionsTable> (apps/reef/app/views/accounts/permissions-table.tsx)
-  maxHeight={MAX_HEIGHT}  # 360: "about ten compact rows"
+  maxRows={10}
```

## Evidence

- **Before:** no test covered the row cap. The permissions table showed
ten rows and a quarter of the eleventh (360 = 32 + 10.25 × 32).
**After:** story `ScrollingRowsInsideTable` (`maxRows={5}`) checks the
cap:
  ```text
  row_4 bottom == rows' scroll port bottom
  row_5 top    >= rows' scroll port bottom
  ```
  It fails when the cap is 8px taller than five rows.
- `container.test.tsx`: `maxRows` of `0`, `-1`, `1.5`, `Infinity` and
`NaN` throw a `RangeError`; `3` renders. The five rejections fail
without the check.
- Reef: `check`, `typecheck`, `npm test` (2070 unit + 850 story) and
`build` pass. `test:server` has 15 pass and 1 fail in
`scripts/bundle-server.test.mjs`, as #2552 reports.

## Merge Danger

**Door:** two-way

A Wax prop rename. No API, schema or stored state changes.

**Blast Radius:** permissions-table

The permissions list is 8px shorter: it no longer shows part of the
eleventh row. Chromatic shows this change.
AlbertQM added a commit that referenced this pull request Oct 22, 2026
…2586)

## Summary

```diff
 <AgentActivityPage> (apps/reef/app/views/management/agent-activity-page.tsx)
-  variant="split"
-  header: <AgentQuickStart> + <AgentProviders> (card)
-  body:   {activity}
+  header (both views): <ArchivedBanner> when the workspace is archived
+  ?view=activity | readsActivity(query) → variant="split", breadcrumb "Activity", "Back to agent"
+    {activity}                       # the complete list, unchanged
+  otherwise                          → variant="wide"
+    <AgentQuickStart>                # idle agents only
+    grid                             # one column at breakpoints.mobile (640px)
+      <AgentProviders>               # table card, maxRows={3}; hidden with no providers
+      <section "Recent activity">    # as tall as its records
+        <LiveActivity more={{ to: "?view=activity", label: "View all activity", roots: 3 }}>
```

`readsActivity` (`app/views/activity/navigation.ts`) is true when the
URL selects a record or positions the list: `record`, `tree`, `expand`,
`anchor`, paging and branch keys. Only the complete list can show those,
so deep links like `agent?expand=<task>` keep working. A record opened
from Recent activity also stays on the complete list after you close it.

Neither card has a fixed pixel height. Recent activity lists at most
three records, and the grid row takes the taller card.

## Evidence

- **Before:** the agent page shows the complete Activity beside a setup
column, and has no glimpse. Story `AgentActivityAfterRecord`
(`agent?anchor=x&tree=1`) fails when the page checks only `view` and
`record`. Story `ArchivedAgentFullActivity` fails when the complete
Activity drops the header. CI failed `activity/history`
(`agent?expand=<task>` showed the dashboard) and `activity/keyboard`
(the keyboard reader moved to `?view=activity`).
**After:** both stories pass. New Reel scenario
`main/agents/dashboard.test.ts` passes on main-sqlite:
  ```text
  run 4 agent tasks → agent page lists goals 4, 3, 2; not 1
  run goal 5        → appears live; goal 2 drops out
View all activity → ?view=activity lists goal 1 → Back to agent →
dashboard
open goal 5 → ?record=… → Close details → still the complete list
  ```
- Reef: `check`, `typecheck`, `npm test` (2071 unit + 855 story) and
`build` pass. `test:server` has 15 pass and 1 fail in
`scripts/bundle-server.test.mjs`, as #2552 reports.
- `navigation.test.ts` table test for `readsActivity`.
- `container.test.tsx` restores its `console.error` spy after each case,
a follow-up to #2601.
- Reel main-sqlite on this stack: `agents/`, `activity/keyboard`,
`activity/history` and `accounts/` pass (13 files, 21 tests). The full
suite runs in CI.

## Merge Danger

**Door:** two-way

Reef layout and query handling only. No API, schema or stored state
changes.

**Blast Radius:** agent-page

Links to an agent with a record or list position (Run status pages,
Activity rows) now open the complete-list view with a breadcrumb
"Activity". The dashboard drops to one column at 640px.
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