# Architecture (/en/docs/architecture)



One repository holds four deployable pieces: a Chrome extension, an optional sync backend, the agent skills that glue an AI tool to the extension, and this documentation site. Everything is built around one position: the browser is the data master, and the only remote component is a sync server you choose to point at.

<Mermaid
  chart="flowchart LR
  subgraph device[&#x22;Your device&#x22;]
    agent[&#x22;AI tool<br/>(Claude Code / Cursor / ZCode)&#x22;]
    host[&#x22;oncewise-message host<br/>ai.oncewise.native&#x22;]
    bg[&#x22;Extension background<br/>(service worker)&#x22;]
    sp[&#x22;Side panel workbench&#x22;]
    ct[&#x22;Content script<br/>(step engine)&#x22;]
    page[&#x22;Target page&#x22;]
  end
  sync[&#x22;oncewise-ai-sync<br/>(optional, self-hosted)&#x22;]

  agent -- &#x22;Chrome DevTools MCP:<br/>inspect, verify&#x22; --> page
  agent -- &#x22;client.mjs (local IPC)&#x22; --> host
  host -- &#x22;Chrome native messaging&#x22; --> bg
  bg -- &#x22;injects per flow site&#x22; --> ct
  ct -- &#x22;reads and acts&#x22; --> page
  sp -- &#x22;enable, inputs, history&#x22; --> bg
  bg -- &#x22;scripts + versions<br/>(only if configured)&#x22; --> sync"
/>

## Repository layout [#repository-layout]

```
extension/   Chrome MV3 extension (WXT + React)
backend/     oncewise-ai-sync (Rust/Axum + PostgreSQL)
skills/      Agent skills + the native-messaging programs (source of truth)
demo/        Playwright extension e2e (loads the real build output)
scripts/     Test and demo runners (Python, uv)
docs/        PRDs and user stories (the product's decision record)
docs-web/    This documentation site
```

`DESIGN.md` holds the visual spec; product constraints defer to `docs/`.

## Extension [#extension]

WXT + React, four entrypoints:

* **background** — the service worker. Runtime messaging hub, flow store, business-instance claims (the duplicate-submit guard), and the native-messaging port.
* **content** — injected dynamically per the flow's site, never globally registered. Hosts the step engine that executes the constrained tree ([Flows](./flows)); when the last flow for an origin is deleted, the registration is removed.
* **sidepanel** — the workbench you see when clicking the toolbar icon: flow list, editor with inputs and enable controls, version history, the Optimize-with-AI grant, and the Sync tab.
* **import page** — the confirmation surface for AI deliveries, plus a compact flow list with enable/pause toggles.

`lib/` holds about 29 modules in three families: `flow-*` (schema, compiler, DOM driver, dry-run presentation), `import-*` (handover intake), and `sync-*` (API client, storage, Herald sign-in). Vitest unit tests mirror `lib/` file-for-file; story-level acceptance lives in the Playwright suite under `demo/e2e/extension/`, which loads the real build output against self-managed fixtures.

Two design calls worth knowing:

* **Constrained step trees instead of scripts.** A flow is data from a closed vocabulary with engine-enforced budgets. Nothing a flow carries can execute, which is what makes unattended runs on logged-in pages acceptable.
* **Local-first storage.** Flows, settings, inputs and the 10-version history live in `chrome.storage`. There is no account, no telemetry, and no server in the default loop.

## The local channel [#the-local-channel]

`skills/oncewise-message/` ships the host (`host.mjs`) and the CLI (`client.mjs`). The AI tool writes a request to a local IPC endpoint; the host relays it to the extension over Chrome's native messaging; one JSON result comes back the same way. The host relays in memory and persists no flow data. The channel can validate, save and verify flows; it cannot enable, delete or roll back — those transitions exist only behind the extension UI, and the import page remains the place where you confirm deliveries.

## Backend [#backend]

`oncewise-ai-sync` (Rust/Axum, sqlx migrations at startup, utoipa for the spec): key spaces (client-generated ID + key) and org-bound spaces, scripts, immutable versions. Routes group into `auth`, `spaces`, a workspace group (invites, members, ownership — gated deployments only), `scripts`, `ops`; the whole surface is generated into the [OpenAPI reference](./openapi) and served at `/api/openapi.json`. The server keeps no accounts or member tables of its own; on a gated deployment, org-bound spaces resolve membership and roles live from the Herald organization layer, while key spaces keep the space-code semantics ([Self-hosting](./self-hosting)).

On the client, `sync-*` modules implement the same surface: server URL and mode probe, spaces and versions, and the Herald OAuth flow (authorization window, silent renewal, epoch-guarded local session storage).

## Verification [#verification]

All test and demo run instructions are maintained centrally in [`scripts/index.md`](https://github.com/timzaak/OnceWise/blob/main/scripts/index.md) — environment prerequisites, log locations, failure recovery. The repository's README keeps no duplicate commands. The demo environment (`uv run scripts/demo-start.py`) brings up the extension build, the backend, a Herald login dependency and a browser with the extension loaded, so the whole loop above can be exercised by hand.
