Skip to content

feat(ui): rewrite the web interface on Vite, and give conversations a first-class API - #2569

Open
Charlesthebird wants to merge 1 commit into
mainfrom
charlesthebird/uiRewrite
Open

feat(ui): rewrite the web interface on Vite, and give conversations a first-class API#2569
Charlesthebird wants to merge 1 commit into
mainfrom
charlesthebird/uiRewrite

Conversation

@Charlesthebird

@Charlesthebird Charlesthebird commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Rebuilds the web interface on Vite + React 19 — React Router, SWR, antd 6, Emotion. It is a static bundle behind nginx with no server process; settings come from window.environmentVariables, rewritten by the container on every start, so one image serves every deployment.

The application API is reached over gRPC-Web: grpcserver.WebHandler wraps the existing *grpc.Server, and the HTTP server routes gRPC-Web requests to it ahead of its middleware chain.

Note

Reading the diff. 616 of the 696 files are ui/, which is the rewrite's tree — read it as a new app, not as a diff. The other 80 are three things:

  • helm/ — the UI pod stops running a Next.js server and becomes nginx serving a static bundle, so nginx.conf, supervisord.conf, ui-deployment.yaml and the UI values change together with their tests.
  • go/ and proto/ — the gRPC-Web seam (grpcserver/grpcweb.go, httpserver/server.go, app.go) and the five AgentInstance changes below, plus generated proto and sqlc output.
  • CLAUDE.md and .nvmrc — the repo guide's UI section, and the pinned Node version.

Testing this PR

One command builds a Kind cluster and installs this checkout on it — controller, UI and agent runtime all built from the working tree, over the chart's published images. It ends holding a port-forward, so the last thing it prints is a working URL.

./ui/dev-scripts/setup-cluster.sh     # ~25 min, mostly image builds

http://localhost:8080

Tip

The script also leaves one agent on the cluster — an assistant template on a kagent harness — so Agents has something in it and you can send a message straight away, without creating anything first.

Warning

make create-kind-cluster && make helm-install does not work, and fails silently five different ways — including that the chart installs published images, so none of your changes are on the cluster while everything looks healthy. ui/dev-scripts/README.md covers each one, and the dev-server loop for iterating.

UI Extensions

The app declares vendor extension points anyone can use to add to it or restyle it, all in one configuration object:

Point Contributes
navItems Sidebar entries, positioned by order
navOverrides Hide, rename, re-order or redirect the app's own entries
routes Whole pages, merged into the router
routeHandles Shell data attached to the app's own routes
slots Components at named points inside existing pages
formFields Extra fields on existing forms
tableColumns Extra columns on existing tables
api Operation and endpoint overrides, payload transforms
providers App-level React providers
theme Colour, radius and font tokens
shell Whole shell regions replaced outright
branding Name and mark wherever the shell states its identity
providerIcons Provider icons for the model-config form
agentLinks Agent destinations

Installing one is two edits: build a VendorExtensionConfig, then point src/vendorExtensions/activeConfig.ts at it. Overriding theme tokens restyles the app's own components, not just the extension's.

Note

📖 ui/docs/vendor-extensions.md — every extension point and what it receives. Worth reading before reviewing the vendorExtensions/ tree.

Substrate

The pages follow the CRDs. An Agent is derived, not a resource — a Harness × AgentTemplate pair read from AgentTemplate.status.harnesses[], so there is no "New agent" button. The landing page explains the four concepts over three tabs; an agent's page lists its conversations, and a conversation is an AgentInstance.

Five additive server-side changes, none affecting an existing caller:

Change Note
AgentInstance gains a name End to end. A column, so a rename writes only the column.
ListAgentInstances takes a query Narrows to one agent, resolved through prepared_revision.
A2A gateway reads in any state A suspended conversation still opens.
A share resolves to its owner The token widens what the reader's account may read.
app.go defaults the A2A gateway Used when no extension supplies one; an extension's handler still wins.

Test Coverage

cd ui && yarn typecheck && yarn lint && yarn test && yarn test:pw
cd go && go test ./...
make proto-generate && make -C go sqlc-generate   # then confirm `git status` is clean

376 unit tests. The browser suite runs in Chromium and Firefox, plus a Chromium pass with the example extension installed.

Follow Ups

  • The browser suite runs against the dev server, not the built image and a real controller. The UI Playwright E2E workflow that did that is removed here, along with the harness it drove (playwright/scripts/setup.sh, playwright/mocks/server.mjs); restoring it is follow-up work. yarn test:pw:live covers a few journeys against a real cluster in the meantime.
  • Storybook and the Chromatic workflow are removed, since the rewrite ships no stories.
  • ui/playwright/DEFERRED.md lists the rest, and the surface each spec waits on. Nothing is committed as a skipped test.

Note

go test ./... fails one pre-existing test on macOS — TestFetchSourceReusesExistingMaterialization, /var vs /private/var, in a package this change does not touch.

Upgrade Notes

Caution

Breaking. The chart no longer sets NEXT_PUBLIC_BACKEND_URL, BACKEND_INTERNAL_URL or BACKEND_GRPC_URL, and drops ui.backendInternalUrl, ui.backendGrpcUrl and ui.volumes.nextjsCache.

The oauth2-proxy skip-auth-regex now names /assets/ and env-config.js instead of the Next.js paths, which no longer exist.

Also included, unrelated to the rewrite: helm/tools/grafana-mcp now passes -allowed-hosts. The server rejects any Host header it was not told about, so one reached over the cluster network answered the MCP handshake with Forbidden — the RemoteMCPServer sat Accepted=False and every agent using it failed to build its tool set. Found while testing the tools pages; happy to split it out if preferred.


🤖 written by Claude

@github-actions github-actions Bot added the enhancement New feature or request label Aug 25, 2026
@Charlesthebird
Charlesthebird force-pushed the charlesthebird/uiRewrite branch from 9d67c52 to 6426683 Compare August 25, 2026 23:43
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 25, 2026
@Charlesthebird
Charlesthebird force-pushed the charlesthebird/uiRewrite branch 4 times, most recently from 06e71ff to 11aaee0 Compare August 26, 2026 00:25
@Charlesthebird

Copy link
Copy Markdown
Collaborator Author
kagent-ui-crud.mp4

🤖 written by Claude

@Charlesthebird

Copy link
Copy Markdown
Collaborator Author
kagent-ui-agent-chat.mp4

🤖 written by Claude

@Charlesthebird
Charlesthebird force-pushed the charlesthebird/uiRewrite branch 3 times, most recently from 3228eee to 8264ec6 Compare August 26, 2026 01:14
… first-class API

The Next.js app is replaced by a Vite + React 19 single-page app. Routing is React
Router's, reads go through SWR, components come from antd 6 and styling from Emotion.
Settings reach the app at runtime from `window.environmentVariables` rather than being
frozen into a build, so one image serves every deployment.

The pages follow the v1alpha3 model rather than the one the old app was written for.
An agent is not a resource: it is what exists once a Harness admits an AgentTemplate,
read out of `AgentTemplate.status.harnesses[]`. The agents landing page says so, in an
overview that maps the four concepts — AgentTemplate, Harness, Agent, AgentInstance —
onto the Agent Substrate words for the same things, and its three tabs are the way to
each. An agent's own page lists its conversations, and a conversation is the chat.

Chat runs over A2A on gRPC-Web, which needed a server-side half:

- `AgentInstance` gains a name, end to end — migration, sqlc, store, proto, service,
  gRPC handler and policy — so a conversation can be called something. Additive: an
  empty name renders by id.
- `ListAgentInstances` takes a query rather than six positional arguments, and can
  narrow to one agent by template and harness. The pair is resolved through
  `prepared_revision`, so no new column is needed and rows written before the filter
  existed still match.
- The A2A gateway serves an instance in any state for reads, so a suspended
  conversation can still be opened and its transcript read.
- A share resolves to the share *and* the instance's owner, because that is what a
  share grants: the reader stays themselves, and the token widens what they may read.
- The gRPC server can hand its gRPC-Web handler to the HTTP server, so a browser
  reaches the services over the origin it was served from. The split happens outside
  the router's middleware chain: that chain is for REST-shaped handlers, and a
  gRPC-Web frame neither survives it nor needs it.

Test coverage is 376 unit tests and 88 browser tests, all against the real pages. What
could not be covered honestly is written down in `ui/playwright/DEFERRED.md` with the
surface each spec is waiting on, rather than committed as a skipped test — a skipped
spec reads as coverage and that list does not.

`ui/dev-scripts/` builds a Kind cluster with kagent on it in one command, for anyone
who wants to try the app against a real backend. Without a cluster, `ENABLE_MOCK_UI=true`
serves every page from in-browser fixtures, and each such page says on itself that the
data is not real.

Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
@Charlesthebird
Charlesthebird force-pushed the charlesthebird/uiRewrite branch from 8264ec6 to ec60b82 Compare August 26, 2026 01:50
@Charlesthebird
Charlesthebird marked this pull request as ready for review August 26, 2026 02:21
@Charlesthebird
Charlesthebird requested review from a team and peterj as code owners August 26, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant