AGENTS.md@crates/nac-server/web · git:20261013.20d3fc3 · 2026-10-13 · sha256 57bb987638f2e3bb

AGENTS.md@crates/nac-server/web git:20261013.20d3fc3A

Immutable. This exact content never changes and is served at /api/v1/blob/57bb987638f2e3bb.

# 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.
- 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.
- `components/inspector/Transcript.tsx` keeps canonical message grouping, tool
  pairing, and attributed command results in one ordered transcript view. Feature
  mutations and output paging remain in their feature owners.
- `components/inspector/ThreadsView.tsx` keeps orchestrator thread/workset
  navigation and episode rendering together. Direct-child and managed-host
  workflows remain separate features.
- `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.