Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
398dfaf
chore(web): chat/wire comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
478c416
chore(web): chat top-level comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
2ba0cc7
chore(web): chat turn/tool activity comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
9746b2d
chore(web): more chat comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
f0c5fe4
chore(web): chat block comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
1d3717e
chore(web): shell comments say why, not what, part 1 (CL-8378)
TheGreatAxios Sep 18, 2026
7343973
chore(web): shell comments say why, not what, part 2 (CL-8378)
TheGreatAxios Sep 18, 2026
609c0cf
chore(web): shell/layout comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
baa17a1
chore(web): shell context-menu comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
1c3f683
chore(web): settings comments say why, not what, part 1 (CL-8378)
TheGreatAxios Sep 18, 2026
0259193
chore(web): settings comments say why, not what, part 2 (CL-8378)
TheGreatAxios Sep 18, 2026
fda59b3
chore(web): pages comments say why, not what, part 1 (CL-8378)
TheGreatAxios Sep 18, 2026
96852ad
chore(web): pages comments say why, not what, part 2 (CL-8378)
TheGreatAxios Sep 18, 2026
4fdfb85
chore(web): insights page comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
2cdec66
chore(web): skill/library page comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
17f644b
chore(web): remaining pages comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
2034810
chore(web): pages cleanup pass (CL-8378)
TheGreatAxios Sep 18, 2026
2150d92
chore(web): cleanup pass on chat/shell comment overflows (CL-8378)
TheGreatAxios Sep 18, 2026
ad8a9fd
chore(web): lib/api-query comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
e44a55e
chore(web): lib icons/client-log comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
321cb9b
chore(web): library comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
ef33581
chore(web): command palette top-level comments say why, not what (CL-…
TheGreatAxios Sep 18, 2026
1097e3c
chore(web): command palette remaining comments say why, not what (CL-…
TheGreatAxios Sep 18, 2026
a28f269
chore(web): query-client comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
03068b2
chore(web): api.ts comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
496b4e2
chore(web): session.ts comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
72a7a51
chore(web): routes.tsx comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
3e7e0e4
chore(web): onboarding.ts comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
53e9a66
chore(web): needs-converge.ts comments say why, not what (CL-8378)
TheGreatAxios Sep 18, 2026
62a64da
chore(web): trim remaining file-header and inline comments (CL-8378)
TheGreatAxios Sep 18, 2026
0f470f6
chore(web): trim comments in insights/agent-source/routines files (CL…
TheGreatAxios Sep 18, 2026
3296cfc
chore(web): trim comments in test-infra and workbench-path files (CL-…
TheGreatAxios Sep 18, 2026
1de6d3d
chore(web): trim comments in router/insights/navigation files (CL-8378)
TheGreatAxios Sep 18, 2026
e9d1197
chore(web): trim comments in path-ids and bench-context files (CL-8378)
TheGreatAxios Sep 18, 2026
a9f6cb6
chore(web): trim comments in insights-path/library-scope/dom/myra fil…
TheGreatAxios Sep 18, 2026
61f39de
chore(web): trim comments in command-palette-provider and threads (CL…
TheGreatAxios Sep 18, 2026
05e173f
chore(web): trim remaining 4-line file headers (CL-8378)
TheGreatAxios Sep 18, 2026
5414b81
chore(web): trim comments in skills/skill-source/last-workbench files…
TheGreatAxios Sep 18, 2026
b920091
chore(web): trim comments in myra-deploy and library-upload files (CL…
TheGreatAxios Sep 18, 2026
523721c
chore(web): trim comments in agents-api/activatable-row/auto-title fi…
TheGreatAxios Sep 18, 2026
882eb9e
chore(web): trim comments in git-push and root files (CL-8378)
TheGreatAxios Sep 18, 2026
5a20678
chore(web): trim comments in client-bootstrap.ts (CL-8378)
TheGreatAxios Sep 18, 2026
348e618
chore(web): trim comments in pending-approvals.ts (CL-8378)
TheGreatAxios Sep 18, 2026
ff88c3a
chore(web): trim comments in insights-api.ts (CL-8378)
TheGreatAxios Sep 18, 2026
064264d
chore(web): trim comments in agent-deploy.ts (CL-8378)
TheGreatAxios Sep 18, 2026
a47c903
chore(web): trim comments in workbench-create/needs-list/bench-contex…
TheGreatAxios Sep 18, 2026
01b9aa2
chore(web): trim comments in navigation/insights-deeplinks/redirect/s…
TheGreatAxios Sep 18, 2026
b331e8d
chore(web): trim comments in insights-scope/login-routing/chat-artifa…
TheGreatAxios Sep 18, 2026
855fd2b
chore(web): trim comments in bench/tenancy-contracts and deployed-too…
TheGreatAxios Sep 18, 2026
5b9dafc
chore(web): trim comments in auth/ files (CL-8378)
TheGreatAxios Sep 18, 2026
3b21837
chore(web): trim comments in shell/command-palette/lib/pages files (C…
TheGreatAxios Sep 18, 2026
1b13a7e
chore(web): trim last remaining comment blocks (CL-8378)
TheGreatAxios Sep 18, 2026
1b93371
chore(web): fix formatting after comment trims (CL-8378)
TheGreatAxios Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions apps/web/src/action-command-dispatch-race.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
// Regression test for the off-route create-dialog race: firing a palette
// action command ("New skill") from a page other than its target route used
// to dispatch the shared "create" event synchronously, then navigate — but
// the target page's window-event listener only registers once it mounts,
// which happens on the next render after navigate's setState. The event
// fired and was gone before anyone was listening.
//
// runActionCommand now goes through a pending-flag (pending-dialog-request.ts,
// the same pattern library-upload.ts already used for "Upload artifact"):
// off-route, it records the flag and navigates; the target page consumes it
// on mount instead of relying on a same-tick dispatch.
// Regression: firing a palette action off-route used to dispatch a "create"
// event before the target page's listener mounted. Now it goes through a
// pending-flag (pending-dialog-request.ts) the target page consumes on mount.

import { afterEach, describe, expect, test } from "bun:test";
import { act } from "react";
Expand Down Expand Up @@ -50,11 +42,8 @@ describe("runActionCommand off-route dispatch ordering", () => {
});
expect(navigated).toEqual(["/skills"]);

// Only now (mirroring main.tsx's setState-based navigate re-rendering
// the route switch on the next tick) does the section actually mount.
// The section reads the stock skill-asset list on mount; serve it an
// empty one so the test exercises the pending-flag path, not a
// network failure.
// Serve an empty skill-asset list so the test exercises the
// pending-flag path, not a network failure.
globalThis.fetch = (async () =>
new Response(JSON.stringify([]), {
status: 200,
Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/activatable-row.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { afterEach, describe, expect, test } from "bun:test";

import { isAdditiveSelectClick, isRowActivationKey, rowActivationProps } from "./activatable-row";

// `isAdditiveSelectClick`'s Mac/non-Mac branch reads `navigator.platform`,
// which happy-dom's `GlobalRegistrator` reports as whatever the *host* OS
// is — Darwin-flavored on a Mac, something else on Linux CI. Each test
// below pins the platform it means to exercise instead of inheriting the
// host's, so both branches are deterministic on any OS.
// happy-dom reports `navigator.platform` as the host OS, so each test pins
// the platform it means to exercise to stay deterministic on any OS.
const originalPlatform = Object.getOwnPropertyDescriptor(navigator, "platform");

function stubPlatform(platform: string): void {
Expand Down
17 changes: 4 additions & 13 deletions apps/web/src/activatable-row.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
// A clickable `TableRow` (library, agents, skills) needs the same three
// things to be keyboard-operable: an accessible role, a tab stop, and
// Enter/Space activation. Pure so the "which keys activate a row" rule is
// testable without rendering a table.
// Pure so "which keys activate a row" is testable without a table render.
import type { KeyboardEvent } from "react";

export function isRowActivationKey(key: string): boolean {
return key === "Enter" || key === " ";
}

/**
* Whether a click's modifiers mean "add this row to the selection" rather
* than "activate/replace". Cmd-click is the additive gesture on every
* platform; Ctrl-click only joins in on non-Mac, because on Mac Ctrl-click
* is the native context-menu gesture — the browser can fire `click` and
* `contextmenu` from the same physical click, and treating Ctrl as additive
* there would silently toggle the very row the context menu is about to
* act on.
*/
// Ctrl-click is additive only on non-Mac; on Mac it's the context-menu
// gesture, and treating it as additive there would toggle the row the
// context menu is about to act on.
export function isAdditiveSelectClick(event: {
readonly metaKey: boolean;
readonly ctrlKey: boolean;
Expand Down
36 changes: 9 additions & 27 deletions apps/web/src/agent-deploy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
// Deploys a hand-authored agent the same way Myra deploys herself
// (`myra-deploy.ts`): a `workflow`-kind asset holding a rendered source
// tree, pushed over the stock git smart-HTTP route, then deployed through
// the stock `POST /workflows/deployments`. Generalized over {name,
// displayName, systemPrompt} so the create-agent panel can deploy any
// agent through the one path the platform actually backs.
// (`myra-deploy.ts`), generalized over {name, displayName, systemPrompt}.
import { renderBundledWorkflowSourceTree } from "@corbits/workflows/client";
import { type } from "arktype";
import { WorkflowDeploymentResponse } from "@intx/types";
Expand Down Expand Up @@ -156,10 +152,8 @@ async function withPushToken<T>(
}
}

/** Renders this agent's built definition as the same bundled source tree
* Myra deploys (`pushMyraSource`) — the bundle's `buildMyraWorkflow` is
* generic over which agent it builds — and pushes it to the asset's `main`.
* Returns the commit sha the deploy pins to. */
// The bundle's `buildMyraWorkflow` is generic over which agent it builds.
// Returns the commit sha the deploy pins to.
export async function pushAgentSource(
tenantId: string,
assetId: string,
Expand Down Expand Up @@ -219,11 +213,8 @@ export function isAgentDeploySourceAssetName(name: string): boolean {
return AGENT_DEPLOY_SOURCE_ASSET_NAME.test(name);
}

/** The inverse of `agentDeploySourceAssetName`: recovers the slug this
* pipeline deployed an asset under, so a caller re-deploying an existing
* agent can reuse its slug instead of re-deriving one from its display
* name. Null when the name isn't this pipeline's `agent-<slug>-source`
* shape. */
/** Lets a caller re-deploying an existing agent reuse its slug instead of
* re-deriving one from its display name. */
export function agentSlugFromSourceAssetName(assetName: string): string | null {
const match = AGENT_DEPLOY_SOURCE_ASSET_NAME.exec(assetName);
return match?.[1] ?? null;
Expand Down Expand Up @@ -257,12 +248,8 @@ export function buildScheduledRunBody(deployerAddress?: string): string {
return `This is your scheduled run. ${task} Mail the result to ${deployerAddress} (pass it as a single-item \`to\` list) with a short, descriptive subject.`;
}

/** The deploying person's mailbox address — same source `session.ts`'s
* `fetchSession` reads, same shape `threads-api.ts` builds a person
* participant's address from (`<refId>@<tenantDomain>`). Best-effort: a
* session probe that fails or comes back signed-out just means the
* scheduled run's body falls back to naming nobody, never a failed
* deploy over it. */
// Best-effort: a failed or signed-out session probe just means the
// scheduled run's body falls back to naming nobody, never a failed deploy.
async function resolveDeployerAddress(
tenantDomain: string,
fetchImpl: typeof fetch,
Expand Down Expand Up @@ -316,13 +303,8 @@ async function scheduleAgentRun(

export type DeployedAgent = typeof WorkflowDeploymentResponse.infer;

/**
* Deploys a hand-authored agent: ensures its source asset, pushes its
* rendered definition, resolves the tenant's existing inference offering
* (the same one Myra's own deploy resolves through), and deploys through
* the stock `POST /workflows/deployments`. Fails closed when no provider is
* connected yet — there is no offering to deploy against.
*/
// Fails closed when no provider is connected yet — there is no offering
// to deploy against.
export async function deployAgentSource(
args: { readonly tenantId: string; readonly input: NewAgentInput },
fetchImpl: typeof fetch = fetch,
Expand Down
18 changes: 5 additions & 13 deletions apps/web/src/agent-source-read.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
// Reads an existing agent's deploy source back out of its workflow asset,
// so the new-workbench picker can re-push the same definition into a
// child tenant (agent-deploy.ts already generalizes that push+deploy).
// There is no stock file-read route for a workflow asset (only
// No stock file-read route exists for a workflow asset (only
// package-registry tarballs get one), so this fetches `main` over the
// asset's smart-HTTP git remote with a short-lived read-only token.
import {
Expand Down Expand Up @@ -44,10 +41,8 @@ export type AgentToolPackagePin = { readonly name: string; readonly version: str

type AgentWorkflowStep = (typeof AgentWorkflowJsonShape.infer)["steps"][string];

/** Mints a read-only token, fetches the asset's `main` over its smart-HTTP
* git remote, and parses `definition.json` out of it. Shared by every
* reader below so each mints and revokes its own short-lived token rather
* than holding one open across a batch of assets. */
// Each reader mints and revokes its own short-lived token rather than
// holding one open across a batch of assets.
async function readAgentWorkflowStep(
tenantId: string,
assetId: string,
Expand Down Expand Up @@ -101,11 +96,8 @@ export async function readAgentSource(
};
}

/** The tool packages an agent's own step pins in `definition.json`. Empty
* for an agent whose tools ride bundled into its `workflow.js` closure
* instead (Myra's mail/posix factories never surface here — see
* `deployed-tool-packages.ts`'s `MYRA_TOOL_PACKAGES`, derived from
* `@corbits/myra/package.json`'s own dependencies). */
/** Empty for an agent whose tools ride bundled into its `workflow.js`
* closure instead — see `deployed-tool-packages.ts`'s `MYRA_TOOL_PACKAGES`. */
export async function readAgentToolPackagePins(
tenantId: string,
assetId: string,
Expand Down
34 changes: 8 additions & 26 deletions apps/web/src/agents-api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
// The hub seams this app still needs for agents: definitions (for the
// sidebar's own-agent-DM listing in `shell/bench-activity.ts`), top-level
// runs (for `chat/threads-api.ts`'s live-address resolution), and the
// create-agent deploy mutation. The Agents page itself now reads through
// `chat/threads-api.ts`'s `listChatAgents` — the same chat-partner listing
// — so this file no longer carries a roster/detail data model of its own.
// The Agents page reads through `chat/threads-api.ts`'s `listChatAgents`;
// this file carries no roster/detail data model of its own.

import { WorkflowDefinitionResponse, WorkflowRunResponse, paginatedSchema } from "@intx/types";
import { type } from "arktype";
Expand All @@ -21,10 +17,8 @@ export type AgentInstance = typeof WorkflowRunResponse.infer;
const DefinitionsPage = paginatedSchema(WorkflowDefinitionResponse);
const InstancesPage = paginatedSchema(WorkflowRunResponse);

// The REST pagination ceiling (see `vendor/intx/hub-api/src/pagination.ts`).
// A bench with more agents or instances than this needs real pagination on
// this page, not raised here — tracked as a known limit, not silently
// worked around.
// The REST pagination ceiling; a bench past this needs real pagination,
// not a raised limit here.
const PAGE_LIMIT = 100;

type Validator<T> = (data: unknown) => T | ArkErrors;
Expand Down Expand Up @@ -60,28 +54,16 @@ export function listAgentDefinitions(tenantId: string): Promise<readonly AgentDe
).then((page) => page.data);
}

/**
* The tenant's genuine top-level deployment runs — every non-top-level
* run (workbench host, invited agent, task) excluded server-side by the
* native `GET /workflows/runs` listing's own predicate (`address IS NOT
* NULL AND anchorRunId = id`), the same one `isTopLevelRun` uses.
* Single-tenant (accepted loss): the deleted route expanded the
* requested tenant to its whole descendant subtree via
* `getDescendantTenants`; the native listing filters one tenant, so a
* workspace parent sees only its own runs, not its child workbenches'.
*/
// Single-tenant (accepted loss): the native listing filters one tenant, so
// a workspace parent sees only its own runs, not its child workbenches'.
export function listTopLevelRuns(tenantId: string): Promise<readonly AgentInstance[]> {
return getJSON(`/api/tenants/${tenantId}/workflows/runs?limit=${PAGE_LIMIT}`, InstancesPage).then(
(page) => page.data,
);
}

/**
* Deploys a hand-authored agent through the stock workflow-deploy path
* (`agent-deploy.ts`), then invalidates the bench's agent directory and
* chat-agent roster so both pick up the new deployment without a manual
* refetch.
*/
// Invalidates the bench's agent directory and chat-agent roster so both
// pick up the new deployment without a manual refetch.
export function useDeployAgentMutation(tenantId: string) {
const queryClient = useQueryClient();
return useMutation({
Expand Down
64 changes: 15 additions & 49 deletions apps/web/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// The interface's one seam to the hub: relative /api paths on the origin the
// bundle was served from, validated at the boundary with the platform's own
// response schemas so a shape change surfaces as an error state, never as
// undefined leaking into a page.
// Every response is validated at the boundary with the platform's own
// schemas, so a shape change surfaces as an error state, never `undefined`.

import {
ApprovalResponse,
Expand All @@ -25,11 +23,8 @@ export const PrincipalsSchema = paginatedSchema(PrincipalSummary);
export const TenantApprovalsSchema = paginatedSchema(ApprovalResponse);
export const TenantDetailSchema = TenantResponse;

// `GET /api/tenants/:tenantId/assets` returns a bare array of
// `AssetWithOriginResponse` rows (not the paginated envelope), so the schema
// validates the array directly. These tenant assets — workflows, skills,
// package registries, agent state — are the real, listable store the Library
// page renders as artifacts.
// Returns a bare array, not the paginated envelope, so this validates the
// array directly.
export const AssetsSchema = AssetWithOriginResponse.array();

// Real Library plane: paginated list from GET /api/tenants/:id/artifacts.
Expand Down Expand Up @@ -83,26 +78,17 @@ export type ArtifactListItem = typeof ArtifactListItemSchema.infer;
export type ArtifactListPage = typeof ArtifactListPageSchema.infer;
export type ArtifactDetail = typeof ArtifactRowSchema.infer;
export type ArtifactCounts = typeof ArtifactCountsSchema.infer;
/**
* The envelope paginatedSchema validates, stated structurally: the generic
* schema's inferred type carries an arktype inference artifact that rejects
* plain literals, so pages and tests use this equivalent shape instead.
*/
// Stated structurally because the generic schema's inferred type rejects
// plain literals.
type Paginated<T> = { data: T[]; nextCursor: string | null };
export type PrincipalsPage = Paginated<Principal>;

/** An arktype schema, seen as the validating call every `Type` provides. */
type Validator<T> = (data: unknown) => T | ArkErrors;

/**
* Fetches one hub endpoint and reports exactly what happened: loading, no
* session (401), a failure, or validated data. Pass a module-level schema so
* identity stays stable; the schema never enters the query key.
*
* Empty paths are disabled and never fetch — the boundary owns the gate so
* call sites that still pass `""` when a tenant is unresolved cannot hit
* the network with a broken URL.
*/
// Pass a module-level schema so identity stays stable. Empty paths are
// disabled and never fetch, so a call site with an unresolved tenant
// can't hit the network with a broken URL.
export function useAPIQuery<T>(
path: string,
schema: Validator<T>,
Expand Down Expand Up @@ -133,11 +119,7 @@ export function useAPIQuery<T>(
return toAPIQuery(result);
}

/**
* A one-shot POST against a hub route, parsed the same way `useAPIQuery`
* parses its GETs: loud on a non-2xx status and on a response shape that
* doesn't match the schema, never a silent fallback.
*/
// Loud on a non-2xx status or a shape mismatch, never a silent fallback.
async function postJSON<T>(path: string, schema: Validator<T>, body: unknown): Promise<T> {
let response: Response;
try {
Expand Down Expand Up @@ -184,13 +166,8 @@ export function rejectApproval(
);
}

/**
* One-shot fetch of `GET /api/tenants/:id` — the only place `parentId`
* comes from. A bench is a top-level tenant (`parentId === null`); a workbench
* is a named child tenant, so the raw-id/name heuristic can never tell them
* apart. `bench-context.tsx` fans this out per membership with
* `useQueries` to decide which memberships are benches.
*/
// The only place `parentId` comes from — a bench is a top-level tenant,
// so a raw-id/name heuristic can never tell it apart from a workbench.
export async function fetchTenantDetail(tenantId: string): Promise<TenantDetail> {
const response = await fetch(`/api/tenants/${encodeURIComponent(tenantId)}`, {
headers: { accept: "application/json" },
Expand All @@ -209,24 +186,13 @@ export async function fetchTenantDetail(tenantId: string): Promise<TenantDetail>
return parsed;
}

/**
* Sandboxed HTML preview URL for a Library artifact — the same
* path an `<iframe sandbox>` in the canvas or Library detail pane loads,
* and the "Open in new tab" affordance's `href`. Server-side (`GET
* .../artifacts/:id/preview` in `@corbits/artifacts-hub`) answers 415 for
* a non-HTML artifact.
*/
// The server answers 415 for a non-HTML artifact.
export function artifactPreviewPath(tenantId: string, artifactId: string): string {
return `/api/tenants/${tenantId}/artifacts/${encodeURIComponent(artifactId)}/preview`;
}

/**
* One-shot fetch of a Library artifact's detail — the same
* `GET /api/tenants/:id/artifacts/:artifactId` read `LibraryRoute` uses via
* `useAPIQuery`, but as a plain promise for callers that aren't a mounted
* component (a chat artifact chip's open handler). Never falls back to
* blob bytes: an `artifactId` always resolves through this Library read.
*/
// A plain promise for callers that aren't a mounted component. Never
// falls back to blob bytes: an `artifactId` always resolves through here.
export async function fetchArtifactDetail(
tenantId: string,
artifactId: string,
Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/app-error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// The last line of defence: a render error anywhere in the tree
// used to leave the reader staring at a blank white page. React only offers
// this as a class component (no hook equivalent exists), so it's the one
// class in an otherwise function-component codebase.
// React offers error boundaries only as a class component (no hook
// equivalent exists), so this is the one class in the codebase.

import { reportError } from "@corbits/error-sink";
import { Button, EmptyState } from "@corbits/react-ui";
Expand Down
Loading
Loading