Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9ae7e4f
feat(ui): rewrite the web interface on Vite, and give conversations a…
Charlesthebird Aug 26, 2026
22e01a6
fix(ui): two chat defects, and the caret where the next word goes
Charlesthebird Aug 26, 2026
d03656f
refactor: simplify UI backend support
EItanya Aug 26, 2026
c2501b6
test(ui): fix four flaky browser tests, and let CI run the suite in p…
Charlesthebird Aug 26, 2026
062d679
fix(ui): name a message the same thing on every read
Charlesthebird Aug 26, 2026
21595c1
feat(ui): put the newest conversation at the top of the agent rail
Charlesthebird Aug 26, 2026
b44a18d
refactor(ui): stop enumerating an extension's settings, and drop what…
Charlesthebird Aug 26, 2026
33eb10e
docs(ui): the substrate page is no longer the paging precedent
Charlesthebird Aug 26, 2026
3d91f00
refactor: remove session share authentication
EItanya Aug 26, 2026
d89ce6e
refactor: derive share ownership from instances
EItanya Aug 26, 2026
c37a22c
test(ui): stop the substrate specs claiming a server that no longer n…
Charlesthebird Aug 26, 2026
67e0005
refactor: rename RenameAgentInstance to UpdateAgentInstanceName
Charlesthebird Aug 26, 2026
3d7c34a
feat(ui): rename a conversation from inside it
Charlesthebird Aug 26, 2026
dd99c1b
chore: move the cluster setup script to scripts/setup-cluster
Charlesthebird Aug 26, 2026
03b3395
fix(ui): a renamed conversation updates everywhere it is shown
Charlesthebird Aug 26, 2026
520ac13
feat: forward the controller as well as the UI from setup-cluster
Charlesthebird Aug 26, 2026
06e413e
docs(ui): trim .env.example to what a reader needs
Charlesthebird Aug 26, 2026
5ab5bd1
docs(ui): say what ui/scripts is for
Charlesthebird Aug 26, 2026
005a5c6
docs(ui): say where a new dev script belongs, not just where they are
Charlesthebird Aug 26, 2026
4d5649d
docs(ui): cut the ui/scripts note back to where dev scripts live
Charlesthebird Aug 26, 2026
3e888ae
test(ui): cover the substrate page against a real controller
Charlesthebird Aug 26, 2026
0c122ce
fix(ui): put a task's messages back in the order they happened
Charlesthebird Aug 26, 2026
1884a1a
fix(ui): navigate to where the templates list is, not through the red…
Charlesthebird Aug 26, 2026
dcfd0f1
fix(ui): keep the namespace filter when returning to the templates list
Charlesthebird Aug 26, 2026
b32258b
refactor: remove Kubernetes CRUD service wrappers
EItanya Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
36 changes: 24 additions & 12 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -358,33 +358,45 @@ jobs:
uses: actions/setup-node@v7
with:
node-version-file: ui/.nvmrc
cache: "npm"
cache-dependency-path: ui/package-lock.json

# Honor the pinned npm from ui/package.json "packageManager" so npm ci
# resolves the lock file with the same npm version it was generated with.
# Before any cache step that shells out to yarn: the pinned version in
# ui/package.json "packageManager" is Yarn 4, and without corepack the shim
# on the runner is a different one that cannot read this lock file.
- name: Enable Corepack
run: corepack enable

- name: Cache Yarn downloads
uses: actions/cache@v4
with:
path: ui/.yarn/cache
key: yarn-${{ runner.os }}-${{ hashFiles('ui/yarn.lock') }}
restore-keys: yarn-${{ runner.os }}-

- name: Install dependencies
working-directory: ./ui
run: npm ci
run: yarn install --immutable

- name: Typecheck
working-directory: ./ui
run: yarn typecheck

- name: Run lint
working-directory: ./ui
run: npm run lint
run: yarn lint

- name: Run unit tests (Jest)
- name: Run unit tests
working-directory: ./ui
run: npm run test
run: yarn test

- name: Install Playwright browser (Chromium)
# Both engines the suite declares. Installing only one leaves that project
# failing to launch, which reads as a broken app rather than a missing browser.
- name: Install Playwright browsers
working-directory: ./ui
run: npx playwright install --with-deps chromium
run: yarn playwright install --with-deps chromium firefox

- name: Run Storybook tests (Vitest + Playwright)
- name: Run browser tests
working-directory: ./ui
run: npm run test:vitest
run: yarn test:pw

# This job builds the Docker images for the controller, UI, ADKs, and CLI on arm64.
build:
Expand Down
55 changes: 0 additions & 55 deletions .github/workflows/ui-chromatic.yaml

This file was deleted.

119 changes: 0 additions & 119 deletions .github/workflows/ui-playwright.yaml

This file was deleted.

79 changes: 79 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,85 @@ Common commands:
- Do not commit or push unless asked.
- Keep PRs focused. Explain non-obvious invariants and operational tradeoffs, not line-by-line implementation details.

## The web interface (`ui/`)

A Vite single-page app. It is a static bundle served by nginx: there is no server
process, so there are no server components, no server-side data fetching and no
file-system routing.

**Stack:** Vite + React 19, TypeScript, antd 6 for components, Emotion for styling
(the `css` prop, via `jsxImportSource`), SWR for reads, Yarn 4. React Router owns
routing; there is no file-system routing and no server rendering.

### Commands

Run these from `ui/`:

| Task | Command |
|------|---------|
| Dev server | `yarn dev` |
| Unit tests | `yarn test` |
| End-to-end, no cluster needed | `yarn test:pw` (Chromium and Firefox) |
| End-to-end against a real cluster | `yarn test:pw:live` |
| Type check | `yarn typecheck` |
| Lint | `yarn lint` |

Only lint **errors** gate a change; a handful of warnings are pre-existing.

`scripts/setup-cluster/setup-cluster.sh` builds a Kind cluster with kagent on it in one
command, for work that needs a real backend.

### Settings reach the app at runtime, not at build time

Configuration is read from `window.environmentVariables`, which the container
rewrites from its own environment on every start. So one image serves every
deployment, and a setting is an operator's decision rather than something frozen
into a build. Locally the same values come from `ui/.env` (git-ignored;
`ui/.env.example` documents each one).

Two consequences worth knowing before touching that code:

- The script that supplies them is **synchronous** in `index.html`. Several modules
read settings at import time, so anything awaited would be read before it arrived.
- `import.meta.env` is for build-time flags only. A value that an operator should be
able to change belongs in `window.environmentVariables`.

### Fixtures are opt-in

`ENABLE_MOCK_UI=true` serves the whole API from an in-browser mock (MSW) with no
cluster at all, and `?mock=ok|empty|error|slow` picks which scenario the fixtures
play. **It is off unless asked for**, in a dev server exactly as in a built image: a
page that quietly serves fixtures when the backend is down looks healthy while
showing data that was never real.

When mock mode is on it overrides every backend setting, and anything reporting who
is signed in correctly reports nobody β€” there is no backend to have signed in to.

### Extension points

One `VendorExtensionConfig` contributes navigation entries and overrides, routes and
route handles, slots, form fields, table columns, API overrides, providers, theme
tokens, shell regions, branding, provider icons and agent links. Components read
every colour, radius and font from those tokens, so overriding them restyles
components an extension never touches. When adding a feature, check whether it
belongs behind an extension point rather than as a branch inside a shared component.

The full guide is [ui/docs/vendor-extensions.md](ui/docs/vendor-extensions.md).

### Conventions specific to this codebase

- **Say when data is not real.** A page showing fixtures says so on the page. Never
suppress an error because a mock flag is set β€” a broken backend must not render as
healthy mock data.
- **Normalise at the client boundary.** Go marshals a nil slice as JSON `null`, so
any collection the controller has nothing for arrives as null. Fix it once where the
response is parsed, not at each use.
- **Fixtures must match the controller, not each other.** A fixture, a type and a
test can agree perfectly and all three be wrong; that has happened here more than
once and each time only a real cluster objected. Check the CRD.
- **Prefer a smaller honest test suite** over a green one that proves nothing.
Coverage debt belongs in `playwright/DEFERRED.md`, not in skipped specs.

## 9. References

- [STYLE.md](STYLE.md)
Expand Down
10 changes: 8 additions & 2 deletions go/api/database/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ type Client interface {
GetSessionShareByToken(ctx context.Context, token string) (*SessionShare, error)
ListSessionSharesBySession(ctx context.Context, sessionID string) ([]SessionShare, error)
DeleteSessionShare(ctx context.Context, token, sessionID, userID string) error
RecordShareAccess(ctx context.Context, userID string, shareID int64) error

// Agent memory (vector search) methods
StoreAgentMemory(ctx context.Context, memory *Memory) error
Expand Down Expand Up @@ -124,12 +123,19 @@ type Client interface {
CreateAgentInstance(context.Context, *apiv1alpha1.AgentInstance, string) (*apiv1alpha1.AgentInstance, bool, error)
ForkAgentInstance(context.Context, string, string, string, string, string) (*apiv1alpha1.AgentInstance, bool, error)
GetAgentInstance(context.Context, string, string, string) (*apiv1alpha1.AgentInstance, error)
ListAgentInstances(context.Context, string, string, bool, map[string]string, string, int) ([]*apiv1alpha1.AgentInstance, error)
ListAgentInstances(context.Context, AgentInstanceQuery) ([]*apiv1alpha1.AgentInstance, error)
// UpdateAgentInstanceName sets the instance's display name, scoped to its owner.
// Takes namespace, id, owner and the new name.
UpdateAgentInstanceName(context.Context, string, string, string, string) (*apiv1alpha1.AgentInstance, error)
MarkAgentInstanceReady(context.Context, string, string) (*apiv1alpha1.AgentInstance, error)
TransitionAgentInstance(context.Context, *apiv1alpha1.AgentInstance, apiv1alpha1.AgentInstanceState, apiv1alpha1.AgentInstanceOperation) (*apiv1alpha1.AgentInstance, error)
DeleteAgentInstance(context.Context, string) error
CreateAgentInstanceShare(context.Context, AgentInstanceShare) (*AgentInstanceShare, error)
ListAgentInstanceShares(context.Context, string, string, string, string, int) ([]AgentInstanceShare, error)
// GetAgentInstanceShareByTokenHash resolves a share token to its share and the
// owner of the instance it grants access to. Takes the digest, because only the
// digest is stored.
GetAgentInstanceShareByTokenHash(context.Context, []byte) (*AgentInstanceShare, error)
DeleteAgentInstanceShare(context.Context, string, string, string) error
// CreateAgentInstanceTask reserves the instance's single active-task slot.
CreateAgentInstanceTask(context.Context, string, []byte, *a2a.Task) (*a2a.Task, bool, error)
Expand Down
26 changes: 25 additions & 1 deletion go/api/database/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,14 +270,38 @@ type RuntimeRevision struct {
GoldenSnapshot string
}

// AgentInstanceQuery narrows a page of AgentInstances. Zero values mean "do not
// filter on this", so an empty query lists the caller's own instances in the
// namespace.
type AgentInstanceQuery struct {
Namespace string
UserID string
AllUsers bool
MatchLabels map[string]string
// AgentTemplate and Harness name the agent whose conversations are wanted.
// They are matched against the (AgentTemplate, Harness) pair the instance's
// prepared revision was built from, not against its labels, so they select
// instances stored before either field existed.
AgentTemplate string
Harness string
AfterID string
Limit int
}

type AgentInstanceShare struct {
ID string
Namespace string
InstanceID string
Creator string
Permission string
TokenHash []byte
CreatedAt time.Time
// OwnerUserID is the user the shared AgentInstance belongs to.
//
// Populated only by the token lookup, which joins it in β€” that is what the
// share grants. A visitor is authenticated as themselves and the token widens
// what their account may reach to what the *owner* can see, so the instance
// read has to run as the owner or it finds nothing.
OwnerUserID string
}

// AgentInstanceTaskSnapshot identifies the immutable Substrate snapshot at a
Expand Down
Loading
Loading