# Web client guide

This directory owns the React/Vite client, browser-facing state, checked-in API
contract, component tests, and production-embedded Playwright journeys. The
server remains the source of business truth and wire schemas.

## Ownership and dependencies

- `src/app/features/<feature>/` owns feature model helpers, queries, controller
  hooks/context, and presentation when a workflow spans those concerns.
- `src/app/services/api.ts` is the HTTP transport adapter. TanStack query keys,
  caching, cancellation, invalidation, and polling belong to query owners, not
  presentational components.
- `packages/nac-client/` owns the runtime-free ALL-121 typed HTTP/SSE boundary.
  Its full resource facade and generated operation classification share the
  existing native methods; private control routes stay in the web adapter.
  The standalone service modules are compatibility facades; do not fork its
  transport, replay, reconnect, or command-admission logic back into the app.
- `src/app/types/openapi.generated.ts` is generated from Rust/OpenAPI. Import
  stable aliases from `src/app/types/api.ts`; client-only refinements may live
  there, but do not hand-copy wire DTOs.
- Keep managed-host product state inside `features/managed`. Managed
  orchestrator session views remain session/delegation features because they
  are a distinct durable topology.
- Preserve navigation, abort signals, polling settlement, optimistic state,
  query invalidation, accessibility labels, and behavior-selection defaults.
- Human product operations never invoke agent approval. A focused fresh terminal
  keystroke is intentional input on ordinary attach/reconnect; attaching sends
  no bytes. Accepted-but-uncertain delivery freezes input until explicit recovery
  acknowledgement and never auto-replays old input. Recovery is delivery state,
  not permission; the server retains denial/authentication/backend gates.
- Human input uses trusted public keyboard/paste/composition/pointer events;
  renderer-generated replies are not intent. Bounded runtime-owned uncertainty
  survives view/controller replacement without storing or replaying bytes. Input
  activation binds source, mode, current capabilities and controller revision.
  Historical output preserves factual retention while suppressing all controls.
  Consume cached session availability for passive terminal/history views; do not
  trigger recovering session reads merely to render them.
- `src/app/ui/` owns the pinned shadcn/Radix primitives and each presentation
  instance's overlay layer stack. Product code keeps importing atoms. Overlays
  register with `useLayer` instead of adding their own Escape, outside-press,
  inertness or focus-return handling, and Radix stays non-modal. The Popover
  atom's opt-in `focusScope` owns only initial focus and Tab navigation; see
  [ADR 0007](../../../docs/architecture/0007-scoped-ui-primitives.md).
- Generic providers are for genuinely cross-feature browser state. Do not use
  them as a home for one feature's workflow.
- `src/app/runtime/` owns one immutable consumer binding, private query cache,
  presentation stores and request/stream lifetime. Production feature owners
  read that context; compatibility globals are only for legacy hook fixtures.
  Replace the runtime when any binding changes. Keep caller routing, styles,
  theme and authenticated stream construction explicit at the presentation root.

## Starting points

- `src/App.tsx` — provider/router composition.
- `src/app/services/api.ts` — HTTP transport and error decoding.
- `src/app/services/queries.ts` — stable compatibility barrel only.
- `src/app/services/queries/` — focused host, direct/delegation,
  configuration, session, workspace, project, key, and invalidation owners.
- `src/app/features/managed/` — managed model/query/controller/presentation.
- `src/app/types/api.ts` — stable aliases/refinements over generated schemas.
- `openapi.json`, `scripts/generate-api-types.mjs` — checked-in contract and
  fail-closed generator.
- `e2e/` and `playwright.config.ts` — production-embedded browser coverage.

## Cohesive size exceptions

- `components/inspector/ChatInputBox.tsx` keeps one composer state machine:
  text/selection, slash commands, attachments, send-versus-queue behavior,
  keyboard/IME handling, and its accessible presentation. Put server state and
  API mutations in query/controller owners, not in this component.
- `components/modals/ConfigurationsPanel.tsx` and `SettingsModal.tsx` keep the
  existing dense configuration forms whose field validation and save ordering
  are exercised as one workflow. Managed-host panels do not belong there.
- `components/modals/CreateProjectModal.tsx` keeps the coupled project location,
  SSH/sandbox fields and advanced launch draft in one accessible form. Ordered
  commands and session-request construction belong in `features/setup`, while
  the modal retains local field state and its presentation.
- `store/runtimeStore.ts` keeps snapshot hydration and ordered live-event
  reduction in one store factory. Its activation fence and coupled run/tool/
  transcript projection must share a lifetime; splitting the reduction across
  independent stores would obscure ordering and late-event protection.

These existing UI owners may exceed 800 lines because splitting their tightly
coupled local form/view state would scatter one workflow. Do not add unrelated
queries, providers, or new product workflows to them; extract a real controller
or feature boundary when new behavior proves one.

## Commands

```sh
npm --prefix crates/nac-server/web run typecheck
npm --prefix crates/nac-server/web run lint
npm --prefix crates/nac-server/web run format:check
npm --prefix crates/nac-server/web test
npm --prefix crates/nac-server/web run test:client-package
make test-api-contract
make test-assets
make test-e2e
```

## Generated artifacts and single writers

- `make generate-api-contract` writes `openapi.json`,
  `src/app/types/openapi.generated.ts`, and the transitive schema subset under
  `packages/nac-client/src/`. The generator fails on unsupported schema
  constructs; extend it explicitly rather than widening to `any`.
- `npm ... run build` writes `../assets/dist` and
  `packages/nac-client/dist` and `packages/nac-presentation/dist`. Commit
  source and all outputs together. `vite.presentation.config.ts` reuses the
  native entry and build pipeline; `scripts/presentation-css.ts` scopes the
  generated stylesheet, and `scripts/presentation-types.mjs` removes source
  aliases from emitted declarations. Keep React peers external and CSS/assets
  opt-in; do not create a second UI or transport implementation.
  Do not edit hashed assets by hand.
- `scripts/sync-file-icons.mjs` is the writer for synchronized icon assets when
  that source set changes.

## Placement mistakes

- Do not encode server validation, persistence defaults, or authorization in
  React helpers.
- Do not fetch directly from presentation components when a query owner needs
  to preserve cancellation/polling/invalidation semantics.
- Do not scatter one managed workflow across generic modal/provider folders.
- Do not edit generated API types or committed production chunks manually.
