Skip to content

[codex] Add runtime stream tree explorer#1443

Merged
jonastemplestein merged 1 commit into
mainfrom
sore-function
Jun 10, 2026
Merged

[codex] Add runtime stream tree explorer#1443
jonastemplestein merged 1 commit into
mainfrom
sore-function

Conversation

@jonastemplestein

@jonastemplestein jonastemplestein commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Replaced the project streams table with a lazy runtime-state stream tree rooted at /.
  • Added reusable stream tree browsing UI backed by StreamState.childPaths.
  • Switched stream breadcrumbs children/sibling navigation to read from stream reduced state instead of streams.list.
  • Added admin stream explorer routes under /admin/streams with a namespace selector and detail views.
  • Added an admin-only stream RPC endpoint for arbitrary namespaces so admin stream detail pages can reuse the existing stream viewer.

Why

Stream navigation should reflect the runtime stream model directly. Child streams already live in core reduced state, so the UI no longer needs a project-wide stream list or fake D1-style stream timestamps.

Validation

  • pnpm --dir apps/os typecheck
  • pnpm lint
  • pnpm --dir apps/os test

Note

Medium Risk
Adds admin-only cross-namespace stream RPC and itx streams.namespace; misconfiguration could expose arbitrary namespaces, though both paths require admin credentials.

Overview
Stream navigation now follows runtime reduced state (StreamState.childPaths) instead of a project-wide streams.list() inventory.

The project streams index drops the sortable table, filter/create flow, and D1-style metadata in favor of a lazy StreamTreeBrowser rooted at /. Breadcrumbs use the same model: sibling and child pickers read childPaths from parent/current stream state via new useProjectStreamState, and client-side isImmediateChild filtering is removed.

ProjectStreamView gains optional streamUrl and renderStreamPathLink so admin pages can reuse the feed viewer with non-project RPC URLs and links.

For operators, /admin/streams adds a namespace picker, per-namespace tree views, and a split tree + stream detail layout. Backend support includes itx.streams.namespace(...) (admin access only), /api/admin-streams/<namespace>/… Cap'n Web RPC gated by the admin cookie, and worker dispatch for that route.

Reviewed by Cursor Bugbot for commit ae6e4cc. Bugbot is set up for automated code reviews on this repo. Configure here.

Environment Config Lease

No active environment config lease.

OS

Status: released
Commit: ae6e4cc
Preview: https://os.iterate-preview-3.com
Summary: Preview app released.
Workflow run
Updated: 2026-06-10T13:42:03.663Z

Semaphore

Status: released
Commit: ee0579d
Preview: https://semaphore.iterate-preview-3.com
Summary: Preview app released.
Workflow run
Updated: 2026-06-10T13:41:50.532Z

@jonastemplestein jonastemplestein marked this pull request as ready for review June 10, 2026 12:44

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2634591. Configure here.

Comment thread apps/os/src/components/stream-tree-browser.tsx
@jonastemplestein jonastemplestein force-pushed the sore-function branch 2 times, most recently from b8bf075 to ee0579d Compare June 10, 2026 13:10
@jonastemplestein jonastemplestein merged commit c5f55be into main Jun 10, 2026
9 checks passed
@jonastemplestein jonastemplestein deleted the sore-function branch June 10, 2026 13:40
jonastemplestein added a commit that referenced this pull request Jun 10, 2026
…etchItxQuery (#1457)

## Problem

Converted dashboard routes (streams index) fetch only via `useItxQuery`
over the browser's itx WebSocket — route loaders lost their
`ensureQueryData` prefetch, so every visit painted a first-visit
spinner.

## What this adds (itx DECISIONS **D18**)

- **`src/itx/access.ts`** — `accessForPrincipal` /
`resolveAccessibleContextId` / `requireWorkerExports` extracted from
`fetch.ts` (no behavior change; `resolveAccessibleContextId` now takes
`db` instead of the whole `RequestContext`). The `/api/itx` door and the
new SSR door share one auth boundary, so they cannot drift.
- **`src/itx/server.ts` — `getServerItx(projectSlugOrId,
requestContext?)`**: an in-process project-narrowed `Itx` handle built
from the request context (`principal` from the auth request middleware,
`env` from `cloudflare:workers`, `workerExports` from the context),
mirroring exactly what `fetch.ts` does at WebSocket connect time minus
the transport. Throws with the kernel's no-existence-probing "not found"
wording when the principal may not hold the project.
- **`src/itx/loader.ts` — `getLoaderItx`**: isomorphic accessor in the
`orpc/client.ts` shape. Server branch dynamically imports `server.ts`
(so `cloudflare:workers`/db never enter the browser graph even before
the Start compiler strips the branch); browser branch reuses the per-tab
socket singleton, moved to `src/itx/react/browser-client.ts` so loaders
share the hooks' socket and project-handle cache without importing
React. Explicit return types, non-route module — no routeTree Register
cycles.
- **`prefetchItxQuery`** (same module): best-effort `ensureQueryData`
that catches and discards every failure. Prefetch is an optimization,
never a gate — this is the guard against the prod incident where a
FORBIDDEN thrown during route loading crashed the streams page into the
generic error boundary. The component's `useItxQuery` re-surfaces the
same error inline.
- **`lib/itx-queries.ts`** restructured: each piece of data has ONE
`ItxQueryDefinition` (`{project, queryKey, queryFn, staleTime}`)
consumed by both the React hook and loader prefetches.
- **Exactly one route wired**: the streams index loader seeds the **root
stream state**. Breadcrumbs stay lazy by deliberate choice; they're
seeded for free via the shared per-path cache keys.

### Note: adaptation to main's drift

The original plan said to prefetch `useProjectStreamsList`. Since then
the streams index was rewritten around `StreamTreeBrowser`
(#1443/#1449), whose first paint gates on the **root stream state**, not
the list — and its inline source used ad-hoc keys (`["project", id,
"streams", ...]`) that didn't share cache with the breadcrumbs'
`itxKey`-based entries. So the loader prefetches
`projectStreamStateQuery(root)`, and the route's tree source now keys on
`itxKey.project(id, "streams")` — tree nodes, breadcrumb navigators, and
the prefetch all land on `projectStreamStateKey(...)` per path.
`useProjectStreamsList` keeps its single shared definition.

### DECISIONS numbering

The new entry is **D18** (next free number at time of writing). If
another in-flight PR also claims D18, renumber on merge.

## SSR seeding verification

`router.tsx` wires `setupRouterSsrQueryIntegration({ router,
queryClient, wrapQueryClient: true })`, which dehydrates every query
seeded during SSR loaders and hydrates it client-side — the same
machinery the established `ensureProjectBySlug` beforeLoad pattern
already relies on. Nothing there needed changing; the loader awaits
`prefetchItxQuery`, so the entry is in the cache before dehydration.

## Tests

- `src/itx/loader.test.ts` — prefetch seeds the cache through the
resolved handle; swallows handle-resolution failures (no principal /
forbidden, leaving component-visible error state in cache); swallows
kernel queryFn failures; `getLoaderItx` server-branch wiring.
- **New worker harness** `src/durable-objects/itx-server-handle.*`
(`pnpm test:itx-server-handle`, same family/convention as
`itx-stream-subscribe.*`): hand-built `RequestContext` (principal + real
D1 projects row + `ctx.exports`) → `getServerItx` →
`itx.streams.list()`/`append()` against a **real Stream Durable Object**
through the StreamsCapability loopback. Covers member by slug + id,
admin, stranger (denied), anonymous (denied), unknown slug (denied) —
the insufficient-principal SSR path is exercised here and in the unit
suite.
- `pnpm vitest run src/itx/react` — 22 tests green (plus loader tests).
- Repo root: `pnpm typecheck && pnpm lint && pnpm format && pnpm test`
all green.

Docs: D18 in `src/itx/DECISIONS.md`, SSR section of
`docs/itx-orpc-replacement-plan.md` marked done,
`tasks/os-orpc-teardown.md` checkbox ticked.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches authentication boundaries for SSR and /api/itx (shared module)
and changes loader error handling; well-covered by new harness and
loader tests, but auth drift or silent prefetch failures could affect
first paint or error UX.
> 
> **Overview**
> Adds **SSR/loader prefetch** for itx so TanStack routes can seed React
Query without opening a WebSocket, restoring the prefetch behavior lost
when streams moved off oRPC.
> 
> **Shared auth boundary** — `access.ts` centralizes
`accessForPrincipal`, `resolveAccessibleContextId`, and
`requireWorkerExports`; `/api/itx` in `fetch.ts` and the new SSR path
both use it so connect-time and loader-time access cannot drift.
> 
> **In-process SSR handle** — `getServerItx` builds a project-narrowed
`Itx` from request context via the same resolve chain as WebSocket
connect (no capnweb). **`getLoaderItx`** is the isomorphic entry (server
→ dynamic `getServerItx`; client → shared tab singleton in
`browser-client.ts`). **`prefetchItxQuery`** runs `ensureQueryData` but
**swallows all errors** so forbidden/missing projects do not blow up
route loaders (prod streams error-boundary fix).
> 
> **Query definitions** — `ItxQueryDefinition` in `lib/itx-queries.ts`
is shared by hooks and loaders; the **streams index** loader prefetches
root stream state and aligns tree `itxKey` usage with breadcrumbs.
> 
> **Tests** — Unit tests for loader prefetch contract; new
`test:itx-server-handle` worker harness proving `getServerItx` → real
Stream DO + auth denial cases. Minor e2e timeout/assertion tweaks.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4a96cca. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- CLOUDFLARE_PREVIEW -->
## Environment Config Lease
<!-- CLOUDFLARE_PREVIEW_STATE -->
<!--
{
  "apps": {
    "os": {
      "appDisplayName": "OS",
      "appSlug": "os",
      "status": "deployed",
      "updatedAt": "2026-06-10T17:57:50.268Z",
      "headSha": "4a96cca6c8bd77ec2ad5dc15db252310591bf1d8",
      "message": null,
      "publicUrl": "https://os.iterate-preview-4.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27295319125",
      "shortSha": "4a96cca"
    },
    "semaphore": {
      "appDisplayName": "Semaphore",
      "appSlug": "semaphore",
      "status": "deployed",
      "updatedAt": "2026-06-10T17:56:32.669Z",
      "headSha": "4a96cca6c8bd77ec2ad5dc15db252310591bf1d8",
      "message": null,
      "publicUrl": "https://semaphore.iterate-preview-4.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27295319125",
      "shortSha": "4a96cca"
    }
  },
  "environmentConfigLease": {
    "dopplerConfig": "preview_4",
    "leasedUntil": 1781117677160,
    "leaseId": "a51db8c6-cf17-4ffe-b115-af704299778a",
    "slug": "preview-4",
    "type": "environment-config-lease"
  }
}
-->
<!-- /CLOUDFLARE_PREVIEW_STATE -->
Lease: `preview-4`
Doppler config: `preview_4`
Type: `environment-config-lease`
Leased until: 2026-06-10T18:54:37.160Z

### OS
Status: deployed
Commit: `4a96cca`
Preview: https://os.iterate-preview-4.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27295319125)
Updated: 2026-06-10T17:57:50.268Z

### Semaphore
Status: deployed
Commit: `4a96cca`
Preview: https://semaphore.iterate-preview-4.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27295319125)
Updated: 2026-06-10T17:56:32.669Z
<!-- /CLOUDFLARE_PREVIEW -->

---------

Co-authored-by: Claude Fable 5 <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