Skip to content

connections: categorised, searchable connector grid (#600) - #639

Merged
oxoxDev merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/600-connector-grid
Aug 11, 2026
Merged

connections: categorised, searchable connector grid (#600)#639
oxoxDev merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/600-connector-grid

Conversation

@CodeGhost21

@CodeGhost21 CodeGhost21 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Closes #600.

Sequencing — #637 lands first

This PR is second in line behind #637 (feat(connections): broker providers through the company's own TinyHumans credential). Stated here rather than left to whichever branch pushes last, per review.

The two share five files. #637 establishes the credential tier that decides whether a company can connect anything; this decides how the resulting options are presented. Credential before presentation is the right order, and #637 is the older PR.

They compose. I stacked this branch's two commits onto fix/586-brokered-company-credential locally and ran the suite. The entire overlap is two import lines:

File Conflict Resolution
src/company/composio.rs #637 adds use std::sync::Arc, this adds use serde::Serialize both kept
src/server/ops/composio.rs #637 drops token_configured (moved into resolve_credential), this adds CatalogEntry #637's import + CatalogEntry

No semantic interaction: #637 rewrites credential_source_for / effective_status's credential half, this touches effective_toolkits, fetch_catalog and the DTO's catalog half. Stacked, cargo test --locked passes 1822 + 10 + 1 and --features composio passes 88 (the one failure is the pre-existing network case noted below).

I have deliberately not retargeted this PR's base to #637's branch. #637 is currently 55 commits behind main, so stacking now would run this PR's CI against a stale base and lose the green run against current main. When #637 merges I will rebase this onto main and re-verify.

Summary

Settings → Connections rendered 123 providers as one flat vertical list, twelve at a time behind a "Show all 123 providers" button.

The layout was a symptom, not the defect. list_catalog_toolkits (src/harness/composio.rs) read the backend's catalog[] — whose entries carry slug, name, logo, description and categories — and reduced every one of them to a lowercased slug:

resp.catalog.iter()
    .filter(|entry| entry.enabled.unwrap_or(false))
    .map(|entry| normalize(&entry.slug))   // ← five of six fields dropped here

That Vec<String> then survived every layer downstream unchanged — the catalog cache, OpenModeToolkits, ComposioStatusDto, and frontend/src/api/composio.ts — so the console received 123 bare strings. Nothing to group by, nothing to brand with, and nothing to search but the slug. Its flat list was the honest rendering of what it was handed. The backend already assembles all of this metadata and backend/src/services/composio/catalog.ts states it does so "so the frontend reads name/logo/description/categories straight from here"; it was being discarded one layer short.

#397 built this surface sized for 8 providers and #556 un-capped it to 123 without revisiting the shape — the PREVIEW_COUNT = 12 collapse is the seam where that showed.

Host

  • CatalogEntry (slug, name, description, logo, categories) lands in company::composio, not the harness — the status route is always compiled and src/harness/ is behind the openhuman feature. Same reason TOKEN_KEY and backend_url_or_default live there.
  • Threaded through the catalog cache, OpenModeToolkits and the status DTO.
  • The DTO gains effectiveCatalog alongside effectiveToolkits rather than replacing it: the slug list is the existing contract and is still all an authorize call needs. OpenModeToolkits::slugs() derives it, so the two cannot disagree about what is on offer.
  • De-duplication is or_insert_with, not collect() into the BTreeMap — collecting keeps the last value per key, and a duplicate catalog entry is typically the degenerate one. The test mock's metadata-less Gmail (dup) silently blanked the real Gmail's description until this was explicit.
  • Manifest lists, the degraded fallback, and backends predating the dynamic catalog all yield slug-only entries. That is a first-class state, not a reason to drop a provider — the console renders them with its own typography.

Console

Ports the four behaviours from OpenHuman's Skills.tsx / toolkitMeta.tsx, now that the metadata they need is on the wire:

  • Category chips over a fixed, ordered bucket list, offering only buckets that are actually populated — a chip that reliably yields an empty grid teaches the operator to distrust the whole row.
  • Buckets derived from Composio's own categories[] by substring (mapComposioCategory, near-verbatim). This is the piece worth copying: 123 providers bucket themselves, and provider 124 does too, with no edit on either side of the wire. A slug-only entry falls through to a slug/name keyword heuristic rather than vanishing.
  • Search over name, slug AND description, composed with the chip (AND, not replacing it). Matching on description is new — there was no description on the wire before — so "invoices" now reaches Stripe and QuickBooks.
  • A dense branded tile grid replacing the rows. The preview cut is deleted rather than relabelled: it was a workaround for a flat list being unreadable, and a grid does not need it.

Two deliberate calls in the tile: the whole tile is the affordance (an 8.5rem tile has no room for a label and a button both), and non-actionable tiles — connected, or a viewer who cannot manage — render as a div rather than a disabled button, so "Gmail, connected" stays in the reading order for a member who opened this panel to learn exactly that. Logos are best-effort by construction (derived from the slug wherever the backend published none), so the tile catches the 404 and falls back to a monogram instead of a broken image.

API Or Behavior Changes

  • GET …/composio gains effectiveCatalog — an array of {slug, name, description, logo, categories}, same providers and same order as effectiveToolkits. Additive: effectiveToolkits is unchanged in type, contents and order, so existing consumers need no change. Pinned by the DTO key-order test.
  • harness::composio::list_catalog_toolkits returns Vec<CatalogEntry> instead of Vec<String>. Internal; its only non-test caller is the open-mode status path.
  • Agent-side admission is untouched. toolkit_allowed takes slugs and never consulted this function. This widens what is described, not what is permitted — and a non-empty manifest allowlist still cannot be widened by the catalog.
  • Console: the "Show all N providers" button is gone, replaced by the always-complete grid.

Tests

  • cargo fmt --all -- --check
  • cargo clippy --locked --all-targets -- -D warnings
  • cargo clippy --locked --no-deps --features openhuman,tinycortex --all-targets -- -D warnings
  • cargo build --all-targets
  • cargo test --locked — 1854 + 10 + 1 passed
  • cargo +1.96.1 test --locked --features openhuman,tinycortex --tests — 2775 + 10 + 1 + 11 + 2 passed
  • cargo +1.96.1 test --features composio composio — 84 passed (see caveat below)
  • npm run typecheck, typecheck:unit, typecheck:e2e, npm test (315 passed), npm run build

New coverage:

  • list_catalog_toolkits_carries_the_display_metadata — the host-side regression test. The mock catalog now publishes logo/description/categories, so a mock that omitted them could not have caught this bug. Also pins first-entry-wins de-duplication and logo: None for an unpublished logo.
  • list_catalog_toolkits_falls_back_to_the_plain_allowlist now asserts slug-only CatalogEntry values.
  • Console: mapComposioCategory, providerLabel, availableCategories, filterByCategory, permissionHint, description-search, metadata-carrying, and slug-derived category guessing. visibleProviderRows now asserts the absence of a preview cut — a test still asserting one would be pinning the bug.

Added after review (drift guard on the ported mapComposioCategory):

  • keeps the buckets its OpenHuman twin produces — pins the substring table case-by-case. Not asserting that the code does what the code does: it is the diff a divergence has to survive, so editing the table without editing the list fails the suite. That is the moment the editor learns a twin exists.
  • orders its buckets so the first hit wins, not the last — both copies return on first match, so a multi-category entry depends on Chat → Social → Productivity → Platform. Reordering one side only is the subtlest available drift and was otherwise unpinned.
  • never drops a provider whose category Composio has just invented — the property the review went looking for and could not find stated outright. An unmapped category still yields a tile in "Tools & Automation" rather than a provider silently leaving the grid.
  • A notice on each copy naming the other by repository and path. The OpenHuman half is docs(composio): name the OpenCompany twin of mapComposioCategory openhuman#5496.

Verified against a running host

Not just unit-tested — driven end to end against a --features composio host pointed at a 122-provider mock catalog. The status route returned catalogSource=backend with 122 slugs and 122 metadata entries; all 122 tiles rendered with real Composio logos, connected-first; the Chat chip narrowed to 10; and "invoices" returned Stripe and QuickBooks — a description match that was impossible before this change.

Two caveats, both stated plainly

  1. server::ops::composio::tests::an_admin_is_unaffected fails on my machine, and it is pre-existing. It expects a 409 from the build check, but under --features composio the handler dials the real https://api.tinyhumans.ai, which my box can reach, so the 401 surfaces as a 502. Confirmed identical on a stashed tree. It passes in the default build, and CI does not build the composio feature.
  2. No e2e spec. The grid only renders when inBuild: true, which needs a composio-feature host, and no CI lane builds one. An e2e spec would typecheck and never run — precisely the trap playwright.config.ts documents from workflows: the toolbar picker shows neither the selected workflow nor a placeholder #406. Coverage sits in unit tests plus the host tests instead; if a composio e2e lane is wanted, that is worth its own issue.

Documentation

No doc changes needed. docs/modules/server/ does not document the Composio status DTO shape, and the module-level rustdoc in server/ops/composio.rs, server/ops/composio_toolkits.rs and company/composio.rs — which is where this surface is actually documented — is updated in place, including why CatalogEntry lives in company::composio rather than beside the agent-facing composio_catalog::CatalogToolkit.

Not in scope

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@CodeGhost21, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e8892bc7-e13d-4e0d-883b-bbc16330b445

📥 Commits

Reviewing files that changed from the base of the PR and between 9181d67 and 1f7779a.

📒 Files selected for processing (8)
  • frontend/src/api/composio.ts
  • frontend/src/lib/composio-catalog.ts
  • frontend/src/views/connections/ComposioSection.tsx
  • frontend/test/unit/composio-catalog.test.ts
  • src/company/composio.rs
  • src/harness/composio.rs
  • src/server/ops/composio.rs
  • src/server/ops/composio_toolkits.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

1 major, 1 minor. The major is sequencing with #637; the diff itself is sound.

"The layout was a symptom, not the defect" is the right read, and the evidence is the five-line map(|entry| normalize(&entry.slug)) that reduces a six-field catalog entry to a lowercased string one layer before the console. The flat list really was the honest rendering of what the frontend was handed. Pointing at the backend's own comment — that it assembles name/logo/description/categories so the frontend can read them straight from here — closes the argument.

Three decisions I checked and would keep:

  • effectiveCatalog added alongside effectiveToolkits, with OpenModeToolkits::slugs() deriving the latter, so the two cannot disagree about what is on offer. Adding a richer field beside an existing contract rather than replacing it is the right call when the old shape is still all an authorize call needs.
  • CatalogEntry living in company::composio rather than harness because the status route is always compiled while src/harness/ is behind the openhuman feature. Same reasoning #631 used to move grants_cover_server — good that the two land on the same rule independently.
  • mapComposioCategory returning null on no match so the caller falls through to the slug/name heuristic. I went looking for a provider that could silently vanish from the grid when Composio invents a category nobody mapped; the fallthrough is what prevents it. Worth keeping that property pinned if it is not already.

Major — collides with #637 on the Composio core, both directions of the stack

#637 (feat(connections): broker providers through the company's own TinyHumans credential, also yours) shares five files with this PR:

frontend/src/api/composio.ts            src/company/composio.rs
.../connections/ComposioSection.tsx     src/harness/composio.rs
                                        src/server/ops/composio.rs

Both reshape Composio resolution and the section that renders it, and neither mentions the other. #637 establishes the credential tier that decides whether a company can connect anything; this decides how the 123 options are presented. That argues for #637 first and this rebasing onto it — but it should be a stated order rather than whichever of you pushes last. I have raised the same point on #637.

Minor — mapComposioCategory is a second copy with no drift detection

It is ported from OpenHuman's mapComposioCategory in toolkitMeta.tsx, driving the same catalog from the same free-form strings, and the doc comment says keeping the two in step is cheaper than diverging. Agreed on the trade — but nothing makes a divergence visible. The two consoles will bucket the same provider differently and both will look correct in isolation.

Cheapest fix is a comment on each side naming the other as its twin, so a future edit at least knows a sibling exists. If the substring list ever grows past a handful of entries, a shared fixture asserting both map a known set identically would be worth more.

CodeGhost21 added a commit to CodeGhost21/opencompany that referenced this pull request Aug 11, 2026
…inyhumansai#600)

Review on tinyhumansai#639: `mapComposioCategory` is a second copy of OpenHuman's
`toolkitMeta.tsx` function, and nothing made a divergence visible. The
two consoles would bucket the same provider differently and both would
look correct in isolation.

There is no shared package to hoist it into, so the guard is cheap and
explicit rather than mechanical:

- A notice on this copy naming the twin by repository and path, with a
  matching one to land on the OpenHuman side.
- `keeps the buckets its OpenHuman twin produces` pins the substring
  table case-by-case. It is not asserting that the code does what the
  code does — it is the diff a divergence has to survive. Editing the
  table without editing the list fails the suite, which is the moment
  the editor learns a twin exists.
- `orders its buckets so the first hit wins` pins the branch order too.
  Both copies return on first match, so an entry carrying several
  categories depends on Chat → Social → Productivity → Platform;
  reordering one side only is the subtlest available drift.

Also pins the property the review went looking for and could not find
stated outright: a category string Composio invents tomorrow, matched by
neither the table nor the keyword heuristic, still yields a tile in
"Tools & Automation" rather than a provider silently leaving the grid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@CodeGhost21

Copy link
Copy Markdown
Collaborator Author

Stacking note (raised in review on #637): this PR and #637 share the Composio core — src/company/composio.rs, src/harness/composio.rs, src/server/ops/composio.rs, frontend/src/api/composio.ts, ComposioSection.tsx.

#637 lands first. It establishes the company-credential tier and moves the credential-tier derivation into a single company::composio::resolve_credential, which is what this grid reads connections through. This PR rebases onto it afterwards.

The overlap is adjacent rather than semantic — #637 touches credential resolution and the credential card, this one touches catalog presentation and the connector grid — so the rebase should be mechanical. Two things to pick up when rebasing:

  • ComposioCredentialSource gains a "company" variant, so any exhaustive switch on it in the grid needs the extra arm.
  • GET …/composio now reports the tier the resolver actually returns rather than re-deriving it, and can surface a store-read error instead of a confident none.

Also worth mirroring here: #637's credential card carries copy distinguishing the TinyHumans account key from the model-provider key on the Inference card (#634), since all three cards end up on this screen.

@oxoxDev

oxoxDev commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Re-checked at the new head — narrowing rather than re-blocking.

Closed, and done better than asked. I suggested a comment naming the twin; you added ## This function has a twin. Edit both. on both sides plus mapComposioCategory keeps the buckets its OpenHuman twin produces. A notice tells the next editor a sibling exists; the test tells them when they have already broken it. That is the difference between documenting a duplication and containing one.

Still open — the Major. No stated order against #637, which shares five files with this including src/harness/composio.rs, src/server/ops/composio.rs and ComposioSection.tsx. #636 shows the shape: a Merge order section naming the dependency and what rebases onto what. One paragraph and this clears.

@CodeGhost21

Copy link
Copy Markdown
Collaborator Author

Thanks — both addressed. Taking them in reverse order, since the minor is the one with code.

Minor — mapComposioCategory drift detection

You're right that the doc comment asserted the trade and then left nothing to enforce it. Fixed in c00130f with the cheap guard you suggested, plus one thing your read prompted that I'd missed:

  • A notice on each copy, naming the other by repository and path. The OpenHuman half is docs(composio): name the OpenCompany twin of mapComposioCategory openhuman#5496 (comment-only).
  • keeps the buckets its OpenHuman twin produces — pins the substring table case-by-case. It is not asserting that the code does what the code does; it's the diff a divergence has to survive. Editing the table without editing the list fails the suite, and that is the moment the editor learns a twin exists.
  • orders its buckets so the first hit wins, not the last — this is the one I hadn't considered. Both copies return on first match, so a multi-category entry depends on Chat → Social → Productivity → Platform. ["crm", "marketing"] is Social, not Platform. Reordering the branches on one side only changes real answers while every substring stays identical, which is subtler than editing the table and was completely unpinned. Now pinned on both counts.

On your third "would keep" — the null fallthrough. You went looking for a provider that could silently vanish when Composio invents an unmapped category, and it wasn't stated outright, only implied across two other tests. Now explicit as never drops a provider whose category Composio has just invented: an unmapped string still yields a tile in "Tools & Automation", availableCategories still offers that bucket, and the row survives visibleProviderRows. Good catch that it was only inferable.

I did not attempt hoisting the function into a shared package. That is a cross-repository dependency decision rather than a review nit, and it's noted as follow-up on the OpenHuman PR.

Major — sequencing with #637

Agreed, and stated: #637 lands first. There's a new ## Sequencing section at the top of the PR body. Credential-before-presentation is the right order and #637 is the older PR, so this rebases onto it rather than the other way round.

I also went further than stating it, because "should compose" is worth more as a measurement. I stacked this branch's two commits onto fix/586-brokered-company-credential locally and ran the suite. The entire overlap is two import lines:

File Conflict Resolution
src/company/composio.rs #637 adds use std::sync::Arc, this adds use serde::Serialize both kept
src/server/ops/composio.rs #637 drops token_configured (folded into resolve_credential), this adds CatalogEntry #637's import + CatalogEntry

No semantic interaction — #637 rewrites the credential half of effective_status, this touches the catalog half (effective_toolkits, fetch_catalog, the DTO). Stacked, cargo test --locked passes 1822 + 10 + 1 and --features composio passes 88, the single failure being the pre-existing network case in the PR description.

One deliberate deviation from your suggestion: I have not retargeted this PR's base to #637's branch. #637 is currently 55 commits behind main, so stacking now would run this PR's CI against a stale base and throw away the green run against current main — and any rewrite of #637 under review would churn this branch. The order is stated in prose and I'll rebase onto main and re-verify the moment #637 merges. If you'd rather have it enforced structurally by GitHub, say so and I'll retarget — that's your call, not mine, and it's a one-command change.

Nothing here alters the diff you already reviewed: effectiveCatalog is still additive beside effectiveToolkits, CatalogEntry still lives in company::composio for the always-compiled reason, and the only new code is tests.

CodeGhost21 and others added 3 commits August 11, 2026 17:59
…inyhumansai#600)

`list_catalog_toolkits` read the backend's `catalog[]` — whose entries
carry `slug`, `name`, `logo`, `description` and `categories` — and
reduced every one of them to a lowercased slug. That `Vec<String>` then
survived the cache, `OpenModeToolkits` and the status DTO unchanged, so
the console received 123 bare strings: nothing to group by, nothing to
brand with, and nothing to search but the slug. Its flat list was the
honest rendering of what it was handed.

The backend already assembles all of it and says it does so "for the
frontend"; this stops discarding it one layer short.

- `CatalogEntry` lands in `company::composio`, not the harness: the
  status route is always compiled and `src/harness/` is behind the
  `openhuman` feature. Same reason `TOKEN_KEY` lives there.
- The DTO gains `effectiveCatalog` **alongside** `effectiveToolkits`
  rather than replacing it. The slug list is the existing contract and
  is still all an authorize call needs.
- De-duplication is `or_insert_with`, not `collect()` into the map:
  collecting keeps the LAST value per key, and a duplicate catalog entry
  is typically the degenerate one — the mock's metadata-less
  `Gmail (dup)` silently blanked the real Gmail's description until this
  was explicit.
- Manifest, fallback, and pre-dynamic-catalog backends yield slug-only
  entries. That is a first-class state, not a reason to drop a provider.

Agent-side admission is untouched: `toolkit_allowed` takes slugs and
never consulted this function. This widens what is described, not what
is permitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ai#600)

123 providers rendered as one flat vertical list of name + "Sign in"
rows, twelve at a time behind a "Show all 123 providers" button. Ports
the four behaviours OpenHuman's Skills grid already uses on the same
catalog, now that the host forwards the metadata they need.

- **Category chips** over a fixed, ordered bucket list, offering only
  buckets that are actually populated — a chip that reliably yields an
  empty grid teaches the operator to distrust the whole row.
- **Buckets derived from Composio's own `categories[]`**, by substring,
  ported from `mapComposioCategory`. This is the piece worth copying
  verbatim: 123 providers bucket themselves and provider 124 does too,
  with no edit here. A slug-only entry falls through to a slug/name
  keyword heuristic rather than vanishing.
- **Search over name, slug AND description**, composed with the chip
  rather than replacing it. Matching on description is new — there was
  no description on the wire before — so "invoices" now reaches Stripe
  and QuickBooks.
- **A dense branded tile grid**, replacing the rows. The preview cut is
  deleted rather than relabelled: it was a workaround for a flat list
  being unreadable, and a grid does not need it.

The tile is the affordance — an 8.5rem tile has no room for a label and
a button both. Non-actionable tiles (connected, or a viewer who cannot
manage) render as a div, not a disabled button, so "Gmail, connected"
stays in the reading order for a member who opened this panel to learn
exactly that.

Logos are best-effort by construction — derived from the slug wherever
the backend published none — so the tile catches the 404 and falls back
to a monogram instead of a broken image.

Verified against a live host with a 122-provider mock catalog: the
Chat chip narrows to 10, "invoices" finds Stripe and QuickBooks, and
connected providers sort first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…inyhumansai#600)

Review on tinyhumansai#639: `mapComposioCategory` is a second copy of OpenHuman's
`toolkitMeta.tsx` function, and nothing made a divergence visible. The
two consoles would bucket the same provider differently and both would
look correct in isolation.

There is no shared package to hoist it into, so the guard is cheap and
explicit rather than mechanical:

- A notice on this copy naming the twin by repository and path, with a
  matching one to land on the OpenHuman side.
- `keeps the buckets its OpenHuman twin produces` pins the substring
  table case-by-case. It is not asserting that the code does what the
  code does — it is the diff a divergence has to survive. Editing the
  table without editing the list fails the suite, which is the moment
  the editor learns a twin exists.
- `orders its buckets so the first hit wins` pins the branch order too.
  Both copies return on first match, so an entry carrying several
  categories depends on Chat → Social → Productivity → Platform;
  reordering one side only is the subtlest available drift.

Also pins the property the review went looking for and could not find
stated outright: a category string Composio invents tomorrow, matched by
neither the table nor the keyword heuristic, still yields a tile in
"Tools & Automation" rather than a provider silently leaving the grid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@CodeGhost21
CodeGhost21 force-pushed the fix/600-connector-grid branch from c00130f to 1f7779a Compare August 11, 2026 12:41
@CodeGhost21

Copy link
Copy Markdown
Collaborator Author

Live end-to-end test report

Posting this because the green checks above do not cover half of this PR. CI never builds the composio feature — the header of src/harness/composio_catalog.rs states it, and --all-features is a cargo check, not a cargo test. So list_catalog_toolkits returning Vec<CatalogEntry>, the cache, and the DTO's catalog half are exercised by none of the eight passing lanes. This is what stands behind them instead.

Setup: --features composio host serving the real console bundle, OPENCOMPANY_COMPOSIO_BACKEND_URL pointed at a mock publishing 122 providers in the backend's real {success,data} envelope with logo/description/categories/enabled, five pre-connected. Real Chromium against the real host.

Host — the contract, on real data

effectiveCatalog 122 entries
effectiveToolkits 122 slugs, identical order
Additive contract slug list byte-identical to before; key order …, effectiveToolkits, effectiveCatalog, catalogSource, catalogNotice
Metadata survived 122/122 logos, 119/122 categories, 0 missing names

The 3 entries without categories are deliberate — they exercise the slug/name heuristic fallthrough rather than the categories[] path.

Host — the honesty contract, which no unit test reaches end to end

Killed the catalog backend mid-session and forced a re-fetch through the credential-write eviction path:

catalogSource   : fallback
catalogNotice   : Composio's provider catalog could not be fetched (GET …/toolkits
                  failed: error sending request…), so this is a built-in starter
                  list and may be incomplete.
effectiveToolkits: 8      effectiveCatalog: 8      all slug-only: true

Restored it, re-evicted: backend, 122 entries, notice cleared. effectiveCatalog and effectiveToolkits stayed the same length in every state — degraded, recovered, and steady. That is the invariant that would break first if the two lists ever drifted apart, and it is the reason slugs() derives one from the other rather than storing both.

Console — 20 assertions in a real browser

The defect is gone: all 122 tiles render at once, no "Show all N providers" button survives, multi-column grid.

Ordering: the five connected providers sort to the front.

Metadata reaches the tile: >100 logo images; the backend's description becomes the tile tooltip verbatim (Payments, invoices and subscriptions. on Stripe).

Buckets come from Composio's own strings: chips offer exactly the populated set; Chat narrows to the 10 messaging providers — Discord, Front, Intercom, Microsoft Teams, Slack, Telegram, Twilio, Webex, WhatsApp, Zoom — bucketed from messaging / communication / chat, never a local slug list.

Search reaches what a name cannot: "invoices" returns Stripe and QuickBooks, matched on description alone. This is the capability that did not exist before this PR — there was no description on the wire to match. "googlecalendar" still resolves by slug.

Chip AND search compose: Platform + git intersects both; Chat + git is legitimately empty and says so rather than rendering a blank grid.

Accessibility calls held: a connected tile is a div, not a disabled button, and still reads "Gmail … connected" — the member case. An unconnected tile is a button whose aria-label names what is authorised.

The action is wired: clicking a tile POSTs {"toolkit":"discord"} to …/composio/authorize.

No unexpected console errors.

One check failed first, and it was my assertion

Platform + git also returned DigitalOcean. Not a bug: "digitalocean" literally contains the substring git (d-i-git-alocean). Plain substring matching, unchanged from before this PR — the old helper did slug.includes(q) || label.includes(q) too. I corrected the expectation rather than the behaviour, since altering search semantics is outside #600's scope. Flagging it because it is mildly surprising and someone may want it as a follow-up; it is pre-existing either way.

What this does not cover

  • No automated regression guards any of the above. It was a manual run, and it will not re-run on the next change. A composio-feature CI lane is what would fix that properly — worth its own issue rather than being smuggled into this PR.
  • The mock is my own fixture, not the real Composio backend. It matches the shape ComposioToolkitCatalogEntry deserialises and the shape the existing harness mock uses, but a live-tenant run would still be worth doing before this is trusted on hosted.

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

0 major. Approving — the sequencing item is closed, and answered harder than asked.

I asked for a stated order. You added ## Sequencing — #637 lands first with the reasoning (credential tier before presentation, and #637 is the older PR), then went further and actually stacked this branch's commits onto fix/586-brokered-company-credential locally, ran the suite, and enumerated the entire overlap — two import lines in src/company/composio.rs and src/server/ops/composio.rs, with the resolution for each.

That converts "these five shared files will probably be fine" into a checked claim. It is also the answer to the thing I could not verify from outside: five shared files between two large PRs is exactly where a clean textual merge hides a semantic break, and you demonstrated it does not here rather than asserting it.

The twin notice and mapComposioCategory keeps the buckets its OpenHuman twin produces from the earlier round stand. Nothing further from me — good to merge once #637 lands.

@oxoxDev
oxoxDev merged commit 9116562 into tinyhumansai:main Aug 11, 2026
9 checks passed
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.

connections: 123 providers render as one flat list — port OpenHuman's categorised, searchable connector grid

2 participants