# Dialogs (v2) All modal/dialog overlays used throughout the application. v2 adds consistent loading and error states to all mutation-backed dialogs. **Surface level**: All dialogs render at Level 2 (`--popover`, 16% lightness in dark mode) with the `shadow-lg` token (light mode only). See theme.md surface hierarchy. **Max height**: All dialogs cap at `max-h-[85vh]` with internal scroll on the form/body region. The header and footer remain fixed. This prevents dialogs from overflowing on small screens or when content is unexpectedly long (e.g., a ProjectPicker with 20 repos). ### Source: `packages/web/src/components/CreateInitiativeDialog.tsx`, `packages/web/src/components/RegisterProjectDialog.tsx`, `packages/web/src/components/RefineSpawnDialog.tsx`, `packages/web/src/components/TaskDetailModal.tsx`, `packages/web/src/components/editor/DeleteSubpageDialog.tsx` --- ## v1 -> v2 Changes | Aspect | v1 | v2 | |--------|----|----| | Create Initiative: Branch field | Present (optional text input) | REMOVED (auto-generated on first execution dispatch) | | All dialogs: submit loading | Some had it, inconsistent | All show `[spinner] ing...` on submit button | | All dialogs: submit error | Some had it, inconsistent | All show red error text below form, above buttons | | All dialogs: sizing | One-size `max-w-lg` | Size class per dialog type (sm/md/lg) | | All dialogs: animation | Default shadcn (fade+zoom) | Documented: scale+fade enter, fade+scale exit | | All dialogs: focus management | Unspecified | Documented per dialog: initial focus, tab order, Esc | | All dialogs: backdrop behavior | Unspecified | Close-on-backdrop per type (safe for confirmations, guarded for forms) | | All dialogs: form validation | Submit-only | Inline validation on blur + submit | --- ## Create Initiative Dialog **Trigger**: "New Initiative" button on `/initiatives` **Size**: `max-w-lg` (512px) — needs room for ProjectPicker list. This is deliberately wider than Register Project (`max-w-md`) because the ProjectPicker checkbox rows need horizontal space for project name + truncated URL side by side. Do NOT widen further — if the project list exceeds 6 items, the ProjectPicker scrolls internally (`max-h-[200px] overflow-y-auto`). **Backdrop**: Click outside closes ONLY if form is pristine. If any field is dirty, show `window.confirm("Discard unsaved changes?")`. ### Focus management - **Open**: `autoFocus` on Name input - **Tab order**: Name -> Execution Mode -> Project checkboxes -> Register link -> (Advanced toggle if present) -> Cancel -> Create - **Escape**: Same behavior as backdrop click (close if pristine, confirm if dirty) ### Default state ``` +----------------------------------------------------------+ | Create Initiative [x] | | | | Name * | | [Auth System Overhaul___________________________] | | | | Execution Mode | | [ Review v ] | | | | Projects | | +----------------------------------------------------+ | | | [x] backend github.com/org/backend-api | | | | [ ] frontend github.com/org/frontend-app | | | | [ ] shared github.com/org/shared-lib | | | | + Register new project | | | +----------------------------------------------------+ | | | | [ Cancel ] [ Create ] | +----------------------------------------------------------+ ``` Note: Branch field has been removed from the default view. Branches are auto-generated (`cw/`) on first execution task dispatch. See auto-branch system in architecture docs. ### Advanced section (collapsed by default) Power users can override the auto-generated branch name via a collapsible "Advanced" section. The toggle sits below the Projects picker. ``` | +----------------------------------------------------+ | | | [x] backend github.com/org/backend-api | | | | [ ] frontend github.com/org/frontend-app | | | | + Register new project | | | +----------------------------------------------------+ | | | | [v] Advanced | | +----------------------------------------------------+ | | | Branch Name (auto-generated if blank) | | | | [cw/auth-system-overhaul_________________________] | | | +----------------------------------------------------+ | | | | [ Cancel ] [ Create ] | ``` - Toggle: `text-sm text-muted-foreground` with chevron icon (ChevronRight rotates to ChevronDown). Use shadcn `` + `` + `` — NOT a custom disclosure widget. The Collapsible primitive handles aria-expanded and content animation. - Container: `border rounded-md p-3 bg-muted/30` — visually recessed - Branch input: pre-filled with `cw/` as placeholder, editable. The placeholder updates live as the user types in the Name field (slugify on change). - Collapsed by default — 99% of users should not touch this - When collapsed, the section occupies a single line (~28px). The dialog height does NOT jump when toggled — use `` with `animate-collapsible-down` / `animate-collapsible-up` (already defined in shadcn's Collapsible animation presets). ### Submitting state ``` +----------------------------------------------------------+ | Create Initiative [x] | | | | Name * | | [Auth System Overhaul___________________________] | | | | Execution Mode | | [ Review v ] | | | | Projects | | +----------------------------------------------------+ | | | [x] backend github.com/org/backend-api | | | | [ ] frontend github.com/org/frontend-app | | | +----------------------------------------------------+ | | | | [ Cancel ] [ [spinner] Creating... ] | +----------------------------------------------------------+ ``` - `[ Create ]` button replaced with `[ [spinner] Creating... ]` — uses `` - Button is `disabled` during mutation — both `disabled` prop AND `pointer-events-none` (belt and suspenders) - `[ Cancel ]` remains enabled but closing the dialog is prevented - `[x]` close button: hidden during mutation (not just disabled — reduces visual noise) - Backdrop click and Escape are both suppressed during mutation via `onInteractOutside` / `onEscapeKeyDown` ### Error state ``` +----------------------------------------------------------+ | Create Initiative [x] | | | | Name * | | [Auth System Overhaul___________________________] | | | | Execution Mode | | [ Review v ] | | | | Projects | | +----------------------------------------------------+ | | | [x] backend github.com/org/backend-api | | | +----------------------------------------------------+ | | | | Failed to create initiative. | | [ Cancel ] [ Create ] | +----------------------------------------------------------+ ``` - Error text: `text-sm text-destructive`, positioned between form fields and footer buttons - Cleared on next submit attempt - For field-specific errors (e.g., "Name already exists"), show inline below the offending field instead of the generic error position. Generic server errors stay above buttons. ### Form validation | Field | Rule | Fires on | Inline message | |-------|------|----------|----------------| | Name | Required, non-empty after trim | blur + submit | "Name is required" | | Name | Unique across initiatives | submit (server) | "An initiative with this name already exists" | | Execution Mode | Always valid (has default) | -- | -- | | Projects | Optional, no validation | -- | -- | | Branch (Advanced) | Optional, valid slug chars | blur | "Branch name can only contain a-z, 0-9, /, -" | Inline error styling: `text-xs text-destructive mt-1` below the input. Input border changes to `border-destructive`. **Fields**: - Name (required text input, `autoFocus`) - Execution Mode (select: Review per Phase / YOLO) - Projects (ProjectPicker: checkbox list + register link) - Branch (optional, inside Advanced collapsible) **Source**: `packages/web/src/components/CreateInitiativeDialog.tsx` --- ## Register Project Dialog **Trigger**: "Register Project" button on `/settings/projects` or "+ Register new project" in ProjectPicker **Size**: `max-w-md` (448px) — simpler form, fewer fields than Create Initiative **Backdrop**: Click outside closes only if form is pristine. Dirty form triggers `window.confirm("Discard unsaved changes?")`. ### Focus management - **Open**: `autoFocus` on Project Name input - **Tab order**: Project Name -> Repository URL -> Default Branch -> Cancel -> Register - **Escape**: Same as backdrop click ### Default state ``` +----------------------------------------------------------+ | Register Project [x] | | | | Project Name * | | [backend-api_______________________________________] | | | | Repository URL * | | [https://github.com/org/backend-api________________] | | | | Default Branch | | [main______________________________________________] | | | | [ Cancel ] [ Register ] | +----------------------------------------------------------+ ``` ### Submitting state ``` | [ Cancel ] [ [spinner] Registering... ] | ``` ### Error state ``` | Failed to register project. | | [ Cancel ] [ Register ] | ``` ### Branch auto-detection After the user enters a valid-looking Repository URL and the input loses focus, fire a debounced (500ms) request to detect the default branch. This avoids users guessing wrong (some repos use `master`, `develop`, `trunk`). ``` | Repository URL * | | [https://github.com/org/backend-api________________] | | | | Default Branch | | [[spinner] Detecting...______________________________] | ``` States: - **Idle**: Input shows "main" as default value, editable - **Detecting**: Spinner inside input, field temporarily `readonly`, `text-muted-foreground`. Submit button is NOT disabled during detection — user can submit with the current branch value. - **Detected**: Value replaced with detected branch (e.g., "master"), field editable again. If user has already manually edited the branch field before detection completes, do NOT overwrite — user intent wins. Track a `branchManuallyEdited` boolean. - **Detection failed**: Keep current value, show subtle hint: `text-xs text-muted-foreground mt-1` — "Could not auto-detect branch. Using current value." - **Cancelled**: If the user modifies the URL while a detection request is in flight, cancel the previous request (`AbortController`) and start a new debounce cycle. Trigger conditions for detection: - URL field loses focus AND URL has changed since last detection - URL matches a basic pattern: starts with `http://`, `https://`, or `git@` - Do NOT fire on every keystroke — only on blur with 500ms debounce Implementation note: Detection requires a server-side tRPC procedure (e.g., `detectDefaultBranch`) that runs `git ls-remote --symref HEAD`. This is a nice-to-have; ship without it first, add later. The wireframe accounts for both states. The procedure MUST have a 5-second timeout — private repos with bad auth will hang `git ls-remote` indefinitely. ### Form validation | Field | Rule | Fires on | Inline message | |-------|------|----------|----------------| | Project Name | Required, non-empty after trim | blur + submit | "Project name is required" | | Project Name | Unique | submit (server) | "A project with this name already exists" | | Repository URL | Required, non-empty | blur + submit | "Repository URL is required" | | Repository URL | Valid URL format | blur | "Enter a valid git repository URL" | | Repository URL | Unique | submit (server) | "This repository is already registered" | | Default Branch | Non-empty (has default "main") | blur | "Branch name is required" | **Fields**: - Project Name (required, unique) - Repository URL (required, unique, git repo URL) - Default Branch (text input, defaults to "main", auto-detected when possible) **Source**: `packages/web/src/components/RegisterProjectDialog.tsx` --- ## Refine Spawn Dialog **Trigger**: "Refine with Agent" button in Content tab, or "Retry" button after agent crash **Size**: `max-w-md` (448px) — single textarea, compact dialog **Backdrop**: Click outside always closes (no data loss risk — instructions are optional, low-cost to retype) ### Focus management - **Open**: `autoFocus` on Instructions textarea - **Tab order**: Instructions textarea -> Cancel -> Start - **Escape**: Closes dialog immediately ### Default state ``` +----------------------------------------------------------+ | Refine Content [x] | | | | Instructions (optional) | | +----------------------------------------------------+ | | | Focus on the authentication flow section. | | | | Add more detail about the OAuth providers we | | | | need to support. | | | | | | | +----------------------------------------------------+ | | | | [ Cancel ] [ Start ] | +----------------------------------------------------------+ ``` ### Submitting state ``` | [ Cancel ] [ [spinner] Starting... ] | ``` ### Error state ``` | Failed to start agent. | | [ Cancel ] [ Start ] | ``` **IMPLEMENTATION BUG**: The current `RefineSpawnDialog.tsx` renders `{error && ...}` AFTER ``, placing the error text below the buttons where it is easy to miss. Fix: move the error rendering ABOVE `` to match the pattern used by all other dialogs. See lines 116-120 of the current implementation. **Fields**: - Instructions (optional textarea, free-form guidance for the agent, 3 rows default, auto-expands up to 8 rows via `min-h-[80px] max-h-[200px] resize-y`) **Source**: `packages/web/src/components/RefineSpawnDialog.tsx` --- ## Task Detail Modal **Trigger**: Click any task row in Plan tab or Execution tab **Size**: `max-w-xl` (576px) — wider to accommodate metadata grid + dependency lists without cramping **Backdrop**: Click outside closes IF no inline edit is active. If the description textarea is open with unsaved changes, show `window.confirm("Discard description changes?")`. If the textarea is open but content hasn't changed, close immediately. ### Focus management - **Open**: Focus the dialog container itself (metadata is read-only, description edit is opt-in) - **Tab order**: Description area (if pending task) -> Queue Task -> Stop Task -> Close (X) button - **Escape**: Closes dialog if no inline edit is active. If description textarea is open, first Escape cancels the edit (reverts text), second Escape closes the dialog. ### Default state ``` +----------------------------------------------------------+ | Implement PKCE Flow [x] | | | | +------------------------+-------------------------+ | | | Status [IN_PROGRESS]| Priority normal | | | | Phase 2. OAuth Flow| Type execute | | | | Agent blue-fox-7 | | | +------------------------+-------------------------+ | | | | Description | | Implement the OAuth 2.0 PKCE extension for public | | clients. Generate code verifier/challenge pair, | | include in authorization request, and validate | | during token exchange. | | | | Dependencies | | * Set up OAuth routes [DONE] | | | | Blocks | | * GitHub provider adapter [BLOCKED] | | * Microsoft provider adapter [PENDING] | | | | [ Queue Task ] [ Stop Task ] | +----------------------------------------------------------+ ``` ### Queue in progress ``` | [ [spinner] Queuing... ] [ Stop Task ] | ``` ### Stop in progress ``` | [ Queue Task ] [ [spinner] Stopping... ] | ``` ### Error state ``` | Failed to queue task. Try again. | | [ Queue Task ] [ Stop Task ] | ``` **Fields** (read-only with inline edit affordances): - 2-column metadata grid: Status (with badge), Priority, Phase, Type, Agent - Description text — **click to edit** (pencil icon on hover, expands to textarea, saves on blur/Enter) - Dependencies list (with StatusDot per dependency) - Blocks list (dependents with StatusDot) ### Inline description editing The Description section shows a subtle edit affordance on hover: ``` | Description [pencil] | | Implement the OAuth 2.0 PKCE extension for public | | clients. Generate code verifier/challenge pair... | ``` On click, the text block becomes an auto-sizing `