AGENTS.md@packages/client/src/components/access-grant · git:20261021.3c7a08e · 2026-10-21 · sha256 a6d28029d4a73d8f

AGENTS.md@packages/client/src/components/access-grant git:20261021.3c7a08eA

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

# DOX — packages/client/src/components/access-grant

Files in this directory. One row per source file.

| File | Purpose |
|------|---------|
| `__tests__/ProvisionPromptDialog.test.tsx` | E24 line controls per class, E25 copy + focus (labelled modal, close focused, no answer emphasised), answers/dismissal rejects all, remote hand-off opens the wizard without `initialRun` (3.3). See change: add-access-provisioning (tasks 3.6, 3.7). |
| `__tests__/ProvisionPromptHost.test.tsx` | One dialog at a time, `provision_response` once, `provision_dismiss` closes (first answer wins), expiry tick, reconnect reconcile (F5; needs capability). See change: add-access-provisioning (task 3.2). |
| `GrantPromptDialog.tsx` | S1 held / S2 deferred access-grant dialog on client-utils `Dialog` (`size="md"`, non-flush, `testId="grant-dialog"`). Exports `GrantPromptDialog({prompt, now, queued, onAnswer})`. Subject = monospace headline (`grant-dialog-subject`, React text only); plane-specific title/reason; store named in `grant-dialog-consequence` before buttons. Footer order Deny (`Dialog.Cancel`), Allow once, Allow always — ALL `neutral` intent, none emphasised/focus-defaulted (initial focus = built-in close). Held: `grant-dialog-waiting` countdown from `expiresAt`. Deferred: `grant-dialog-deferred` pill, no countdown, allow-once OMITTED. Ladder (`grant-dialog-ladder`) only when `copy.ladder` non-empty: radios = denied subject + carried rungs, denied preselected, no free text, `grant-dialog-selected` beside answers. Allow once/always send selected rung; Deny + every dismissal send the denied subject. See change: add-access-grant-dialog (tasks 7.1, 7.2, 7.4, 7.5). Filesystem/cwd planes (`YOLO_PLANES`) render `GrantDialogYoloOffer` in the body, before the footer: dashed secondary affordance, NOT a verdict; activation never answers the pending denial (10.74). See change: add-access-grant-dialog (tasks 8b.7a) Path-plane prompts show the pi-trust checkbox; `onAnswer(verdict, subject, piTrust?)`. See change: mirror-dashboard-trust-to-pi. Title/plane/reason copy for `preview-egress`. See change: improve-canvas-html-and-url-ux. |
| `GrantPromptHost.tsx` | App-wide mount of the grant dialog (rendered in both App returns). Exports `GrantPromptHost({onMessage, send, ws, store?, loadPending?})`. `grant_channel` → `setGrantChannel`; `ws === null` → `clearGrantChannel`; `grant_request`/`grant_dismiss` → `GrantPromptStore.apply`. One dialog at a time (`queue[0]`, rest counted). Answer → `store.claim` gate → `grant_response` over WS (late answer after dismissal never sent). 1 s tick drives countdown + `store.expire`. Every (re)connect reconciles vs `GET /api/access/prompts` prompted entries. See change: add-access-grant-dialog (tasks 7.1-7.3) Sends `piTrust` with `grant_response`. See change: mirror-dashboard-trust-to-pi. |
| `PiTrustCheckbox.tsx` | "Also trust this folder in pi" checkbox + `usePiTrustChoice(source)` (seeded from source default). See change: mirror-dashboard-trust-to-pi. |
| `ProvisionPromptDialog.tsx` | Access-provisioning dialog on client-utils `Dialog` (`size="lg"`, `flush`, `showClose`, `testId="provision-dialog"`, `ariaLabel` = title). Exports `ProvisionPromptDialog({request, now, queued, onAnswer(lines)})`. Fixed header, ONE focusable scroll region (`provision-body`), reach + consent block + footer pinned below. Initial focus = built-in close; nothing preselected; `Reject all` / `Approve selected (n)` both neutral. Dismissal (Escape/backdrop/close/Reject all) answers every line `reject`. No folder lines → remote-only title + `Dismiss`. Claim = `requested for` + root (`provision-claim`). See change: add-access-provisioning (tasks 3.2, 3.3, 3.7). |
| `ProvisionPromptHost.tsx` | App-wide mount of the provisioning dialog (App.tsx, both returns, beside `GrantPromptHost`, which owns the capability). Exports `ProvisionPromptHost({onMessage, send, ws, store?, loadPending?})`. `provision_request`/`provision_dismiss` → `ProvisionPromptStore.apply`. One dialog at a time. Answer → `store.claim` gate → `provision_response` over WS (late answer after dismissal never sent). 5 s tick drives `store.expire` + expiry pill. Reconcile vs `GET /api/access/provision` on (re)connect, only while `useHasGrantChannel()`. See change: add-access-provisioning (task 3.2). |
| `ProvisionRequestParts.tsx` | Shared request parts for the dialog AND the Access page cards. `useProvisionAnswer(lines)` (per-line approve set, per-line pi-trust over one `usePiTrustChoice("accessGrants")` seed; `answerLines()` D9 wire shape — approved project-root line always carries explicit `piTrust`; `rejectLines()`), `ProvisionLines` (real path, bounded inert reason, `no reason given`, `… cut`, class pill = icon + word, approve checkbox only on `grantable` and only when `answerable`, `Read-only` otherwise, `Select all that need approval` toggle for grantable lines, `PiTrustCheckbox` on project-root lines), `ProvisionRemoteGoals` (names the task per goal; `Open Remote access` lazy-opens `RemoteAccessDialog` on its landing, no `initialRun`; pill `Remote access opened`), `ProvisionConsequence` / `ProvisionConsent` (every session on this machine; consent, not protection). See change: add-access-provisioning (tasks 3.2-3.4). |
| `YoloAccessCard.tsx` | Access page YOLO card (S6). Exports `YoloAccessCard({yolo, base, store?, onChanged?})`. Active (`yolo-access-active`, `data-scope`): planes (filesystem, working directory), EVERY root + add time or "every folder" when unscoped, remaining (`yolo-access-remaining`; env = "until the server stops"), End now (`yolo-end` → `endYolo`). No dismiss control. Below: `YoloActivationForm` with `allowUnscoped`, `base` = selected session cwd. See change: add-access-grant-dialog (tasks 8b.7, 8b.7a, 8b.7b) Body copy + planes line name the agent path gate. See change: yolo-covers-agent-path-gate. |
| `YoloActivation.tsx` | Shared YOLO activation for three entry points, one server session. Exports `YoloActivationForm({yolo, base, allowUnscoped?, afterActivate?, store?, onChanged?})`, `GrantDialogYoloOffer({subject})` (base = denial subject, no unscoped, says the request still needs an answer), `DirectoryYoloAction({cwd})` (own folder pre-selected). Roots only from `fetchYoloRoots(base)`, `roots[0]` pre-selected, no free text; durations only from `durationsMinutes`, shortest pre-selected; unscoped (`yolo-unscoped-option`, warning tokens + `border-2` + bold) never pre-selected. Live session → add mode (`data-mode="add"`, no durations, `yolo-add-note` "timer is unchanged", `yolo-add-root`); unscoped live → `yolo-covers-all`. `available=false` → every control rendered disabled + `yolo-unavailable-reason`, never hidden (10.54). After POST → `yoloStatus.refresh()`. See change: add-access-grant-dialog (tasks 8b.7a, 8b.7b) A new `base` resets the root choice (no stale root from the previous folder; review 9.5). `YoloPiTrustDisclosure` (in `ActivationBody`, so all 3 surfaces) shows `yolo-pi-trust-disclosure` "chosen roots are also trusted in pi until YOLO ends" iff `fetchPiTrustMirrorConfig().sources.yolo`. See change: mirror-dashboard-trust-live-remote. |
| `YoloIndicators.tsx` | Active-YOLO signals, undismissable, no live region, links to `/settings/access`. Exports `useYoloAppBar()` (sidebar `header-app-bar` props while live: orange `--severity-warning-bg` tint + inset `--accent-orange-text` underline, static `title`, `data-yolo`; no click handler, no 1 Hz tick, one `setTimeout` at expiry), `YoloBarLink` (first bar child, `absolute opacity-0`, revealed on focus; keyboard/AT path), `YoloComposerStrip({cwd})` (single-line `yolo-composer-strip` above the composer, scoped by `isCwdInYoloScope`; sentence link + time sibling `shrink-0 tabular-nums`, `∞` + sr-only `yolo.noExpiry` when never expiring), module-private `yoloCoverageSentence` (scope x expiry -> 4 static `yolo.coverage*` keys), helpers `useLiveYoloSession` (expired = inactive), `yoloRemainingLabel`, `yoloTone`. `useOpenAccess` leaves modified / non-primary clicks to the browser. `YoloPill` / `YoloSessionIndicator` removed (wrapped + grew rows, issue #898). Copy names agent + dashboard file access. Adds `YoloDockFooter({cwd,store})` (the dock's last line: `h-8` `flex-none`, same internals `YoloStripContent`, own tick, keeps `yolo-composer-strip` testid, no live region) and `useYoloLiveForCwd(cwd,store)` (no 1 Hz tick; lets the dock know it has content). `YoloComposerStrip` unchanged until folder home swaps to the dock (task 8.2). See change: add-access-grant-dialog (8b.7), yolo-covers-agent-path-gate, fix-yolo-indicator-overflow, add-composer-dock. |
| `__tests__/GrantPromptDialog.test.tsx` | allow-always carries the pi-trust choice. See change: mirror-dashboard-trust-to-pi. |
| `__tests__/PiTrustCheckbox.test.tsx` | #F1: checkbox defaults + consequence text in worktree-init, pin, Add Folder, grant prompt. See change: mirror-dashboard-trust-to-pi. #F2: `sources.worktreeInit` {t,f} → checkbox checked iff; "Trust until dashboard restarts" sends `piTrust` (scope session). No session-scope verdict in grant dialog → not tested there. See change: mirror-dashboard-trust-live-remote. |