S

SideButton Knowledge Module

SideButton Portal — Capabilities — Knowledge Module

SideButton knowledge module — UI selectors, data model, and page states documenting Portal — Capabilities.

Available free v1.2.0 Browser
$ sidebutton install sidebutton
Download ZIP

PRIVACY CONVENTION: This skill file must describe the PAGE — elements, selectors, layouts, states, and public UI copy (placeholder text, button labels, default headings). It must NOT record tenant-specific data visible to a logged-in user: real ticket keys (use PROJ-123), real agent hostnames (use agent-host or {hostname}), real IP addresses (use 203.0.113.10), real workflow/automation IDs (use {id}), real user names or emails, real Jira host (use {tenant}.atlassian.net), real registry names (use {registry-name}), or real automation display names. When re-verifying, generalize observed values before writing them into this file.

SOURCE OF THIS FILE (2026-09-03): written from the shipped implementation, with route behaviour confirmed unauthenticated. Everything below marked (source) comes from the repo rather than from a browser pass; re-verify it logged in before raising confidence.

Portal — Capabilities

What This Is

The account's capability sheet: what can this fleet actually do, in six server-rendered tabs — Roles · Workflows · Playbooks · Skills · Workspace · Agents. It is the hub of the sidebar's Settings level and the home of the per-agent role and effort controls. Three older pages became tabs of it and stay reachable as deep pages (/portal/roles, /portal/workflows, /portal/skills).

Its design rule is data honesty: the page never merges what the portal asked for with what an agent reported, and it never invents an answer it does not have. That is why a delivery field says who stamped it, a declared-but-unrouted workflow verdict says so, and the two workspace-shaped tabs refuse to answer at all under All workspaces.

URL Patterns

PatternBehaviour
/portal/capabilities302 → /portal/capabilities/roles (verified unauthenticated); the query string rides along, so a ?workspace= deep link survives the hop
/portal/capabilities/{tab}The page; {tab}roles · workflows · playbooks · skills · workspace · agents
/portal/capabilities/{unknown}302 → …/roles, the same house style unknown params take elsewhere in the portal (source)
?workspace={slug}Scope override for that request; an unknown or archived slug is ignored (source)
?role={slug}Filter, honoured on the Workflows and Skills tabs only (source)

Unauthenticated, every tab 302s to /portal/login (→ Auth0). A nonexistent portal path 404s, which is how the routes above were confirmed to exist.

Page Structure

+--[Sidebar: Settings level]--+--[Main]--------------------------------+
|  < Back to fleet            |  H1: "Capabilities"                    |
|  CAPABILITIES               |  scope line: workspace or All          |
|   Capabilities   (hub)      |  +--[tab strip: 6 tabs + counts]----+  |
|     Roles         (sub)     |  |  Roles | Workflows | Playbooks   |  |
|     Workflows     (sub)     |  |  Skills | Workspace | Agents     |  |
|     Playbooks     (sub)     |  +----------------------------------+  |
|     Skills        (sub)     |  +--[panel]-------------------------+  |
|     Workspace     (sub)     |  |  fixed-layout table              |  |
|     Agents        (sub)     |  |   row  -> click -> detail row    |  |
|  AUTOMATION ...             |  +----------------------------------+  |
+-----------------------------+----------------------------------------+

The sidebar sub-items and the tab strip render the same listTABS in src/lib/portal/capabilities-nav.ts — so a sub-item click and a tab click land on one URL and both highlight. The hub item lights on a prefix match; a sub-item lights only on an exact URL match (six siblings share one prefix) (source).

Key Elements

CSS is namespaced cap-* (src/styles/capabilities.css) so nothing collides with the portal design system (source).

ElementSelector / classNotes
Tab strip.cap-tabs (renders the shared lens-pill markup)One link per tab, with a count
Panel.cap-panelSection container; panels stack
Tabletable.cap-table.cols inside .cap-twtable-layout: fixed, min-width: 960px, widths from a <colgroup> of percentages
Summary rowtr.row (tr.row.open when expanded)Click toggles; open row gets an accent inset rule
Detail rowtr.detailA colspan row rendered server-side and hidden until the summary row is clicked
Status pill.cap-pill + .ok / .warn / .bad / .mute / .runSemantic colour only
Filter chip.cap-fchip (.cur = active)Writes ?role= — a link, not client state
Button.cap-btn + .sm / .primary / .accent / .ghostColour rule below
Eligibility toggle.cap-tog (.on when granted)Roles and Agents tabs
Effort select.cap-selectAgents tab; Workflows tab has its own
Busy state.is-busy on the controlSet while a write is in flight; the control is pointer-events: none

Button colour rule (source)

ClassTokenMeans
.cap-btn.primary--sb-primary teal #19D3A6 (portal dark theme; the light-mode token is #15C39A)Create or write — Register, Edit playbook, Push config
.cap-btn.accent--sb-accent orange #FB8C3D (portal dark theme; light mode #F97316)Job intake — Run a workflow, Run now, Discover
.cap-btn / .cap-btn.ghostborder / muted foregroundNavigation only

Orange is exact — nothing but job intake is orange. Teal is not: two teal buttons only navigate (Roles page on a blocked playbook, Create a workspace on the pick-a-workspace state), so treat teal as write or create, unless its label is plainly a page name and confirm before automating a click.

Data Model

The page composes existing reads; no table is its own. Per tab (source):

TabAnswersWrites it carries
RolesThe account role registry, which agents are eligible for each role and where that set came from, guides · persona · packs behind it, and workflow-tag-only slugs as Unregistered rowsEligibility toggle → PUT /api/agents/:id/settings; RegisterPOST /api/roles (admin + trial + pack-consent gated; the toast carries the commit sha or PR link)
WorkflowsEvery agent workflow on the account — ?role= is the only row filter, so a disabled one and one nobody in scope can run stay on the list (dimmed, disabled / no agent); each one's declared verdict tokens and where they routeEffort → /api/pipelines/effort; app → /api/pipelines/agentic-app; enabled → PATCH /api/pipelines/:id (all admin-gated); Run now opens the run modal on that pipeline, Run a workflow opens it unselected → POST /api/jobs; Run now becomes a plain hint when no agent in scope may run its role
PlaybooksEvery playbook with can run here over the agents in scope, its runs, flow strip and the issue types that start it. A blocked one names why — roles missing or steps that name no role — and a disabled one reads off firstNone — the buttons are links to the editor and the library
SkillsPack overview, catalog packs, plugins, workspace skills, the base pack, and every domain of the account pack — labelled linked to {scope} / not linked here, never filtered out; ?role= filtersDiscover opens the run modal on the SD coverage workflow with the domain as input — a hint when no agent in scope may run that role, and absent entirely when the account has no SD coverage pipeline
WorkspaceThe scoped workspace's setup and what actually reached its agentsPush configPOST /api/workspaces/:id/apply; results render per agent in a toast
AgentsThe agents in scope: effective roles and their source, what was delivered, effort and appEligibility toggles and the effort override → PUT /api/agents/:id/settings

Effective roles is the rule the Roles and Agents tabs both paint: enabled_roles when that set is non-empty, else capabilities — and the row states which of the two it read. Both empty renders unrestricted, which is not the same as a grant.

Gating is per endpoint, not per page (source): the three pipeline writes are admin-gated and Register additionally needs the trial + pack-consent gate, while the agent-settings and /apply writes are not admin-gated. A non-admin sees read-only chips where a control would be.

States & Variations

StateTriggerWhat renders
Scoped to one workspaceThe workspace switcher's cookie, or ?workspace={slug}Every figure answers that workspace; the header names it
All workspacesNo workspace scopedRoles · Workflows · Playbooks · Skills answer account-wide and the header says so
Pick a workspaceAll workspaces + the Workspace or Agents tabThe switcher's own list of active workspaces as buttons, instead of guessing or showing empty rows. Agents adds the one account-wide number it can state; Workspace shows the list alone
Row expandedClick a tr.rowIts hidden tr.detail shows; one open row per table; no column header moves
Role filter active?role={slug} on Workflows or SkillsThe matching chip is .cur; each tab validates the slug against its own chips and otherwise filters nothing
Write in flightToggle / select / button clickedThe control takes .is-busy (dimmed, inert) until the response lands
Write refusedServer rejects an eligibility writeThe control reverts to the state the server printed, not to the optimistic one
Unregistered roleA workflow metadata.role tag with no registry rowAn Unregistered row with a Register button (teal)
Declared but unroutedA verdict token no playbook step routesThe row's detail labels it not routed (the chip carries the long form as a title) rather than hiding it
Delivery fieldAny row of the Workspace/Agents delivery tableLabelled stamped by the portal at push or reported by the machine at /health
Not adminNon-admin sessionRead-only chips instead of the admin-gated controls

Common Tasks

1. Grant an agent a role Capabilities → Agents (or Roles) → find the agent/role → click the eligibility toggle. This is the only place the per-agent grant is edited; Settings ▸ Agents no longer carries role chips.

2. Change an agent's effort Capabilities → Agents → the effort select in Effort & app. It writes the agent-settings override; clearing it falls back to the agent default, then the account default — the option text names what it will fall back to.

3. Register a role that only exists as a workflow tag Capabilities → Roles → the Unregistered row → Register. The row does not become runnable by itself: the registry row arrives via the pack commit and the next sync, and the fleet still has to be granted the role (task 1).

4. Run a workflow on the scoped workspace Capabilities → Workflows → Run now on the row (or Run a workflow in the header) → the run modal opens on that pipeline.

5. Push a workspace's config to its agents Capabilities → Workspace → Push config. The per-agent outcome renders in a toast; with no agent assigned the button is a link to the workspace editor instead.

6. Check what actually reached an agent Capabilities → Workspace or Agents → the delivery table. Read the stamped by label before believing a row: portal-stamped means the portal sent it, machine-reported means the agent said so at /health.

Tips

  • The URL is the whole state: tab, scope and filter are all in it, so any view is a shareable link.
  • Switching tabs keeps ?workspace=; it keeps ?role= only for the two tabs that read it.
  • Only the current tab's loader runs, so a slow tab is that tab's reads — not the page's.
  • The three deep pages (/portal/roles, /portal/workflows, /portal/skills) each carry a ← Back to Capabilities link and light their tab's sub-item in the sidebar.

Gotchas

  • /portal/capabilities is never a view — it 302s to the roles tab, and so does an unknown tab. Assert on the final URL, not the requested one.
  • The unknown-tab redirect is invisible unauthenticated: auth runs first, so /portal/capabilities/nope 302s to /portal/login. Only a logged-in request shows the tab guard.
  • Skills, Roles and Workflows are no longer sidebar items — they are tabs. Automation that clicked those sidebar links must open the tab, or the deep page URL directly.
  • The per-agent role chips and effort select are gone from Settings ▸ Agents (/portal/settings?tab=agents — the tab they were always on, a sibling of General; retired with the writes landing here) — a test that toggles a role there has nothing to click; the tab links to Capabilities → Agents instead.
  • Sub-items light on an exact URL match, the hub on a prefix — asserting "active" with a prefix match will report all six as active.
  • Opening a row must not move a column header: the table is fixed-layout with <colgroup> widths and the detail row is a colspan row. A drift here is a real regression, not a rendering quirk.
  • no guide on an Unregistered row means "not found", not "absent" — a registered role distinguishes an unreadable base pack (unknown) from a real absence; the unregistered rows do not carry that signal yet.
  • Portal API is not its own column on the Workspace tab — it is a sub-line inside the MCP cell; only the Agents tab gives it a field of its own.
  • Do not read a delivery value without its stamped by label — the page keeps requested and reported apart on purpose, and quoting one as the other is the exact confusion this page was built to end.
  • A toggle can be refused (the server validates the slug against the account registry): the control reverts to the server's answer, so read the row after a write instead of trusting the click.
  • Persona editing is not on this page yet (KAN-55 open as of 2026-09-03): the Roles detail's Write persona is a link to /portal/roles/{slug}.