Skip to content

Rebuild the operator UI on design tokens and a summary-first connections page - #554

Merged
zackbart merged 4 commits into
mainfrom
operator-ui-tokens
Sep 17, 2026
Merged

zackbart merged 4 commits into
mainfrom
operator-ui-tokens

Conversation

@zackbart

@zackbart zackbart commented Sep 17, 2026

Copy link
Copy Markdown
Owner

The operator page was dense and brutalist: every connector rendered a full card
with its description, status line, permissions, OAuth actions, credential
panel, refresh button, diagnostics, drift panel, and tool list, all expanded at
once. Six connectors filled a screen and a half. This reworks the layout and
replaces the stylesheet with a token layer a deployment can theme.

Connections page

  • One summary line above the list: 4 connected · 1 needs authorization · 1 unavailable · 76 tools. The two counts an operator can act on appear only
    when they are not zero, so a healthy deployment stays short.
  • One row per connector: status dot, name, id, auth scope, tool count, state.
    Everything else is behind a row you expand.
  • The disclosure is a native <details> and not store state, so an identity
    change cannot leave one connector's panel open over another's data.
  • No cards and no shadows. A 1px border is the only thing separating a surface.
  • Same /ui/data payload, same gates, and the drift panel still refuses to
    render a tool name or a schema.

Tokens and branding.theme

browser.css resolves every color, radius, and font through a custom property
and mixes tints, hovers, and focus rings from those with color-mix, so the
new config only has to append a :root block after the stylesheet:

operatorUi({
  branding: {
    theme: {
      accent: "#7c3aed",
      radius: 4,
      fontFamily: "Inter, system-ui, sans-serif",
      colorScheme: "system",
    },
  },
})

Light and dark are the same tokens under prefers-color-scheme; colorScheme
pins one with a data-scheme attribute on <html>. Each token is gated the
way the branding URLs already were — hex color, CSS length, plain font-family
list, one of system/light/dark. The gates are narrow because these values
land in a :root block on the page, where anything unvalidated would be CSS
injection. A rejected value takes the default and createConnecta warns,
naming it.

Not done

Base Web was considered and dropped: React plus Styletron would take the
inlined operator bundle from ~85 KB to several hundred, in a UI whose whole
story is one self-contained HTML document that runs unchanged on Workers. The
bundle grew by ~5 KB instead.

Verification

npm run check passes. New coverage: theme resolution and its gates against
declaration-closing, url(), var(), comment, and unterminated-string
payloads; the rendered data-scheme attribute and token block; the startup
warning; summarizeConnectors and connectorSummaryParts including the
loading and drift cases; connectorStatusTone, authScopeLabel, and
permissionLabel. A /code-review high pass produced five findings, all fixed
in the second commit.

🤖 Generated with Claude Code

zackbart and others added 4 commits September 17, 2026 18:16
…ons page

The connections page opens with a summary strip and one line per connector;
the description, permissions, OAuth actions, credentials, diagnostics, drift,
and tool list move into a row an operator expands. Same payload, same gates.

The stylesheet resolves every color, radius, and font through a custom
property and derives the rest with color-mix, which makes `branding.theme`
(accent, radius, fontFamily, monoFamily, colorScheme) a gated `:root` block
appended after it rather than a second design system. Light and dark are the
same tokens, following the OS unless a deployment pins the scheme.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- droppedThemeTokens compared raw colorScheme to the resolved value, warning
  that a padded-but-accepted `" dark "` had been dropped.
- ConnectaTheme was unexported, so the type inside ConnectaBranding could not
  be imported from the package.
- .brand and .product are declared after the .navlink they also carry, so a
  linked masthead label is no longer muted and inset.
- Removed three class names the rewritten stylesheet no longer defines.
- A connector name is an h2 again, restoring the page's heading outline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four stat tiles become a single line — "4 connected · 1 needs
authorization · 1 unavailable · 76 tools" — with the two counts an operator
can act on shown only when they are not zero. No shadows anywhere, the gate is
a centered column instead of a panel, and the .card and .stat rules are gone
with their last callers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two findings from an independent review. `.rows` clips to its radius, so a
summary's focus ring was cropped by the container; it is inset now. And an
empty notice collapsed with `display: none`, which takes the live region out
of the accessibility tree and leaves it appearing and filling in the same
frame — it collapses to zero height instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zackbart
zackbart merged commit 3a1a567 into main Sep 17, 2026
1 check passed
@zackbart
zackbart deleted the operator-ui-tokens branch September 17, 2026 22:52
zackbart added a commit that referenced this pull request Sep 17, 2026
…e docs (#555)

Version bump across package.json, src/version.ts, the lockfile, and the Node
template's pin. The changelog entry also restores the documentation bullet that
the #554 edit dropped from Unreleased.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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