# Task 3 Report: Add Install Detection and UI ## Status Implemented Task 3 only. The install hook detects standalone mode, Chromium's deferred install event, and iOS Safari instructions. The reusable prompt is rendered once in each required shell surface and uses the existing modal/button/icon patterns. ## Changed files - `src/lib/useInstallPrompt.ts`: added the browser-only install state hook, local deferred-event type, event listener lifecycle, install action, and dismissal behavior. - `src/components/InstallAppPrompt.tsx`: added the reusable Chromium/iOS install action and iOS instructions modal. - `src/index.css`: added `.safe-area-bottom` using `env(safe-area-inset-bottom)`. - `src/pages/LoginPage.tsx`: added one compact install action below the login form. - `src/components/AppShell.tsx`: added one install action to the mobile header. - `src/components/AdminShell.tsx`: added one install action to the mobile header. ## Verification ### Typecheck Command: ```text npm.cmd run typecheck ``` Output: ```text > agenda-pro@1.0.0 typecheck > tsc -b --noEmit ``` Exit code: `0`. ### Production build Command: ```text npm.cmd run build ``` Output summary: ```text Preflight OK: Node v26.4.0. vite v5.4.21 building for production... ✓ 2465 modules transformed. ✓ built in 4.08s ``` Exit code: `0`. ### Diff validation Command: ```text git diff --check ``` Result: no whitespace errors. Git emitted only existing LF/CRLF conversion notices for worktree files. ## Self-review - The hook uses a local `BeforeInstallPromptEvent` interface and does not add an unsafe global declaration. - `beforeinstallprompt` and `appinstalled` listeners are removed on unmount. - Standalone detection suppresses the CTA, and unsupported browsers remain unchanged. - iOS instructions are limited to Safari and the existing `Modal` owns backdrop/Escape close behavior. - The safe-area utility is applied only inside the iOS modal content. - Each shell renders exactly one prompt instance outside `SidebarContent`, avoiding duplicate listeners from the desktop/mobile sidebar duplication. - The accessible action text remains `Instalar AgendaMax` in every supported prompt. - No Task 4 tests or documentation changes were added. ## Concerns - No browser-level visual/manual check was run; verification was limited to the required typecheck, production build, and diff inspection. - `git diff --check` reports line-ending notices from the existing Windows worktree configuration, not whitespace failures. ## Preserved worktree changes Existing `.superpowers/sdd/*` changes and untracked `graphify-out/*` artifacts were not reverted or staged. ## Review Fixes - `src/lib/useInstallPrompt.ts`: added a ref-based in-flight guard so rapid clicks can invoke the native prompt only once. The deferred event is restored when `prompt()` or `userChoice` fails, the error is contained, and the state returns to `available` for retry. `appinstalled` now clears dismissal before setting `installed`. - `src/components/Modal.tsx`: added `aria-label="Cerrar"` to the shared icon-only close button. - `src/components/InstallAppPrompt.tsx`: added an explicit `compact` presentation. Header instances use a fixed icon button with a screen-reader label and tooltip, while Login keeps the full visible action label. - `src/components/AppShell.tsx` and `src/components/AdminShell.tsx`: enabled the compact prompt presentation in the narrow mobile headers. ## Review Fix Verification ### Typecheck Command: ```text npm.cmd run typecheck ``` Output: ```text > agenda-pro@1.0.0 typecheck > tsc -b --noEmit ``` Exit code: `0`. ### Production build Command: ```text npm.cmd run build ``` Output summary: ```text Preflight OK: Node v26.4.0. vite v5.4.21 building for production... ✓ 2465 modules transformed. ✓ built in 3.95s ``` Exit code: `0`. ### Diff inspection `git diff --check` reported no whitespace errors. The only messages were existing Windows LF/CRLF conversion notices. The implementation diff contains only the hook, shared prompt, shared modal accessibility, and two mobile header call-site fixes described above. ## Review Fix Self-Review - A second click while `installInFlight.current` is true returns immediately, including clicks occurring after React has scheduled the deferred-event state update. - Failed native prompt or choice promises no longer produce an unhandled rejection and restore the captured event for a retryable `available` state. - A later `appinstalled` event overrides any prior dismissal and returns the hook state as `installed`. - Both modal close mechanisms remain intact, and the icon-only close control now has an accessible name. - Header install controls are compact without hiding the action from assistive technology; the full accessible name remains `Instalar AgendaMax`. - No unrelated worktree files were modified or staged, and no Task 4 tests or documentation were added. ## Remaining Concerns - No browser-level visual/manual check was run; verification is limited to typecheck, production build, and source diff inspection. - The existing Windows worktree continues to emit LF/CRLF conversion notices during Git operations.