Frontend — Conceptual Deep Dive
Purpose and Mental Model
Agentweaver's frontend is a browser-based control room for agent work. It does not run agents, decide orchestration topology, or persist long-term state itself. Its job is to:
- authenticate the user,
- let the user choose a project and issue commands,
- ask the backend for authoritative snapshots,
- subscribe to live run events,
- fold those events into UI-friendly state, and
- render the current state clearly enough that a human can steer, approve, inspect, or recover work.
The most important rebuilding idea is snapshot + stream:
- Snapshots answer, "What does the backend know right now?" They come from REST calls and are used when a page first loads, when a completed run is reopened, or when the UI needs metadata such as project lists, teams, graph descriptors, work plans, files, and settings.
- Streams answer, "What changed after I started watching?" They come from Server-Sent Events (SSE) on a run stream.
- Reducers turn raw events into display models: timelines, run status, graph state, coordinator topology, approval cards, and child request lists.
This gives the UI a robust mental model: the backend is the source of truth; the frontend is a deterministic projection of backend facts.
Where this lives:
apps/web/apps/Agentweaver.Web/
Frontend Boundary
The frontend has three runtime layers:
- React/Vite SPA — the application the user interacts with. It owns routing, presentation, browser state, REST calls, SSE consumption, and UI projections.
- Agentweaver API — the authoritative backend. It owns projects, auth, runs, orchestration, work plans, event logs, files, reviews, and mutations.
- Static web host — a small ASP.NET Core app that serves the built SPA and redirects
/docsto the external documentation site. It is not a backend-for-frontend and does not implement the SPA's API routes.
A rebuild should preserve that boundary. Avoid putting business decisions in the browser just because the browser has enough data to guess. For example, the coordinator graph is server-authored: the UI renders topology snapshots and deltas instead of recomputing dependencies on the client.
Trade-off: this makes the UI simpler and safer, but it means the backend must emit complete enough facts for the UI to render useful state.
Where this lives:
apps/web/src/apps/Agentweaver.Web/Program.cs
Technology Shape
The SPA is a TypeScript React app built with Vite. It uses React Router for browser routes, Fluent UI for the visual system, React Flow/Dagre for graph-like views, and Vitest/Testing Library for frontend tests.
The entrypoint mounts React into the #root element, wraps the app in React StrictMode, and uses a small error boundary so a render exception becomes a recoverable error screen instead of a blank page.
At the app root, the UI is wrapped in:
- a Fluent UI provider, so components share theme tokens,
- a browser router, so deep links are normal URLs,
- an auth gate, so protected app routes do not render until session validation completes,
- a persistent shell, so navigation and top-level context remain stable across pages.
App.tsx declares <Routes> explicitly; it does not consume a runtime route registry. AppShell supplies ProjectListProvider and NotificationsProvider. Showcase/provider examples are not the product's composition root.
Rebuild principle: keep the app root boring. Cross-cutting concerns belong there; feature behavior belongs in pages, hooks, reducers, and components.
Where this lives:
apps/web/src/main.tsxapps/web/src/App.tsxapps/web/package.jsonapps/web/vite.config.ts
Routing and Information Architecture
Routes are split into global destinations and project-scoped destinations.
Global routes do not require a project id:
- overview and project gallery/creation,
- Assistant, sessions, skills, observability, and cluster surfaces,
- platform settings, with its own authorization gate.
Project-scoped routes start with /projects/:projectId and represent the work surface for one project:
- dashboard,
- board,
- flow,
- orchestrations,
- workspace,
- settings,
- team / casting,
- memories,
- workflows,
- diagnostics / heartbeat,
- orchestration detail pages with embedded run inspection.
All signed-in routes sit inside the persistent shell. The shell is intentionally above individual pages because navigation, project switching, top bar status, and the floating orchestration action should not disappear when the user opens a deep orchestration page.
The shell derives the active project from the URL. When the user moves to a global page, it remembers the last active project in local storage so the project switcher and project-scoped navigation can still point somewhere useful. This is a UX convenience only; the route remains the source of truth for the currently displayed page.
Rebuild principle: routes should describe user intent, not implementation detail. An orchestration detail URL should be directly openable after refresh, and the page should be able to reconstruct its state from route parameters plus backend snapshots.
Where this lives:
apps/web/src/App.tsxapps/web/src/components/shell/
API Client Design
The frontend uses one conceptual API client: a typed wrapper around fetch. Each method describes a backend operation in application terms, while the private request layer handles shared mechanics:
- combine the configured base URL with a method path,
- attach session auth if present,
- include cookies for cookie-backed sessions,
- JSON-encode request bodies,
- parse successful JSON responses,
- throw a structured API error for non-OK responses.
API origin, not an /api base path
API_URL is an origin, or an empty string for same-origin requests. Client method paths include /api where the actual endpoint does; authentication and protocol paths may be rooted elsewhere.
For example, API_URL="" plus /api/projects calls the same-origin API. API_URL="http://localhost:5000" plus /api/projects calls the development API. Configuring /api as the origin would produce the incorrect /api/api/projects.
This convention is what lets the same SPA run in multiple environments:
- local development can point at
http://localhost:5000, - containerized production can use
"", with the gateway routing same-origin API requests, - the bundle does not need to be rebuilt just because the API origin changes.
Why centralize API calls?
Centralization gives the app one place to solve auth, errors, request formatting, and response typing. Pages can stay focused on interaction flow: "create a run," "load graph," "approve review," or "list projects." It also makes conventions enforceable; a new endpoint should be added as a method that accepts application inputs and returns typed application data.
Trade-off: the API client can become large. Keep it organized around backend resource groups and avoid embedding page-specific UI decisions in it.
Where this lives:
apps/web/src/api/apiClient.tsapps/web/src/api/client.tsapps/web/src/api/types.tsapps/web/src/config.ts
Runtime Configuration and Static Hosting
The SPA is built once and configured at container startup. index.html loads /env-config.js before the React bundle. Runtime window.__AGENTWEAVER_CONFIG__ supplies the API origin; an empty string selects same-origin routing.
This design separates build-time artifacts from deployment-time configuration:
- Vite builds static JavaScript, CSS, and assets.
- The container decides where the API is at startup.
- The ASP.NET Core host serves SPA files and redirects
/docsand its descendants externally; it does not bundle the VitePress site. - Non-HTML assets can be cached aggressively because their built filenames are content-addressed by Vite.
- HTML and fallback responses should not be treated as immutable because they bootstrap the current app version and runtime config.
Rebuild principle: static hosting should be dumb and predictable. Let the API own API behavior; let the SPA own client behavior; let the host serve files and route unknown non-doc paths back to index.html for client-side routing.
Where this lives:
apps/web/index.htmlapps/web/Dockerfileapps/web/docker-entrypoint.shapps/Agentweaver.Web/Program.cs
Authentication and Session Flow
The UI starts in an auth gate. It does not render the signed-in shell until it has resolved any auth redirect and verified the current session with the backend.
Conceptually, sign-in works like this:
- The unauthenticated page sends the browser to the backend Entra authorization endpoint.
- The backend completes Entra authentication and redirects back to the SPA with a short-lived, one-time exchange code.
- Before rendering protected routes, the auth gate exchanges that code for session information.
- The frontend stores the session token and login in
sessionStorage. A newly opened same-origin tab requests the token from an already authenticated tab through a transientBroadcastChannelexchange. The token is not copied tolocalStorage, cookies, URLs, or other durable cross-tab storage. - The API client sends the token as a bearer header when present and also includes cookies.
- The auth gate asks the backend for auth status. The shared HttpOnly browser cookie can authenticate this bootstrap check, but it intentionally cannot authorize general platform APIs such as
/api/projects; those calls still require the per-tab bearer token. - If the backend says the user is signed in, the shell renders. Otherwise, local session state is cleared and the sign-in page renders.
If multiple API calls reject the same stale bearer token at once, the client performs one shared peer-recovery request and lets all failed calls retry with the recovered token. This prevents a burst of concurrent 401 handlers from clearing a token that another call just restored.
The stored login is not just display data. The auth gate compares it with the backend-reported login. If the browser has a token for one user but the backend session reports another, the UI clears local session state rather than silently mixing identities.
The top bar separately fetches auth status for avatar/login display and exposes sign-out. Sign-out calls the backend and returns the browser to the app root.
Trade-offs:
sessionStoragelimits token lifetime to the browser tab/session. Same-origin tabs can transfer the current token directly while an authenticated peer remains open; a new browser session with no authenticated peer must sign in again.- Sending both bearer auth and cookies supports session bootstrap plus bearer-protected API calls without expanding cookie authentication to mutation endpoints, which would require a broader CSRF design.
- URL auth parameters are stripped after exchange so tokens/codes do not linger in browser history or copied links.
Where this lives:
apps/web/src/App.tsxapps/web/src/config.tsapps/web/src/pages/SignInPage.tsxapps/Agentweaver.Api/Endpoints/AuthEndpoints.cs
State Management Philosophy
Agentweaver does not use a single global Redux-style store. State is scoped to the part of the UI that owns it:
- auth/session state lives in the auth gate and browser session storage,
- the project list lives in a small React context shared by shell components,
- the last active project lives in local storage as a navigation convenience,
- page-level forms and toggles live in local component state,
- run timelines and coordinator topology are derived from event streams through reducers,
- persisted backend state is reloaded through REST snapshots instead of being treated as browser-owned.
This keeps state lifetimes aligned with user workflows. A page can be remounted when the active project changes, forcing clean refetches. A deep orchestration page can be opened directly and rebuilt from snapshots plus the stream. A shell-level project switcher can share the project list without making every feature depend on a global app store.
Rebuild principle: store the minimum browser state needed for responsiveness and navigation. Anything authoritative should be fetched from, or streamed by, the backend.
Where this lives:
apps/web/src/hooks/useProjectList.tsxapps/web/src/components/shell/projectContext.tsapps/web/src/timeline/apps/web/src/state/topologyReducer.ts
Live Run Timeline: Event-Sourced UI Projection
The live run UI is the heart of the frontend. It treats a run as an ordered stream of facts.
A run can emit events such as:
- agent turn started / ended,
- message deltas and final messages,
- tool calls and tool results,
- shell/tool approval requests,
- workflow graph updates,
- sandbox warnings,
- review and merge lifecycle events,
- coordinator lifecycle events,
- subtask status changes,
- child questions or approvals,
- terminal completion/failure events.
The stream hook uses fetch, not browser EventSource. That is intentional: authenticated streams need custom headers such as Authorization, and replay after reconnect benefits from Last-Event-ID.
The hook keeps a bounded event buffer so a runaway stream does not grow the DOM forever. It recognizes terminal events so completed streams stop reconnecting. It uses reconnect backoff so transient network issues do not immediately fail the page.
Reconnect after coordinator confirmation
Coordinator runs pause the stream at the confirmation gate: when the run enters awaiting_confirmation, the backend closes the stream with a done event. At that point OutcomeSpecPanel fetches the latest spec directly from the REST API (fetchSpec()) so the panel always shows the persisted, authoritative spec rather than reconstructed stream state. The internal terminalRef is reset and reconnectKey is incremented, which causes useRunStream to re-open a fresh stream against the same run ID. When the user clicks Confirm, the frontend calls onReconnect(), which triggers the same reconnectKey increment and stream re-open. New coordinator events — work-plan creation, subtask dispatch, child run starts — start flowing immediately after confirmation without a manual page refresh.
The timeline builder is pure: given an event list, it returns the display model. It groups activity by reported intent, pairs tool calls with results, and keeps messages with the step that produced them. The same event sequence produces the same timeline whether it came live from SSE or from a persisted event log.
This is the key mental model: SSE events are not rendered directly. They are normalized into durable UI concepts.
Where this lives:
apps/web/src/api/sse.tsapps/web/src/timeline/runTimelineSteps.tsapps/web/src/components/RunTimeline.tsxapps/web/src/components/AgentSessionPanel.tsxapps/web/src/pages/CoordinatorRunPage.tsx
Snapshot + Stream Synchronization
A live stream alone is not enough. Users frequently open pages after work has already started or completed. A completed run may no longer have an active stream. A coordinator topology snapshot may have been emitted before the browser connected.
Agentweaver solves this by merging independent inputs; opening the stream does not wait for the REST seed:
- REST seed — load the latest known snapshot or persisted event list.
- SSE stream — subscribe concurrently and buffer live changes.
- Deduplication — avoid showing the same event twice, usually by sequence id.
- Timeline projection — derive display state from the merged event list. Run/generation guards reject stale seed responses; positive sequences are deduplicated, with restricted handling for sequence-zero singleton events.
The backend side of reconnect is a durable cursor, not a cross-replica live channel. The Postgres event provider reads ordered rows after the last delivered sequence; the client-side REST seed, buffering and reducer fold remain the separate steps above.
For embedded single-agent/child runs, the surface resolves run metadata, optionally fetches persisted events for terminal or parked states, fetches a graph descriptor when needed, and then merges live stream events over the seed.
For coordinator runs, the page loads graph/work-plan/children snapshots so the all-up graph and agent rail render immediately, then applies coordinator SSE events as live deltas.
Trade-off: merge logic adds complexity, but it gives a much better operator experience. Refreshing a finished run should not show an empty timeline just because the live stream has already closed.
Where this lives:
apps/web/src/pages/CoordinatorRunPage.tsxapps/web/src/api/sse.ts
Single-Agent Run Flow
Inspection of an existing single-agent or coordinator-child run follows this path. Public POST /api/runs is retired (410); new work enters through coordinator submission rather than a direct single-agent creation API:
- A project orchestration creates work and, when needed, child runs.
- The backend creates the run and returns identifiers.
- The run appears in project/coordinator surfaces.
- Embedded inspection resolves the run metadata and stream key.
- The surface loads any persisted seed events and graph descriptor.
- The surface opens the SSE stream.
- The timeline and graph update as events arrive.
- Review, request-changes, commit, and merge actions call the API and then refresh or reconnect the stream projection.
Run inspection is deliberately built from reusable pieces: timeline, graph/workflow panels, review controls, sandbox/files panels, and stream hooks. A rebuild should keep the stream projection independent from the visual layout so the same run projection can appear in different contexts.
Important edge case: coordinator child runs may not appear in the parent project run list because they are children, not top-level project runs. Embedded inspection can still resolve them directly by run id and treat that run id as the stream/graph key.
Where this lives:
apps/web/src/pages/AssistantRunPage.tsxapps/web/src/components/ArtifactBrowser.tsx
Coordinator Orchestration Flow
Coordinator mode is the multi-agent execution path. The frontend presents it as one orchestration, but internally it is a coordinator run plus child runs.
Conceptually:
- The user gives a goal.
- The coordinator drafts or confirms an outcome specification.
- The backend decomposes the goal into a work plan and topology.
- Subtasks are dispatched to child runs.
- Child runs emit their own events, questions, tool approvals, and terminal states.
- The coordinator stream re-projects the all-up lifecycle so the user can monitor and steer from one page.
- When children are ready, assembly/review/merge phases progress through coordinator events.
The topology reducer is intentionally thin. It applies server-authored snapshots and deltas, merges subtask status updates, and attaches steering state to existing nodes. It does not invent dependencies or compute topology from scratch. This protects the UI from accidentally disagreeing with backend scheduling rules.
Coordinator pages also need special handling for child questions and approvals. The user sees them in the all-up coordinator page, but the response must be sent to the child run that asked. Therefore each displayed request carries the child run id and, when available, the subtask id.
Automation toggles such as autopilot and auto-approve tools are shown at the coordinator level, but backend behavior may cascade them to children. The UI uses optimistic state for responsiveness and reverts on API failure.
Rebuild principle: show the user one orchestration, but keep run ownership precise. Coordinator commands go to the coordinator; child answers and tool grants go to the requesting child.
Where this lives:
apps/web/src/components/StartOrchestrationDialog.tsxapps/web/src/pages/CoordinatorRunPage.tsxapps/web/src/state/topologyReducer.tsapps/web/src/pages/FlowPage.tsx
How the UI Stays in Sync
The UI stays in sync by following these rules:
- Use route params as identity. A page knows which project/run to load from the URL.
- Fetch snapshots on entry. Load enough REST data to render immediately, even for completed runs.
- Subscribe to the run stream. Open one SSE stream for the run currently being watched.
- Replay from the last event id. On reconnect, ask the backend for events after the last seen sequence.
- Deduplicate defensively. Streams, snapshots, reconnects, and singleton events can overlap.
- Fold, do not mutate ad hoc. Raw events become stable UI state through reducers and derived selectors.
- Let terminal events stop liveness. Completed/failed/merged/declined states should not reconnect forever.
- Treat backend snapshots as authoritative. Especially for coordinator topology, work plans, child ownership, and run status.
This pattern is close to event sourcing, but only on the client projection side. The frontend does not own the event log; it consumes the backend's event log and renders a projection.
Error Handling and Recovery
Frontend error handling is layered:
- render errors are caught by the root error boundary,
- API non-OK responses become structured client errors,
- auth failures clear local session and return to sign-in surfaces,
- stream failures reconnect with backoff where safe,
- missing optional snapshots are tolerated when the stream can still provide state,
- missing durable logs fall back to live SSE when available,
- terminal/parked runs use persisted events because no live stream may exist.
A rebuild should distinguish between fatal and non-fatal failures. Failure to fetch an optional graph descriptor should not prevent the timeline from rendering. Failure to validate auth should prevent protected routes. Failure to reconnect a run stream after repeated attempts should surface an actionable status rather than silently freezing.
Content and Safety Considerations
Timeline text is rendered through the shared safe Markdown surface. Display helpers shorten noisy file paths in row titles while the full result remains available in details.
The important design principle is to make untrusted run output observable without making it executable. Agent and tool output should be treated as data.
Where this lives:
apps/web/src/timeline/runTimelineSteps.tsapps/web/src/components/RunTimeline.tsxapps/web/src/components/SafeMarkdown.tsx
Rebuild Checklist
If rebuilding the Agentweaver frontend from scratch, implement in this order:
- Static Vite React shell with routing and a root error boundary.
- Runtime config loader that can set API base URL at deployment time.
- Typed API client with centralized auth, credentials, JSON parsing, and API errors.
- Entra sign-in handoff, session exchange, session validation, and sign-out.
- Persistent app shell with global/project navigation and project context.
- Project list/provider and project-scoped pages.
- Run stream hook using fetch-based SSE with auth headers,
Last-Event-ID, dedupe, terminal detection, and reconnect backoff. - Pure timeline reducer that folds raw events into display items.
- Embedded single-agent/child run inspection using REST seeds plus live SSE.
- Coordinator page using graph/work-plan/children seeds plus coordinator SSE.
- Thin topology reducer that applies server-authored snapshots and deltas.
- Review, approval, question-answering, and steering actions that call the correct owning run.
- Static hosting with SPA fallback and external docs redirects.
Gotchas and Conventions
- Configure
API_URLas an origin or""; method paths retain their actual/apiprefix. - The static web host is not the API. It serves SPA files/fallbacks and redirects docs externally.
- Runtime API URL should override build-time environment so one bundle can deploy to multiple environments.
- Use fetch-based SSE, not plain
EventSource, if authenticated headers and replay control are required. - Finished or parked runs need REST seeds because their live stream may already be closed.
- Coordinator topology is server-authored; render it instead of recomputing it.
- Child questions and tool approvals shown on the coordinator page must be answered against the child run that asked.
- Keep browser state small. Backend state is authoritative; UI state is a projection.
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Postgres is the event relay |
| subtitle | Any API replica can serve a cursor over durable RunEvents—no sticky session required. |
| group-title0 | Write path · replica A |
| group-title1 | Read path · replica B |
| Run producer | Run producer |
| Run producer | Append a structured event |
| Run producer | runId + type + payload |
| EF event stream | EF event stream |
| EF event stream | Serialize writes per run |
| EF event stream | pg_advisory_xact_lock |
| RunEvents | RunEvents |
| RunEvents | Shared PostgreSQL table |
| RunEvents | (RunId, Sequence) |
| Web / MCP watcher | Web / MCP watcher |
| Web / MCP watcher | Consume ordered events |
| Web / MCP watcher | last delivered cursor |
| SSE endpoint | SSE endpoint |
| SSE endpoint | Emit id + event + data |
| SSE endpoint | ordered response frames |
| EF subscriber | EF subscriber |
| EF subscriber | Read Sequence > cursor |
| EF subscriber | idle poll: 250 ms |
| e1 | append |
| e2 | commit |
| e3 | ordered batch |
| e4 | yield |
| e5 | SSE frames |
| assurance-title | POSTGRES LANE ONLY |
| assurance-line1 | SQLite register-channel / replay / tail is a separate implementation—not this architecture. |
| assurance-line2 | Late-delta suppression is process-local; do not read it as a database-wide terminal fence. |
| Run producer | Input |
| Run producer | RunStreamEntry |
| Run producer | Identity |
| Run producer | runId + event type |
| Run producer | Body |
| Run producer | Structured payload |
| Run producer | Ack |
| Run producer | After durable commit |
| EF event stream | Lock |
| EF event stream | Per-run advisory lock |
| EF event stream | Next |
| EF event stream | MAX(Sequence) + 1 |
| EF event stream | Write |
| EF event stream | Save transaction |
| EF event stream | Commit |
| EF event stream | Before acknowledgement |
| RunEvents | Table |
| RunEvents | Key |
| RunEvents | RunId + Sequence |
| RunEvents | Order |
| RunEvents | Ascending sequence |
| RunEvents | Reuse |
| RunEvents | Same type / payload |
| Web / MCP watcher | Client |
| Web / MCP watcher | Web or MCP |
| Web / MCP watcher | Resume |
| Web / MCP watcher | Last delivered cursor |
| Web / MCP watcher | Replica |
| Web / MCP watcher | No sticky requirement |
| Web / MCP watcher | History |
| Web / MCP watcher | Durable ordered events |
| SSE endpoint | Frame |
| SSE endpoint | id + event + data |
| SSE endpoint | Cursor |
| SSE endpoint | Last-Event-ID |
| SSE endpoint | Delivery |
| SSE endpoint | Yield ordered events |
| SSE endpoint | Close |
| SSE endpoint | After batch is drained |
| EF subscriber | Query |
| EF subscriber | Sequence > cursor |
| EF subscriber | Idle |
| EF subscriber | Poll after 250 ms |
| EF subscriber | State |
| EF subscriber | Shared durable table |
| EF subscriber | Blocked |
| EF subscriber | Retryable: keep open |
| producer | Coordinator or run execution; Acknowledgement follows commit |
| append | Allocate MAX(Sequence) + 1; Save and commit transaction |
| store | Cross-replica ordered history; Explicit duplicates must match payload |
| client | Reconnect from the cursor; No local channel dependency |
| sse | Cursor advances after delivery; Drain batch before terminal close |
| reader | Query the shared durable table; Retryable assembly_blocked stays open |
| notes | POSTGRES LANE ONLY; SQLite register-channel / replay / tail is a separate implementation—not this architecture.; Late-delta suppression is process-local; do not read it as a database-wide terminal fence. |
| groups | Write path · replica A; Read path · replica B |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Frontend: intent and projection |
| takeaway | The browser presents backend facts; the API remains the authority. |
| group-title-0 | BROWSER · OPERATOR INTENT |
| group-title-1 | BACKEND FACTS · UI PROJECTION |
| AuthGate | AuthGate |
| AuthGate | Validated SPA session |
| AuthGate | REST and SSE use the session bearer |
| AuthGate | App.tsx |
| Rendered controls | Rendered controls |
| Rendered controls | Status, timeline and graph |
| Rendered controls | Operator decisions become new API calls |
| Rendered controls | CoordinatorRunPage.tsx |
| AppShell | AppShell |
| AppShell | LeftNav · project switcher · floating actions |
| AppShell | ProjectList + Notifications providers |
| AppShell | AppShell.tsx:134-182 |
| Client projection | Client projection |
| Client projection | Reducers combine backend facts |
| Client projection | Topology is server-authored, not invented |
| Route pages | Route pages |
| Route pages | Board · run · workspace |
| Route pages | Route parameters select the current scope |
| Route pages | App.tsx:80-127 |
| Seed + live events | Seed + live events |
| Seed + live events | Independent REST and SSE inputs |
| Seed + live events | Positive sequence IDs deduplicate events |
| Seed + live events | useSeededRunStream.ts |
| API client | API client |
| API client | Typed requests and error handling |
| API client | API_URL origin + endpoint /api paths |
| API client | config.ts:13-36 |
| Agentweaver API | Agentweaver API |
| Agentweaver API | Projects · runs · graph · events |
| Agentweaver API | Server owns persisted state and topology |
| AuthGate | enter |
| AppShell | contains |
| Route pages | request |
| API client | HTTP |
| Agentweaver API | history + SSE |
| Seed + live events | events |
| Client projection | render |
| scope | Read direction: intent down the left; backend facts rise on the right. |
| groups | BROWSER · OPERATOR INTENT; BACKEND FACTS · UI PROJECTION |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Routes: global and project scope |
| takeaway | App.tsx declares routes; AppShell supplies shared context, not a route registry. |
| group-title-0 | GLOBAL · NO PROJECT PARAMETER |
| group-title-1 | SHARED SHELL + PROJECT ROUTES |
| App.tsx Routes | App.tsx Routes |
| App.tsx Routes | / · /overview · /projects |
| App.tsx Routes | Global sessions, settings and assistant |
| App.tsx Routes | App.tsx:80-100 |
| AppShell | AppShell |
| AppShell | ProjectListProvider |
| AppShell | NotificationsProvider wraps shell content |
| AppShell | AppShell.tsx:134-182 |
| Operator destinations | Operator destinations |
| Operator destinations | /console → /assistant |
| Operator destinations | /sessions is global; ?project scopes it |
| Operator destinations | App.tsx:93-100,130-135 |
| Project route family | Project route family |
| Project route family | /projects/:projectId |
| Project route family | Dashboard · board · flow · orchestrations |
| Project route family | App.tsx:104-126 |
| Platform settings | Platform settings |
| Platform settings | /platform-settings |
| Platform settings | Non-admin users redirect to /overview |
| Platform settings | App.tsx:87-92 |
| Project resources | Project resources |
| Project resources | Workspace · settings · team |
| Project resources | Cast · agent memory · memories · skills |
| Project resources | App.tsx:110-117 |
| Global observability | Global observability |
| Global observability | /observability · /traces · /agents |
| Global observability | Redirect pages resolve destination scope |
| Global observability | App.tsx:99-101 |
| Project operations | Project operations |
| Project operations | Observability · workflows |
| Project operations | Diagnostics · heartbeat · cluster |
| Project operations | App.tsx:118-125 |
| AppShell | wraps |
| App.tsx Routes | declares |
| scope | Cards group declared paths, not navigation dependencies. Global admin/observability are independent routes. |
| groups | GLOBAL · NO PROJECT PARAMETER; SHARED SHELL + PROJECT ROUTES |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Static hosting and API origins |
| takeaway | The Web host serves the SPA; API_URL is an origin or empty, never /api. |
| group-title-0 | WEB HOST · STATIC DELIVERY |
| group-title-1 | BROWSER · RUNTIME DESTINATIONS |
| Browser request | Browser request |
| Browser request | Assets or a client-side route |
| Browser request | The Web host is not the API host |
| Browser request | Web/Program.cs:39-65 |
| Runtime configuration | Runtime configuration |
| Runtime configuration | window.__AGENTWEAVER_CONFIG__ |
| Runtime configuration | API_URL selects the API origin |
| Runtime configuration | config.ts:13-36 |
| Static file middleware | Static file middleware |
| Static file middleware | Default files + static assets |
| Static file middleware | Non-HTML assets get immutable caching |
| Static file middleware | Web/Program.cs:39-50 |
| Origin resolution | Origin resolution |
| Origin resolution | Origin string, or "" = same-origin |
| Origin resolution | Client endpoints append their own /api |
| SPA route fallback | SPA route fallback |
| SPA route fallback | Unknown route → index.html |
| SPA route fallback | React handles the resulting route |
| SPA route fallback | Web/Program.cs:61-65 |
| API destination | API destination |
| API destination | REST + authenticated fetch SSE |
| API destination | Same-origin still uses /api endpoints |
| API destination | api/sse.ts:239-337 |
| Documentation route | Documentation route |
| Documentation route | /docs and /docs/{path} |
| Documentation route | Temporary redirect preserves suffix |
| Documentation route | Web/Program.cs:52-59 |
| External documentation | External documentation |
| External documentation | Configured documentation base URL |
| External documentation | Not the local SPA fallback |
| Browser request | asset |
| Static file middleware | unmatched |
| Runtime configuration | supplies |
| Origin resolution | requests |
| Documentation route | 302 redirect |
| scope | Parallel concerns: static delivery, runtime API selection and external docs redirection. |
| groups | WEB HOST · STATIC DELIVERY; BROWSER · RUNTIME DESTINATIONS |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Entra sign-in and browser session |
| takeaway | The callback returns a one-time code; session exchange delivers the SPA bearer. |
| group-title-0 | SIGN-IN · API + ENTRA |
| group-title-1 | SESSION · PER-TAB BROWSER STATE |
| Browser sign-in | Browser sign-in |
| Browser sign-in | Begin API Entra authorization |
| Browser sign-in | State binds the browser callback |
| Browser sign-in | AuthEndpoints.cs |
| Session exchange | Session exchange |
| Session exchange | POST the one-time exchange code |
| Session exchange | Returns validated token + browser session |
| Session exchange | AuthEndpoints.cs:398-455 |
| Microsoft Entra ID | Microsoft Entra ID |
| Microsoft Entra ID | Authenticate the user |
| Microsoft Entra ID | Identity authority, not GitHub OAuth |
| Per-tab sessionStorage | Per-tab sessionStorage |
| Per-tab sessionStorage | Store the SPA bearer |
| Per-tab sessionStorage | No durable localStorage token |
| Per-tab sessionStorage | config.ts:60-138 |
| API callback | API callback |
| API callback | Validate callback and state |
| API callback | Issue a one-time frontend exchange code |
| Same-origin peer tab | Same-origin peer tab |
| Same-origin peer tab | BroadcastChannel request/response |
| Same-origin peer tab | Transient token transfer to a new tab |
| Same-origin peer tab | config.ts:207-240 |
| Frontend callback | Frontend callback |
| Frontend callback | Receive exchange code |
| Frontend callback | Do not treat the code as an access token |
| Authenticated requests | Authenticated requests |
| Authenticated requests | REST and fetch-based SSE |
| Authenticated requests | Bearer token; API authorizes resources |
| Authenticated requests | api/sse.ts:239-337 |
| Browser sign-in | sign in |
| Microsoft Entra ID | callback |
| API callback | code |
| Frontend callback | POST code |
| Session exchange | session |
| Per-tab sessionStorage | transfer |
| Per-tab sessionStorage | bearer |
| scope | Only a one-time exchange code crosses the callback URL; the session token stays out of URLs. |
| groups | SIGN-IN · API + ENTRA; SESSION · PER-TAB BROWSER STATE |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Live timeline: independent inputs |
| takeaway | REST seed and live SSE run concurrently, then merge into a guarded projection. |
| group-title-0 | INDEPENDENT INPUTS |
| group-title-1 | MERGE · PROJECT · RECOVER |
| Current run ID | Current run ID |
| Current run ID | Route chooses stream scope |
| Current run ID | Run/generation guards reject stale seeds |
| Current run ID | useSeededRunStream.ts:43-151 |
| Merge run events | Merge run events |
| Merge run events | Positive-sequence deduplication |
| Merge run events | Restricted sequence-zero singleton rules |
| Merge run events | mergeRunEvents.ts:23-79 |
| Persisted event history | Persisted event history |
| Persisted event history | REST seed requested independently |
| Persisted event history | A seed failure does not block live SSE |
| Persisted event history | useSeededRunStream.ts:85-134 |
| Timeline projection | Timeline projection |
| Timeline projection | Reducers + server topology seed |
| Timeline projection | Render graph, timeline, status and controls |
| Timeline projection | CoordinatorRunPage.tsx |
| Live event transport | Live event transport |
| Live event transport | Authenticated fetch + credentials |
| Live event transport | Last-Event-ID resumes the cursor |
| Live event transport | api/sse.ts:239-337 |
| Parser and event buffer | Parser and event buffer |
| Parser and event buffer | Dedupe and bounded retention |
| Parser and event buffer | Cursor advances with accepted events |
| Unexpected disconnect | Unexpected disconnect |
| Unexpected disconnect | Bounded reconnect backoff |
| Unexpected disconnect | Explicit reconnect reopens after gate action |
| done / terminal | done / terminal |
| done / terminal | Stop the current transport |
| done / terminal | A gate done is not universal run completion |
| done / terminal | api/sse.ts; RunEndpoints.cs |
| Current run ID | seed |
| Current run ID | live |
| Persisted event history | seed events |
| Live event transport | frames |
| Parser and event buffer | events |
| Merge run events | merged |
| Live event transport | disconnect |
| Parser and event buffer | done |
| scope | No REST→SSE prerequisite. Backend topology is an input; browser reducers do not author it. |
| groups | INDEPENDENT INPUTS; MERGE · PROJECT · RECOVER |
