diff --git a/tasks/shieldai-unified-restructure/01-project-foundation-cleanup.md b/tasks/shieldai-unified-restructure/01-project-foundation-cleanup.md
new file mode 100644
index 0000000..ed12af6
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/01-project-foundation-cleanup.md
@@ -0,0 +1,65 @@
+# 01. Project Foundation — Root Config & Directory Cleanup
+
+meta:
+ id: shieldai-unified-restructure-01
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: []
+ tags: [infrastructure, cleanup, foundation]
+
+objective:
+- Establish a clean project root with unified tooling, removing the fragmented packages/ + services/ split and preparing the workspace for a single monolithic web app plus native mobile projects.
+
+deliverables:
+- Updated root `package.json` with single workspace entry `web/`
+- Updated `pnpm-workspace.yaml` to include `web/`, `iOS/ShieldAI`, `android/`, `browser-ext/`
+- Updated `turbo.json` with tasks for web, browser-ext
+- Remove or archive legacy `packages/`, `services/`, `server/` directories
+- Ensure `web/` is the primary application workspace
+- Root-level shared config files (`.nvmrc`, `.editorconfig`, `tsconfig.base.json` cleanup)
+
+steps:
+1. Backup `packages/`, `services/`, `server/` to a dated archive branch or tar.gz before removal.
+2. Update root `package.json`:
+ - Remove `workspaces` field referencing `packages/*` and `services/*`
+ - Add `workspaces: ["web", "browser-ext"]` or use pnpm workspace syntax
+ - Update scripts to reference `web/` directly (e.g., `"dev": "pnpm --filter web dev"`)
+3. Update `pnpm-workspace.yaml`:
+ ```yaml
+ packages:
+ - "web"
+ - "browser-ext"
+ ```
+4. Update `turbo.json`:
+ - Remove package-specific build outputs that no longer apply
+ - Ensure `web#build`, `web#dev`, `web#test` are defined
+ - Add `browser-ext#build` if needed
+5. Clean root of obsolete files:
+ - `check-identity.js`, `test-classifier.ts`, `test-maxpayload.ts`, `test-ws-maxpayload*.js` (move to web/ if still needed)
+ - `docker-compose*.yml`, `Dockerfile` (will be recreated in task 42)
+ - `infra/`, `load-tests/`, `docs/` (evaluate if content should move to web/docs or archive)
+6. Verify `web/` has its own `package.json`, `vite.config.ts`, `tsconfig.json`, and can run independently.
+7. Run `pnpm install` from root to regenerate lockfile and verify workspace resolves correctly.
+8. Run `pnpm dev` from root and confirm web app starts on expected port.
+
+tests:
+- Unit: N/A (infrastructure task)
+- Integration: `pnpm install` completes without workspace errors; `pnpm dev` starts web app; `pnpm build` in web/ succeeds
+- E2E: N/A
+
+acceptance_criteria:
+- [ ] `pnpm-workspace.yaml` only references `web` and `browser-ext`
+- [ ] `pnpm install` from root resolves dependencies cleanly
+- [ ] `pnpm dev` starts the web app without errors
+- [ ] `packages/`, `services/`, and `server/` are no longer in the working tree (archived or deleted)
+- [ ] No broken references to removed directories in root config files
+
+validation:
+- Run `ls packages/ services/ server/` and confirm "No such file or directory" or they are empty placeholders
+- Run `pnpm dev` and verify `http://localhost:3000` responds
+- Run `pnpm build` inside `web/` and verify output in `web/.output/` or `web/dist/`
+
+notes:
+- Do NOT delete git history. Use `git mv` where possible, or archive via `git branch archive/legacy-$(date +%Y%m%d)` before deleting.
+- The `web/` directory already exists (was `redux/`) and contains a SolidStart scaffold. We build from there.
+- `iOS/ShieldAI` and `android/` are native projects and do not participate in the pnpm workspace; they are tracked as sibling directories.
diff --git a/tasks/shieldai-unified-restructure/02-theme-system-brand-palette.md b/tasks/shieldai-unified-restructure/02-theme-system-brand-palette.md
new file mode 100644
index 0000000..a50fbe7
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/02-theme-system-brand-palette.md
@@ -0,0 +1,69 @@
+# 02. Theme System — Auto-Shifting CSS with ShieldAI Brand Palette
+
+meta:
+ id: shieldai-unified-restructure-02
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-01]
+ tags: [frontend, design-system, css, theme]
+
+objective:
+- Create a comprehensive, auto-shifting CSS theme system for the web app using Tailwind v4 `@theme` and CSS custom properties with `@property` animations, adapted from the Lendair reference but using a ShieldAI-appropriate brand palette.
+
+deliverables:
+- `web/src/app.css` — Complete theme file with:
+ - `@property` declarations for all animatable color tokens
+ - `:root` light-mode color tokens (backgrounds, text, brand, borders, semantic, glass)
+ - `@media (prefers-color-scheme: dark)` dark-mode overrides
+ - `:root.light` and `:root.dark` manual override classes
+ - `@theme` block registering all tokens for Tailwind v4
+ - Base styles (`html`, `body`, `*:focus-visible`)
+ - Utility classes (gradients, glassmorphism, gradient text, glow shadows, dot-grid background, noise texture)
+- Global background dot-grid pattern and smooth 500ms color transitions
+- Typography scale using Inter font family
+- Spacing and radius scale aligned with Lendair's design
+
+steps:
+1. Copy the Lendair `app.css` structure from `~/code/Lendair/web/src/app.css` as the scaffold.
+2. Replace the brand color tokens with ShieldAI palette:
+ - Primary: deep indigo `#4f46e5` (brand-primary), light `#818cf8` (brand-primary-light), dark `#4338ca` (brand-primary-dark)
+ - Accent: electric cyan `#06b6d4` (brand-accent), light `#67e8f9` (brand-accent-light), dark `#0891b2` (brand-accent-dark)
+ - Alert/Danger: amber `#f59e0b` (warning), red `#ef4444` (error)
+ - Keep neutral grays from Lendair for backgrounds and text
+3. Add `@property` declarations for every custom property that should animate smoothly.
+4. Define `@theme` block with all tokens so Tailwind v4 utility classes resolve correctly.
+5. Implement `prefers-color-scheme: dark` media query with full dark palette.
+6. Add `.light` and `.dark` classes on `:root` for manual JS toggling.
+7. Define utility classes:
+ - `.gradient-primary`, `.gradient-accent`, `.gradient-subtle`, `.gradient-card`
+ - `.glass`, `.glass-dark`
+ - `.text-gradient-primary`, `.text-gradient-accent`
+ - `.shadow-glow-primary`, `.shadow-glow-accent`
+ - `.bg-dot-grid`, `.bg-noise`
+8. Set global `background-color`, `background-image` (dot-grid), and `transition` on `:root` and `body`.
+9. Import the CSS in `web/src/entry-client.tsx` or `web/src/app.tsx` (whichever is the root).
+10. Create a small `useTheme()` hook in `web/src/lib/theme.ts` that detects `prefers-color-scheme`, exposes `theme` signal, and toggles `.light`/`.dark` on `document.documentElement`.
+
+tests:
+- Unit: `useTheme()` hook returns correct initial theme based on media query; toggling updates class and signal
+- Visual: Open app in light and dark OS modes; verify all colors shift smoothly over 500ms
+- Visual: Toggle theme manually via `useTheme()`; verify override persists across reloads (localStorage)
+
+acceptance_criteria:
+- [ ] All color tokens animate smoothly between light/dark modes
+- [ ] Tailwind utilities like `text-brand-primary`, `bg-bg-secondary`, `border-border` work correctly
+- [ ] Manual theme toggle (light/dark/system) functions and persists
+- [ ] Dot-grid background pattern renders correctly in both modes
+- [ ] No FOUC (flash of unstyled content) on initial load
+
+validation:
+- Open `http://localhost:3000` in browser DevTools
+- Toggle OS dark mode; verify background, text, and card colors shift
+- Run `document.documentElement.classList.add('dark')` in console; verify immediate dark mode
+- Check computed styles for `--color-bg` and confirm it matches token values
+
+notes:
+- Reference: `~/code/Lendair/web/src/app.css` — this is the exact structure we want, just with ShieldAI colors.
+- Tailwind v4 `@theme` is compile-time; changing tokens requires rebuild. Runtime theme switching uses CSS custom properties.
+- The `transition` on `:root` and `body` must include `background-color` and `color` for smooth shifts.
+- Consider adding a `theme-color` meta tag that updates with the theme for mobile browsers.
diff --git a/tasks/shieldai-unified-restructure/03-ui-primitive-library.md b/tasks/shieldai-unified-restructure/03-ui-primitive-library.md
new file mode 100644
index 0000000..a0d9a5a
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/03-ui-primitive-library.md
@@ -0,0 +1,82 @@
+# 03. UI Primitive Library — Button, Card, Input, Badge, Modal, Toast
+
+meta:
+ id: shieldai-unified-restructure-03
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-01, shieldai-unified-restructure-02]
+ tags: [frontend, components, design-system, solidjs]
+
+objective:
+- Build a reusable set of UI primitive components in SolidJS that match the ShieldAI theme and are used across all pages and features. These should feel cohesive with the Lendair component style but adapted for ShieldAI's security-focused brand.
+
+deliverables:
+- `web/src/components/ui/Button.tsx` — Variants: primary, secondary, ghost, danger. Sizes: sm, md, lg. States: disabled, loading.
+- `web/src/components/ui/Card.tsx` — Container with gradient-card background, border, padding, and optional header/footer slots.
+- `web/src/components/ui/Input.tsx` — Text input with label, error state, helper text, and focus ring. Types: text, email, password, number.
+- `web/src/components/ui/Badge.tsx` — Status badges with variants: default, success, warning, error, info.
+- `web/src/components/ui/Modal.tsx` — Accessible dialog with overlay, close button, focus trap, and animation.
+- `web/src/components/ui/Toast.tsx` — Toast notification system (single component + `ToastProvider` context) with auto-dismiss, positions, and variants.
+- `web/src/components/ui/index.ts` — Barrel export for all primitives.
+- `web/src/components/ui/ui.test.ts` — Unit tests for each primitive.
+
+steps:
+1. Create `web/src/components/ui/` directory.
+2. **Button**:
+ - Props: `variant`, `size`, `disabled`, `loading`, `onClick`, `children`, `class` (merge with base)
+ - Primary: `.gradient-primary` bg, white text, `.shadow-glow-primary`
+ - Secondary: transparent bg, border, primary text
+ - Ghost: transparent, no border, text only
+ - Danger: red gradient bg
+ - Loading state shows spinner SVG and disables interaction
+3. **Card**:
+ - Props: `children`, `class`, `header?`, `footer?`
+ - Uses `.gradient-card`, `.border-border/50`, rounded-xl
+4. **Input**:
+ - Props: `label`, `type`, `value`, `onInput`, `error`, `helperText`, `placeholder`
+ - Styled with bg-transparent, border, focus ring using `--color-focus-ring`
+ - Error state adds red border and error text
+5. **Badge**:
+ - Props: `variant`, `children`
+ - Small rounded pill with semantic color backgrounds
+6. **Modal**:
+ - Props: `isOpen`, `onClose`, `title`, `children`, `size?`
+ - Uses SolidJS `Portal` for rendering outside DOM tree
+ - Backdrop click closes; Escape key closes
+ - Focus trap inside modal content
+ - Enter/exit CSS transitions
+7. **Toast**:
+ - Create `ToastContext` with `showToast({ message, variant, duration })` API
+ - `ToastProvider` renders a fixed-position container
+ - Individual toasts auto-dismiss after duration (default 4s)
+ - Variants: success, error, warning, info
+8. Write unit tests for each component using Vitest + Testing Library.
+9. Create barrel export `index.ts`.
+
+steps:
+- Unit: Each component renders correctly with default props
+- Unit: Button variants produce correct CSS classes
+- Unit: Input error state renders error message
+- Unit: Modal open/close toggles visibility and calls onClose
+- Unit: Toast context show/dismiss works and auto-dismiss fires
+- Visual: Inspect all components in Storybook-style page (optional) or directly in landing page
+
+acceptance_criteria:
+- [ ] All 6 primitives exist in `web/src/components/ui/` with barrel export
+- [ ] Each primitive has full TypeScript types
+- [ ] Button supports all 4 variants, 3 sizes, disabled, and loading states
+- [ ] Input supports error states and focus rings
+- [ ] Modal is accessible (focus trap, ESC to close, backdrop click)
+- [ ] Toast system can queue multiple toasts and auto-dismiss
+- [ ] All components use theme tokens (no hardcoded colors)
+- [ ] Unit tests pass with >80% coverage
+
+validation:
+- `cd web && pnpm test` runs and passes all UI tests
+- Manual inspection: create a temporary `/ui-test` route importing all primitives and verify visual appearance in both light/dark modes
+
+notes:
+- Reference Lendair components: `~/code/Lendair/web/src/components/ui/Button.tsx`, `Card.tsx`, `Input.tsx`, `Modal.tsx`, `Toast.tsx`
+- Keep components uncontrolled where possible (SolidJS signals for state) to avoid prop drilling.
+- The `class` prop should always merge with base classes using a utility like `clsx` or `tailwind-merge`. Install if not present.
+- Do NOT add business logic to primitives — they are purely presentational.
diff --git a/tasks/shieldai-unified-restructure/04-layout-components.md b/tasks/shieldai-unified-restructure/04-layout-components.md
new file mode 100644
index 0000000..5e5d809
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/04-layout-components.md
@@ -0,0 +1,89 @@
+# 04. Layout Components — Navbar, Footer, PageContainer, AppShell
+
+meta:
+ id: shieldai-unified-restructure-04
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-01, shieldai-unified-restructure-02, shieldai-unified-restructure-03]
+ tags: [frontend, layout, navigation, solidjs]
+
+objective:
+- Build the structural layout components that wrap every page: a responsive navbar with navigation and auth state, a footer, a page container for consistent padding/max-width, and an AppShell that composes them all with the theme background.
+
+deliverables:
+- `web/src/components/layout/Navbar.tsx` — Responsive nav with:
+ - ShieldAI logo/wordmark (SVG)
+ - Navigation links: Features, Pricing, Blog, Dashboard
+ - Auth buttons: Sign In (secondary), Get Started (primary)
+ - Mobile hamburger menu with slide-out drawer
+ - Theme toggle button (light/dark/system)
+ - Scroll-aware background blur/glass effect when scrolled
+- `web/src/components/layout/Footer.tsx` — Multi-column footer with:
+ - Logo and tagline
+ - Link columns: Product, Company, Resources, Legal
+ - Social links (GitHub, Twitter/X, LinkedIn placeholders)
+ - Copyright and privacy/terms links
+- `web/src/components/layout/PageContainer.tsx` — Wrapper with:
+ - `max-width` and centered layout
+ - Responsive horizontal padding (`px-4 md:px-6 lg:px-8`)
+ - Optional vertical padding prop
+ - `class` prop for additional overrides
+- `web/src/components/layout/AppShell.tsx` — Root layout component:
+ - Renders ` `, `{children} `, ``
+ - Applies dot-grid background to main content area
+ - Handles scroll-to-top on route change
+ - Wraps with `MetaProvider` and `Title`
+- `web/src/components/layout/index.ts` — Barrel export.
+
+steps:
+1. Create `web/src/components/layout/` directory.
+2. **Navbar**:
+ - Use SolidJS `createSignal` for mobile menu open state
+ - Use `onMount` + scroll listener to toggle `scrolled` class for glass effect
+ - Logo: create a simple SVG shield icon with gradient fill (reference Lendair's logo approach)
+ - Links use `` from `@solidjs/router` for SPA navigation
+ - Theme toggle uses `useTheme()` hook from task 02
+ - Auth buttons conditionally render based on auth state (stub for now, wire in task 23)
+3. **Footer**:
+ - Grid layout: 4 columns on desktop, 2 on tablet, 1 on mobile
+ - Link groups as data arrays mapped with ``
+ - Bottom bar with copyright text
+4. **PageContainer**:
+ - Simple wrapper `div` with `max-w-7xl mx-auto` and padding classes
+ - Accept `class` prop and merge with defaults
+5. **AppShell**:
+ - Import `MetaProvider`, `Title` from `@solidjs/meta`
+ - Import `Navbar`, `Footer`
+ - Render children inside `` with `min-h-screen`
+ - Add `onCleanup` to remove scroll listener if added in Navbar
+ - Use SolidStart `root` pattern: this becomes the `root` component passed to ``
+6. Wire AppShell into `web/src/app.tsx` as the Router root.
+7. Create barrel export.
+
+steps:
+- Unit: Navbar renders all links; mobile menu toggles
+- Unit: Footer renders all link columns
+- Unit: PageContainer applies correct max-width and padding classes
+- Unit: AppShell renders Navbar + children + Footer in correct order
+- Visual: Resize browser to verify responsive breakpoints
+
+acceptance_criteria:
+- [ ] Navbar is sticky/fixed at top with glass effect on scroll
+- [ ] Mobile hamburger menu opens/closes smoothly
+- [ ] Theme toggle button changes theme immediately
+- [ ] Footer is responsive and all links are clickable
+- [ ] PageContainer centers content with consistent padding
+- [ ] AppShell wraps all routes correctly via SolidStart `root`
+- [ ] No layout shift on initial load
+
+validation:
+- `cd web && pnpm test` passes layout tests
+- Open `http://localhost:3000` and verify navbar/footer render on all routes
+- Test mobile viewport (<768px) and verify hamburger menu works
+- Toggle theme and verify navbar/footer colors shift correctly
+
+notes:
+- Reference Lendair: `~/code/Lendair/web/src/components/layout/Navbar.tsx`, `Footer.tsx`, `PageContainer.tsx`
+- The AppShell replaces the current `app.tsx` Router root. Make sure ` ` still works inside it.
+- For auth state in Navbar, create a stub `useAuth()` hook that returns `{ isAuthenticated: false }` for now; it will be wired to tRPC in task 23.
+- Keep the Navbar height consistent to avoid layout shift when the glass effect activates.
diff --git a/tasks/shieldai-unified-restructure/05-landing-page-hero.md b/tasks/shieldai-unified-restructure/05-landing-page-hero.md
new file mode 100644
index 0000000..5aaf063
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/05-landing-page-hero.md
@@ -0,0 +1,75 @@
+# 05. Landing Page — Hero Section with Animated Background
+
+meta:
+ id: shieldai-unified-restructure-05
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-02, shieldai-unified-restructure-03, shieldai-unified-restructure-04]
+ tags: [frontend, landing-page, animation, solidjs]
+
+objective:
+- Build the hero section of the ShieldAI landing page, inspired by Lendair's landing page structure but with ShieldAI-specific content and an animated gradient/mesh background that evokes AI and security.
+
+deliverables:
+- `web/src/components/landing/HeroSection.tsx` — Hero component with:
+ - Animated background (gradient mesh or wave effect using CSS/Canvas)
+ - ShieldAI logo/icon centered above headline
+ - Headline: "AI-Powered Identity Protection for Everyone"
+ - Subheadline explaining the value proposition
+ - Two CTAs: "Get Started" (primary) and "Learn More" (secondary ghost)
+ - Trust indicators: "No credit card required", "Free tier available"
+- `web/src/components/landing/ColorWaveBackground.tsx` — Reusable animated background component:
+ - Canvas-based or CSS-based flowing gradient mesh
+ - Configurable colors (primary brand palette)
+ - Respects `prefers-reduced-motion`
+ - Performance-optimized (requestAnimationFrame, no layout thrashing)
+- `web/src/routes/index.tsx` — Landing page route composing HeroSection + background
+
+steps:
+1. Create `web/src/components/landing/` directory.
+2. **ColorWaveBackground**:
+ - Option A (recommended): Port Lendair's `ColorWaveBackground.tsx` if it exists, or create a CSS-only solution using `@keyframes` on radial gradients.
+ - Option B: Use a `` with a simple mesh gradient animation.
+ - Colors should flow between indigo `#4f46e5`, cyan `#06b6d4`, and deep slate `#0f172a`.
+ - Add `prefers-reduced-motion` check that freezes animation.
+ - Expose props: `yOffset`, `scale`, `speed` (like Lendair's API).
+3. **HeroSection**:
+ - Centered text layout inside `PageContainer`
+ - Logo: shield SVG with `.gradient-primary` background circle and `.shadow-glow-primary`
+ - Headline uses `.text-gradient-primary` for visual impact
+ - Subheadline uses `text-text-secondary` with `text-xl`
+ - CTA buttons use the `Button` primitive (primary + ghost variants)
+ - Small text below buttons for trust signals
+ - Add subtle entrance animation (fade-in + translate-y) on mount
+4. Compose in `web/src/routes/index.tsx`:
+ - Import `ColorWaveBackground`, `HeroSection`, `Title` from meta
+ - Set `ShieldAI — AI-Powered Identity Protection `
+ - Render background first (absolute or fixed), then HeroSection with `relative z-10`
+5. Verify responsive behavior: text scales down on mobile, buttons stack vertically.
+
+steps:
+- Unit: HeroSection renders headline and CTAs
+- Unit: ColorWaveBackground respects reduced-motion preference
+- Visual: Animation runs smoothly at 60fps in Chrome DevTools performance panel
+- Accessibility: Verify no seizure-inducing flashes; reduced motion works
+
+acceptance_criteria:
+- [ ] Hero section displays ShieldAI headline, subheadline, and two CTAs
+- [ ] Animated background runs smoothly without jank
+- [ ] Background respects `prefers-reduced-motion`
+- [ ] All text uses theme tokens and shifts correctly in dark mode
+- [ ] Responsive: mobile shows stacked layout with readable text
+- [ ] CTA buttons are clickable and navigate to correct routes ("/signup", "#features")
+
+validation:
+- Open `http://localhost:3000`
+- Verify hero renders with animated background
+- Toggle dark mode; verify text remains readable against background
+- Enable "Reduce motion" in OS settings; verify animation pauses
+- Run Lighthouse audit; verify no performance regressions from background animation
+
+notes:
+- Reference Lendair's `~/code/Lendair/web/src/routes/index.tsx` hero section for structure and copy patterns.
+- The background animation is decorative — it should never interfere with text readability. Use `z-index` and `backdrop-blur` if needed.
+- If using Canvas, remember to handle `devicePixelRatio` for crisp rendering on high-DPI screens.
+- Keep the animation lightweight; avoid Three.js unless absolutely necessary (it adds significant bundle size).
diff --git a/tasks/shieldai-unified-restructure/06-landing-page-features.md b/tasks/shieldai-unified-restructure/06-landing-page-features.md
new file mode 100644
index 0000000..9c8be7a
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/06-landing-page-features.md
@@ -0,0 +1,95 @@
+# 06. Landing Page — Features, How It Works, CTA Sections
+
+meta:
+ id: shieldai-unified-restructure-06
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-02, shieldai-unified-restructure-03, shieldai-unified-restructure-04, shieldai-unified-restructure-05]
+ tags: [frontend, landing-page, solidjs]
+
+objective:
+- Complete the landing page by adding the remaining content sections: How It Works (step timeline), Platform Features (6-card grid), For Users split panel, Why ShieldAI value props, and a final CTA banner. These should match Lendair's section structure and visual quality.
+
+deliverables:
+- `web/src/components/landing/HowItWorksSection.tsx` — 3-step staggered timeline:
+ - Step 1: "Enroll Your Identity" — Sign up and add emails, phones, family members
+ - Step 2: "We Monitor 24/7" — Dark web scans, voiceprint detection, spam filtering
+ - Step 3: "Get Instant Alerts" — Real-time notifications when threats are detected
+ - Alternating left/right layout on desktop, stacked on mobile
+ - Numbered circles with `.gradient-primary` and `.shadow-glow-primary`
+- `web/src/components/landing/FeaturesGridSection.tsx` — 6 feature cards:
+ - DarkWatch: Dark web monitoring
+ - VoicePrint: AI voice clone detection
+ - SpamShield: Spam & scam call blocking
+ - HomeTitle: Property fraud alerts
+ - RemoveBrokers: Data broker removal
+ - Family Plans: Protect your whole household
+ - Each card: icon, title, description, in `.gradient-card` with border
+- `web/src/components/landing/ForUsersSection.tsx` — Split panel:
+ - Left: "For Individuals" — personal protection features
+ - Right: "For Families" — family group management, shared alerts
+ - Each panel: icon, heading, description, bullet list with checkmarks
+- `web/src/components/landing/WhyShieldAISection.tsx` — 3 value prop cards:
+ - "Proactive, Not Reactive" — detect threats before damage
+ - "AI-Powered Detection" — ML models trained on real scam data
+ - "Privacy First" — encrypted data, no selling, GDPR/CCPA compliant
+ - Each with title, description, and bullet points with check icons
+- `web/src/components/landing/CTABannerSection.tsx` — Final call-to-action:
+ - Gradient card with headline "Ready to protect your identity?"
+ - Subtext and two buttons: "Create Account" + "Sign In"
+- Updated `web/src/routes/index.tsx` composing all sections with clip-path polygon transitions between them.
+
+steps:
+1. Create each section component in `web/src/components/landing/`.
+2. **HowItWorksSection**:
+ - Use Lendair's staggered timeline pattern (alternating flex-row/flex-row-reverse)
+ - Add dot-grid background class to the section container
+ - Use CSS `clip-path: polygon(...)` for angled section transitions
+3. **FeaturesGridSection**:
+ - Use CSS Grid: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3`
+ - Each card uses the `Card` primitive with an icon slot
+ - Icons: use Heroicons-style SVGs inline (shield, microphone, phone, home, user-group, lock)
+4. **ForUsersSection**:
+ - Two-column grid with `.gradient-card` panels
+ - Bullet lists use `CheckIcon` component (green checkmark SVG)
+5. **WhyShieldAISection**:
+ - Three cards in a row with checkmark bullet lists
+ - Use `.backdrop-blur-2xl` and angled clip-path like Lendair
+6. **CTABannerSection**:
+ - Full-width gradient card inside `PageContainer`
+ - Buttons navigate to `/signup` and `/login`
+7. Update `index.tsx`:
+ - Import all sections
+ - Add `` and ` ` tags for SEO
+ - Apply clip-path transitions between sections using inline styles
+ - Ensure `ColorWaveBackground` from task 05 is still present behind hero
+8. Add smooth scroll behavior and anchor links (e.g., `#features`, `#how-it-works`).
+
+steps:
+- Unit: Each section renders its content correctly
+- Unit: All `` loops over feature arrays render expected number of items
+- Visual: Verify clip-path transitions render correctly in Chrome, Firefox, Safari
+- Visual: Check responsive stacking on mobile viewport
+- Accessibility: Verify heading hierarchy (single H1 in hero, H2s in sections)
+
+acceptance_criteria:
+- [ ] Landing page contains all 5 content sections plus hero from task 05
+- [ ] Each section has correct heading hierarchy (no skipped levels)
+- [ ] Clip-path polygon transitions render without gaps or overlaps
+- [ ] Feature cards display 6 items in responsive grid
+- [ ] All icons, buttons, and links use theme tokens
+- [ ] Dark mode renders correctly for all sections
+- [ ] Anchor links (`#features`, `#how-it-works`) scroll smoothly to sections
+- [ ] No horizontal scroll on mobile viewport (320px–414px)
+
+validation:
+- Open `http://localhost:3000` and scroll through entire page
+- Verify all sections render with correct content and spacing
+- Run axe DevTools or Lighthouse accessibility audit; verify no critical issues
+- Test on mobile viewport in DevTools
+
+notes:
+- Reference Lendair `~/code/Lendair/web/src/routes/index.tsx` lines 200–469 for exact section structure, clip-path math, and spacing patterns.
+- The `--cut` CSS variable (`clamp(16px, 2.5vw, 40px)`) controls the angle of clip-path transitions — reuse this exact pattern.
+- Keep all copy ShieldAI-specific; do not leave any Lendair references (e.g., "borrow", "lend", "peer-to-peer").
+- Use the `IconPath` helper pattern from Lendair for consistent SVG sizing and stroke styling.
diff --git a/tasks/shieldai-unified-restructure/07-auth-pages.md b/tasks/shieldai-unified-restructure/07-auth-pages.md
new file mode 100644
index 0000000..d3ed037
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/07-auth-pages.md
@@ -0,0 +1,105 @@
+# 07. Auth Pages — Login, Signup, Password Reset, Onboarding
+
+meta:
+ id: shieldai-unified-restructure-07
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-03, shieldai-unified-restructure-04]
+ tags: [frontend, auth, solidjs, forms]
+
+objective:
+- Create the authentication pages for the ShieldAI web app: login, signup, password reset, and a post-signup onboarding flow. These should be visually cohesive with the landing page theme and use the UI primitives.
+
+deliverables:
+- `web/src/routes/(auth)/login.tsx` — Login page:
+ - Email + password form with validation
+ - "Remember me" checkbox
+ - "Forgot password?" link
+ - "Don't have an account? Sign up" link
+ - Social login buttons (Google, Apple placeholders)
+- `web/src/routes/(auth)/signup.tsx` — Signup page:
+ - Name, email, password, confirm password fields
+ - Password strength indicator
+ - Terms of service + privacy policy checkbox
+ - "Already have an account? Log in" link
+- `web/src/routes/(auth)/forgot-password.tsx` — Password reset request:
+ - Email input + submit
+ - Success state with "Check your email" message
+- `web/src/routes/(auth)/reset-password.tsx` — Password reset confirmation:
+ - New password + confirm fields
+ - Token validation from query param
+- `web/src/routes/(auth)/onboarding.tsx` — Post-signup onboarding:
+ - Step 1: Welcome + choose plan tier (Basic, Plus, Premium)
+ - Step 2: Add first watchlist item (email or phone)
+ - Step 3: Invite family members (optional)
+ - Step 4: Setup complete → redirect to dashboard
+- `web/src/components/auth/AuthLayout.tsx` — Shared auth page wrapper:
+ - Split layout: left side with ShieldAI branding + testimonial, right side with form card
+ - Responsive: stacked on mobile
+- `web/src/components/auth/` — Form components: `PasswordInput.tsx` (with visibility toggle), `SocialAuthButtons.tsx`
+
+steps:
+1. Create `web/src/routes/(auth)/` directory. SolidStart file-based routing treats `(auth)` as a route group (no URL prefix).
+2. Create `web/src/components/auth/` directory.
+3. **AuthLayout**:
+ - Left panel: large ShieldAI logo, tagline, and a rotating testimonial quote
+ - Right panel: centered form card with `.gradient-card`, border, padding
+ - Use `PageContainer` for consistent padding
+4. **Login page**:
+ - Use `Input` primitive for email and password
+ - Use `Button` for submit
+ - Client-side validation: required fields, email format
+ - Show error toast on invalid credentials (stub API call for now)
+ - Redirect to `/dashboard` on success (stub)
+5. **Signup page**:
+ - Additional fields: name, confirm password
+ - Password strength indicator: weak/medium/strong based on length + complexity
+ - Validate matching passwords client-side
+ - Checkbox for ToS (required)
+6. **Forgot/Reset password**:
+ - Simple forms with email or new password
+ - Query param parsing for reset token
+7. **Onboarding**:
+ - Stepper UI at top showing progress (1–4)
+ - Each step is a separate view within the page (signal-based state machine)
+ - Step 1: Tier cards with feature comparison (reuse TierComparison component from legacy if available, else create simple cards)
+ - Step 2: Input for email/phone with "Add" button
+ - Step 3: Email inputs for family invites
+ - Step 4: Success animation + "Go to Dashboard" button
+8. **PasswordInput**:
+ - Wraps `Input` with type toggle (text/password)
+ - Eye icon button inside input
+9. **SocialAuthButtons**:
+ - Google and Apple sign-in buttons with brand colors
+ - Placeholder onClick handlers (wire in task 23)
+10. Wire forms to stub `auth` API functions that will later connect to tRPC.
+
+steps:
+- Unit: Login form validates email format and required fields
+- Unit: Signup form validates password match and ToS checkbox
+- Unit: Onboarding stepper advances correctly and collects data
+- Unit: PasswordInput toggles visibility
+- E2E: Fill login form and submit; verify redirect to /dashboard (stub)
+
+acceptance_criteria:
+- [ ] Login page renders with email, password, remember me, forgot password, and social buttons
+- [ ] Signup page renders with all fields, password strength, and ToS checkbox
+- [ ] Password reset flow has request and confirmation pages
+- [ ] Onboarding has 4 steps with progress indicator and collects plan + watchlist + family data
+- [ ] All auth pages are responsive and use theme tokens
+- [ ] Client-side validation prevents submission of invalid forms
+- [ ] Error states show inline messages and toasts
+- [ ] AuthLayout split panel renders correctly on desktop and stacks on mobile
+
+validation:
+- Navigate to `/login`, `/signup`, `/forgot-password`, `/onboarding`
+- Verify all forms render and validate correctly
+- Test responsive layout at 375px, 768px, 1440px viewports
+- Toggle dark mode and verify form card backgrounds shift correctly
+
+notes:
+- Use SolidStart route groups `(auth)` to group auth pages without adding `/auth/` to URLs.
+- Do NOT implement actual JWT handling here — that comes in task 11 (tRPC auth context). Use stub functions that simulate network delay.
+- The onboarding data (plan, watchlist items, family invites) should be collected in a local signal/store and passed to the API in task 23.
+- Consider using `@solidjs/router`'s `useSearchParams` for reading query params (e.g., reset token).
+- Keep accessibility in mind: all inputs need ``, error messages linked via `aria-describedby`, and focus management on step changes.
diff --git a/tasks/shieldai-unified-restructure/08-migrate-existing-pages.md b/tasks/shieldai-unified-restructure/08-migrate-existing-pages.md
new file mode 100644
index 0000000..2f19f6d
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/08-migrate-existing-pages.md
@@ -0,0 +1,95 @@
+# 08. Migrate & Redesign Existing Pages — Blog, Ads, Dashboard Shell
+
+meta:
+ id: shieldai-unified-restructure-08
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-03, shieldai-unified-restructure-04, shieldai-unified-restructure-05, shieldai-unified-restructure-06, shieldai-unified-restructure-07]
+ tags: [frontend, migration, redesign, solidjs]
+
+objective:
+- Port and redesign the existing pages from `packages/web` into the new unified `web/` app, applying the ShieldAI theme system, UI primitives, and layout components. Pages: Blog (list + post), Ads landing page, and Dashboard shell.
+
+deliverables:
+- `web/src/routes/blog.tsx` — Blog listing page:
+ - Hero with "ShieldAI Blog" headline
+ - Grid of blog post cards with cover image, title, excerpt, author, date
+ - Tag filter bar
+ - Pagination or "Load more" button
+- `web/src/routes/blog/[slug].tsx` — Individual blog post page:
+ - Cover image, title, author, date, reading time
+ - Rich text content rendering (Markdown → HTML)
+ - Related posts sidebar
+ - Social share buttons
+- `web/src/routes/ads.tsx` — Ads landing page:
+ - Variant of main landing page with UTM-aware copy
+ - Conversion-focused layout with prominent CTA
+ - Trust badges and testimonials
+- `web/src/routes/(webapp)/dashboard.tsx` — Dashboard shell:
+ - Sidebar navigation for service sections: Overview, DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers, Settings
+ - Top bar with search, notifications bell, user avatar dropdown
+ - Main content area with placeholder widgets for each service
+ - Responsive: sidebar collapses to drawer on mobile
+- `web/src/components/dashboard/` — Dashboard-specific components:
+ - `Sidebar.tsx`, `TopBar.tsx`, `StatCard.tsx`, `ActivityFeed.tsx`, `QuickActions.tsx`
+
+steps:
+1. Create route files in `web/src/routes/` matching the deliverables.
+2. **Blog listing**:
+ - Create a stub data array of blog posts (replace with API in task 23)
+ - Use `Card` primitive for post cards
+ - Grid layout with `grid-cols-1 md:grid-cols-2 lg:grid-cols-3`
+ - Tag pills using `Badge` primitive
+3. **Blog post**:
+ - Use SolidStart dynamic route `[slug].tsx`
+ - Render stub markdown content using a lightweight markdown renderer (e.g., `solid-markdown` or custom component)
+ - Related posts: filter stub data by shared tags
+4. **Ads landing page**:
+ - Copy structure from main landing page (task 05–06)
+ - Modify headline to be conversion-focused (e.g., "Stop AI Scams Before They Reach You")
+ - Add pricing table and FAQ section
+ - Track UTM params with `useSearchParams`
+5. **Dashboard shell**:
+ - Use SolidStart route group `(webapp)` for authenticated routes
+ - Sidebar: vertical nav with icons and labels, active state styling
+ - TopBar: search input (placeholder), notification bell with badge, avatar dropdown with logout/settings
+ - Main area: placeholder grid of `StatCard` components showing mock data
+ - Mobile: sidebar hidden by default, hamburger toggle opens drawer overlay
+6. **Dashboard components**:
+ - `Sidebar`: list of links with icons, active route highlighting
+ - `TopBar`: flex row with search, notifications, profile
+ - `StatCard`: metric number, label, trend indicator (up/down), icon
+ - `ActivityFeed`: list of recent alerts/actions with timestamps
+ - `QuickActions`: grid of shortcut buttons for common tasks
+7. Migrate any reusable logic from `packages/web/src/hooks/` and `packages/web/src/components/` into `web/src/lib/` and `web/src/components/`.
+8. Delete or archive old `packages/web/` after migration is complete (final cleanup in task 41).
+
+steps:
+- Unit: Blog listing renders correct number of post cards
+- Unit: Blog post page renders content for valid slug, 404 for invalid
+- Unit: Dashboard sidebar highlights active route
+- Unit: Dashboard stat cards display mock data correctly
+- Visual: All pages use theme tokens and shift correctly in dark mode
+
+acceptance_criteria:
+- [ ] Blog listing page displays posts in responsive grid with tag filters
+- [ ] Blog post page renders markdown content, author, date, and related posts
+- [ ] Ads landing page has conversion-focused copy, pricing, and FAQ
+- [ ] Dashboard shell has sidebar, top bar, and main content area
+- [ ] Dashboard is responsive: sidebar collapses to drawer on mobile
+- [ ] All migrated pages use ShieldAI theme (no old color values)
+- [ ] No references to old `packages/web/` paths in new code
+- [ ] Route structure matches: `/blog`, `/blog/:slug`, `/ads`, `/dashboard`
+
+validation:
+- Navigate to `/blog`, `/blog/test-post`, `/ads`, `/dashboard`
+- Verify all pages render without errors
+- Test dashboard responsiveness: shrink viewport to <768px, verify sidebar becomes hamburger drawer
+- Toggle dark mode on each page and verify theme consistency
+
+notes:
+- The blog content will eventually come from the tRPC blog router (task 15+). For now, use stub data arrays.
+- The dashboard widgets are shells — they will be filled with real data in task 24.
+- Keep the old `packages/web/` directory intact until task 41 to reference code during migration.
+- Use SolidStart's `FileRoutes` or explicit `` definitions in `app.tsx` for the new routes.
+- The `(webapp)` route group should apply auth protection middleware (task 11) to all routes inside it.
diff --git a/tasks/shieldai-unified-restructure/09-drizzle-schema-migration.md b/tasks/shieldai-unified-restructure/09-drizzle-schema-migration.md
new file mode 100644
index 0000000..be9f7d0
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/09-drizzle-schema-migration.md
@@ -0,0 +1,88 @@
+# 09. Database — Migrate Full Prisma Schema to Drizzle ORM
+
+meta:
+ id: shieldai-unified-restructure-09
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: []
+ tags: [backend, database, drizzle, schema]
+
+objective:
+- Convert the entire 900-line Prisma schema from `packages/db/prisma/schema.prisma` into Drizzle ORM TypeScript schema files in `web/src/server/db/schema.ts` and related files. This is the foundational data layer for the unified monolith.
+
+deliverables:
+- `web/src/server/db/schema.ts` — Main schema file with all tables:
+ - User & Authentication (User, Account, Session, DeviceToken)
+ - Family & Subscription (FamilyGroup, FamilyGroupMember, Subscription)
+ - DarkWatch (WatchlistItem, Exposure)
+ - Notifications & Alerts (Alert)
+ - VoicePrint (VoiceEnrollment, VoiceAnalysis, AnalysisJob, AnalysisResult)
+ - SpamShield (SpamFeedback, SpamRule)
+ - Audit & Analytics (AuditLog, KPISnapshot)
+ - Correlation (NormalizedAlert, CorrelationGroup)
+ - Reports (SecurityReport)
+ - Marketing (WaitlistEntry, BlogPost)
+ - HomeTitle (PropertyWatchlistItem, PropertySnapshot, PropertyChange)
+ - RemoveBrokers (InfoBroker, RemovalRequest, BrokerListing)
+- `web/src/server/db/schema/` — Optional split directory if single file becomes unwieldy:
+ - `auth.ts`, `subscription.ts`, `darkwatch.ts`, `voiceprint.ts`, `spamshield.ts`, `alerts.ts`, `correlation.ts`, `reports.ts`, `marketing.ts`, `hometitle.ts`, `removebrokers.ts`
+- All enums defined as TypeScript const arrays or Drizzle `pgEnum`
+- All indexes, unique constraints, and foreign keys preserved
+- Relations defined using Drizzle's `relations()` helper
+
+steps:
+1. Read and analyze `packages/db/prisma/schema.prisma` completely. Document every model, enum, relation, index, and constraint.
+2. Install Drizzle ORM and PostgreSQL driver in `web/`:
+ - `drizzle-orm`
+ - `pg` (node-postgres) or `@neondatabase/serverless` if using Neon
+ - `drizzle-kit` for migrations
+3. Create `web/src/server/db/schema.ts` (or split directory).
+4. For each Prisma model, create a Drizzle table definition:
+ - Map Prisma field types to Drizzle column types:
+ - `String` → `varchar`, `text`, `uuid`
+ - `Int` → `integer`
+ - `Float` → `real`
+ - `Boolean` → `boolean`
+ - `DateTime` → `timestamp`
+ - `Json` → `jsonb`
+ - `String[]` → `text().array()`
+ - Preserve `@id`, `@default(uuid())`, `@unique`, `@index`, `@relation`
+ - Map Prisma enums to Drizzle `pgEnum()`
+5. Define all indexes using Drizzle's `.index()` and `.unique()` on table definitions.
+6. Define relations using `relations()` helper for:
+ - User → accounts, sessions, familyGroups, subscriptions, alerts, voice enrollments, etc.
+ - Subscription → watchlistItems, exposures, alerts, propertyWatchlistItems, removalRequests
+ - WatchlistItem → exposures
+ - PropertyWatchlistItem → snapshots, changes
+ - And all other one-to-many / many-to-one relations
+7. Export a unified schema object for use with Drizzle Kit.
+8. Verify the schema compiles without TypeScript errors.
+9. Generate an ER diagram or schema summary for documentation.
+
+steps:
+- Unit: Schema file compiles without TS errors
+- Integration: `drizzle-kit generate` produces migration SQL that matches Prisma schema structure
+- Compare: Automated or manual comparison of Prisma schema vs Drizzle schema to ensure no models/fields are missing
+
+acceptance_criteria:
+- [ ] Every model from Prisma schema has a corresponding Drizzle table definition
+- [ ] All enums are defined and used correctly
+- [ ] All primary keys, unique constraints, and indexes are preserved
+- [ ] All foreign key relations are defined using Drizzle relations
+- [ ] `drizzle-kit generate` runs successfully and outputs SQL
+- [ ] Generated SQL creates tables with correct column types and constraints
+- [ ] No data loss: the new schema is structurally equivalent to the old one
+
+validation:
+- Run `cd web && npx drizzle-kit generate` and inspect generated SQL
+- Compare table count: Prisma schema has X models, Drizzle schema has X tables
+- Verify enum values match exactly between Prisma and Drizzle
+- Run `npx drizzle-kit push` against a local PostgreSQL instance and confirm all tables are created
+
+notes:
+- This is the most critical backend task. A missing field or incorrect relation will cascade into broken tRPC routers.
+- Prisma's implicit many-to-many relations must be explicitly defined as junction tables in Drizzle.
+- Prisma's `@updatedAt` auto-timestamp can be replicated with Drizzle's `$onUpdateFn(() => new Date())`.
+- Keep the old Prisma schema file as reference until task 41.
+- Consider using `drizzle-zod` later (task 11+) to auto-generate validation schemas from Drizzle tables.
+- The schema uses PostgreSQL-specific features (arrays, enums, jsonb). Ensure the Drizzle definitions use `pgTable`, `pgEnum`, etc.
diff --git a/tasks/shieldai-unified-restructure/10-db-connection-migrations.md b/tasks/shieldai-unified-restructure/10-db-connection-migrations.md
new file mode 100644
index 0000000..dc16043
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/10-db-connection-migrations.md
@@ -0,0 +1,113 @@
+# 10. Database — PostgreSQL Connection, Migrations, and Seed Data
+
+meta:
+ id: shieldai-unified-restructure-10
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-09]
+ tags: [backend, database, drizzle, postgres]
+
+objective:
+- Set up the database connection layer, migration tooling, and seed data for the unified monolith. Replace the current SQLite setup in `web/` with PostgreSQL, matching the production database used by the legacy `packages/db`.
+
+deliverables:
+- `web/src/server/db/index.ts` — Database connection module:
+ - Creates PostgreSQL connection pool using `pg` or `@neondatabase/serverless`
+ - Exports `db` instance initialized with Drizzle ORM
+ - Handles connection string from environment variables
+ - Graceful shutdown hook to close pool
+- `web/drizzle.config.ts` — Drizzle Kit configuration:
+ - Points to `src/server/db/schema.ts`
+ - Specifies PostgreSQL dialect
+ - Reads database URL from env
+- `web/src/server/db/migrate.ts` — Migration runner script:
+ - Programmatically runs pending migrations on startup
+ - Can be called from `entry-server.tsx` or a standalone script
+- `web/src/server/db/seed.ts` — Seed script:
+ - Creates sample users, subscriptions, watchlist items, alerts, blog posts
+ - Idempotent: can be run multiple times without duplicates
+ - Useful for development and demo environments
+- Environment configuration:
+ - `DATABASE_URL` in `.env` and `.env.example`
+ - Connection pooling settings (max connections, timeout)
+
+steps:
+1. Install dependencies in `web/`:
+ - `drizzle-orm`, `drizzle-kit`
+ - `pg` (for local/dev) or `@neondatabase/serverless` (for serverless deploy)
+ - `@types/pg` if using `pg`
+2. Create `web/src/server/db/index.ts`:
+ ```ts
+ import { drizzle } from 'drizzle-orm/node-postgres';
+ import { Pool } from 'pg';
+ import * as schema from './schema';
+
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
+ export const db = drizzle(pool, { schema });
+ ```
+3. Update `web/drizzle.config.ts`:
+ ```ts
+ import { defineConfig } from 'drizzle-kit';
+ export default defineConfig({
+ schema: './src/server/db/schema.ts',
+ out: './drizzle',
+ dialect: 'postgresql',
+ dbCredentials: { url: process.env.DATABASE_URL! },
+ });
+ ```
+4. Create `web/src/server/db/migrate.ts`:
+ - Import `migrate` from `drizzle-orm/node-postgres/migrator`
+ - Run migrations from `./drizzle` folder
+ - Log success or error
+5. Create `web/src/server/db/seed.ts`:
+ - Use `db.insert()` to populate tables
+ - Create: 2-3 users, 1 family group, 2 subscriptions (basic + premium), 3-5 watchlist items, 2-3 exposures, 5-10 alerts, 3 blog posts, 2 property watchlist items, 1 removal request
+ - Use deterministic IDs or check for existing data before inserting
+6. Add `db:migrate`, `db:generate`, `db:push`, `db:seed` scripts to `web/package.json`.
+7. Create `.env.example` in `web/` with `DATABASE_URL=postgresql://...`
+8. Test locally:
+ - Start PostgreSQL (Docker or local install)
+ - Run `pnpm db:generate` to create migration SQL
+ - Run `pnpm db:push` to apply schema
+ - Run `pnpm db:seed` to populate data
+ - Verify tables exist with correct data using `psql` or a GUI tool
+9. Update `web/src/server/db/schema.ts` from task 09 if any adjustments are needed based on migration output.
+
+steps:
+- Integration: `pnpm db:generate` produces valid SQL
+- Integration: `pnpm db:push` creates all tables in local PostgreSQL
+- Integration: `pnpm db:seed` populates tables without errors
+- Integration: Application can query seeded data via `db.select()`
+
+acceptance_criteria:
+- [ ] `web/src/server/db/index.ts` exports a working Drizzle `db` instance
+- [ ] `drizzle.config.ts` is correctly configured for PostgreSQL
+- [ ] `pnpm db:generate` creates migration files in `web/drizzle/`
+- [ ] `pnpm db:push` applies schema to a PostgreSQL database successfully
+- [ ] `pnpm db:seed` populates all relevant tables with sample data
+- [ ] The app can perform `db.select().from(users)` and return seeded users
+- [ ] Environment variables are documented in `.env.example`
+
+validation:
+- `cd web && pnpm db:generate` — check `drizzle/` folder for SQL files
+- `cd web && pnpm db:push` — verify tables created via `psql -d shieldai -c "\dt"`
+- `cd web && pnpm db:seed` — verify data exists via `psql -d shieldai -c "SELECT COUNT(*) FROM users;"`
+- Create a temporary test route that queries `db.select().from(users)` and renders results
+
+notes:
+- The legacy project used Prisma + PostgreSQL in production. We are keeping PostgreSQL but switching to Drizzle ORM.
+- For local development, Docker Compose with PostgreSQL is recommended:
+ ```yaml
+ services:
+ postgres:
+ image: postgres:16-alpine
+ environment:
+ POSTGRES_USER: shieldai
+ POSTGRES_PASSWORD: shieldai
+ POSTGRES_DB: shieldai
+ ports:
+ - "5432:5432"
+ ```
+- If deploying to Vercel/Netlify serverless, use `@neondatabase/serverless` instead of `pg`.
+- Migration files should be committed to git so all environments run the same migrations.
+- The seed script should be idempotent. Use `ON CONFLICT DO NOTHING` or check existence before inserting.
diff --git a/tasks/shieldai-unified-restructure/11-trpc-auth-context.md b/tasks/shieldai-unified-restructure/11-trpc-auth-context.md
new file mode 100644
index 0000000..f258f77
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/11-trpc-auth-context.md
@@ -0,0 +1,112 @@
+# 11. tRPC Foundation — Auth Context, Middleware, and Protected Procedures
+
+meta:
+ id: shieldai-unified-restructure-11
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-10]
+ tags: [backend, trpc, auth, api]
+
+objective:
+- Establish the tRPC foundation in the unified monolith: router factory, context builder with auth, middleware for protected routes, and error handling. Replace the stub tRPC setup with a production-ready auth-aware API layer.
+
+deliverables:
+- `web/src/server/api/utils.ts` — tRPC initialization and helpers:
+ - `t` object from `initTRPC.context().create()`
+ - `createTRPCRouter` factory
+ - `publicProcedure` — unauthenticated
+ - `protectedProcedure` — requires valid JWT/session
+ - `adminProcedure` — requires admin role
+ - `rateLimitedProcedure` — with basic rate limiting middleware
+- `web/src/server/api/trpc.ts` — Context builder:
+ - `createTRPCContext({ req, res })` that extracts auth from:
+ - Session cookie (SolidStart session)
+ - `Authorization: Bearer ` header
+ - `x-api-key` header (for service-to-service or extension calls)
+ - Returns `{ db, user, session, apiKey }` in context
+ - Uses Drizzle `db` instance from `web/src/server/db/index.ts`
+- `web/src/server/api/root.ts` — Root router:
+ - Imports all sub-routers
+ - Exports `appRouter` and `AppRouter` type
+- `web/src/routes/api/trpc/[trpc].ts` — tRPC API handler:
+ - SolidStart API route that handles all tRPC requests
+ - Uses `fetchRequestHandler` from `@trpc/server/adapters/fetch`
+ - Wires `createTRPCContext`
+- `web/src/lib/api.ts` — tRPC client:
+ - Updates existing file to use correct `AppRouter` type
+ - Adds auth token injection from localStorage/cookie
+ - Handles unauthorized responses (401 → redirect to login)
+- Auth utilities:
+ - `web/src/server/auth/jwt.ts` — JWT sign/verify using `jose` or `jsonwebtoken`
+ - `web/src/server/auth/session.ts` — Session creation and validation
+ - `web/src/server/auth/password.ts` — Password hashing with `bcrypt` or `argon2`
+
+steps:
+1. Install dependencies in `web/`:
+ - `@trpc/server`, `@trpc/client` (already present, verify versions match)
+ - `jose` (modern JWT library, works in Edge runtime)
+ - `bcryptjs` or `argon2` for password hashing
+ - `zod` for input validation (already present via valibot, but standardize on one — recommend `zod` for wider ecosystem compatibility)
+2. Create `web/src/server/auth/jwt.ts`:
+ - `signJWT(payload, secret, expiresIn)`
+ - `verifyJWT(token, secret)`
+ - Use `jose` for Edge compatibility
+3. Create `web/src/server/auth/password.ts`:
+ - `hashPassword(password)` → async hash
+ - `verifyPassword(password, hash)` → boolean
+4. Create `web/src/server/api/trpc.ts` (context builder):
+ - Parse cookies from request headers
+ - Try session cookie first (SolidStart session)
+ - Fall back to Bearer JWT
+ - Fall back to x-api-key
+ - Look up user in database via Drizzle
+ - Return context with `user`, `session`, `db`, `apiKey`
+5. Update `web/src/server/api/utils.ts`:
+ - Initialize tRPC with context type
+ - Define `publicProcedure`
+ - Define `protectedProcedure` using middleware that checks `ctx.user` and throws `TRPCError.UNAUTHORIZED` if missing
+ - Define `adminProcedure` that checks `ctx.user.role === 'admin'`
+6. Update `web/src/server/api/root.ts`:
+ - Keep `exampleRouter` for now
+ - Add placeholder imports for future routers
+7. Update `web/src/routes/api/trpc/[trpc].ts`:
+ - Create SolidStart API route
+ - Use `fetchRequestHandler` with `router: appRouter`, `createContext: createTRPCContext`
+8. Update `web/src/lib/api.ts`:
+ - Import `AppRouter` from `~/server/api/root`
+ - Add `httpBatchLink` with `headers` function that reads auth token from cookie/localStorage
+ - Add error link that handles 401 by redirecting
+9. Test with a temporary protected route that returns `ctx.user`.
+
+steps:
+- Unit: Context builder returns anonymous user for unauthenticated requests
+- Unit: Context builder returns full user for valid JWT
+- Unit: `protectedProcedure` rejects unauthenticated requests with 401
+- Unit: `adminProcedure` rejects non-admin users with 403
+- Integration: tRPC client can call public and protected endpoints
+- E2E: Login flow sets cookie/JWT and subsequent requests are authenticated
+
+acceptance_criteria:
+- [ ] `createTRPCContext` correctly builds context from session, JWT, or API key
+- [ ] `publicProcedure` allows unauthenticated access
+- [ ] `protectedProcedure` requires authentication and provides `ctx.user`
+- [ ] `adminProcedure` requires admin role
+- [ ] tRPC API handler responds correctly at `/api/trpc`
+- [ ] Client library (`web/src/lib/api.ts`) connects and handles auth automatically
+- [ ] JWT sign/verify works with `jose` library
+- [ ] Password hashing uses bcrypt or argon2
+
+validation:
+- Create a temporary tRPC router with public and protected queries
+- Call public query from browser → should succeed
+- Call protected query without auth → should return 401
+- Call protected query with valid JWT → should return user data
+- Run `cd web && pnpm test` to verify auth unit tests
+
+notes:
+- The legacy Fastify auth middleware (`packages/api/src/middleware/auth.middleware.ts`) is the reference for auth logic. Port its JWT verification and API key handling.
+- SolidStart sessions are cookie-based. The tRPC context should read the SolidStart session cookie and validate it.
+- For the extension (task 27), API key auth will be important. Ensure `x-api-key` handling is robust.
+- Consider using `lucia-auth` or `next-auth` SolidStart integration for session management, but a custom JWT approach is fine if it matches the legacy system.
+- Standardize on `zod` for input validation across all tRPC routers. The current setup uses valibot — migrate to zod for consistency.
+- Keep auth utilities in `web/src/server/auth/` separate from tRPC to allow reuse by other server code (e.g., background jobs).
diff --git a/tasks/shieldai-unified-restructure/12-user-family-router.md b/tasks/shieldai-unified-restructure/12-user-family-router.md
new file mode 100644
index 0000000..62bb7af
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/12-user-family-router.md
@@ -0,0 +1,95 @@
+# 12. Backend Router — User & Family Group Management
+
+meta:
+ id: shieldai-unified-restructure-12
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-11]
+ tags: [backend, trpc, users, family, api]
+
+objective:
+- Build the tRPC router for user management and family group functionality. Port all user-related logic from the legacy `packages/api/src/routes/` and `packages/shared-auth/` into a unified `user` router.
+
+deliverables:
+- `web/src/server/api/routers/user.ts` — User router with procedures:
+ - `user.me` — `protectedProcedure` returning current user profile
+ - `user.update` — `protectedProcedure` updating user name, email, image
+ - `user.delete` — `protectedProcedure` deleting account and all related data
+ - `user.listFamilyMembers` — `protectedProcedure` returning family group members
+ - `user.inviteFamilyMember` — `protectedProcedure` sending invite by email
+ - `user.removeFamilyMember` — `protectedProcedure` removing member from group
+ - `user.updateFamilyMemberRole` — `protectedProcedure` changing member role
+- `web/src/server/services/user.service.ts` — Business logic layer:
+ - `getUserById(id)` — fetch user with relations
+ - `updateUser(id, data)` — update and return user
+ - `deleteUser(id)` — cascade delete all user data
+ - `getFamilyGroup(userId)` — get family group with members
+ - `inviteMember(groupId, email)` — create invite record, send email
+ - `removeMember(groupId, userId)` — remove membership
+ - `updateMemberRole(groupId, userId, role)` — update role enum
+- `web/src/server/services/family.service.ts` — Family group logic:
+ - `createFamilyGroup(ownerId, name)`
+ - `getFamilyGroupWithMembers(groupId)`
+ - `transferOwnership(groupId, newOwnerId)`
+- Zod schemas for all inputs in `web/src/server/api/schemas/user.ts`
+
+steps:
+1. Create `web/src/server/api/routers/user.ts`.
+2. Define Zod schemas:
+ - `updateUserSchema`: `name: z.string().min(1).optional()`, `email: z.string().email().optional()`
+ - `inviteMemberSchema`: `email: z.string().email()`, `role: z.enum(['admin', 'member']).default('member')`
+ - `removeMemberSchema`: `userId: z.string().uuid()`
+ - `updateRoleSchema`: `userId: z.string().uuid()`, `role: z.enum(['owner', 'admin', 'member'])`
+3. Implement `user.me`:
+ - `protectedProcedure.query(({ ctx }) => ctx.db.select().from(users).where(eq(users.id, ctx.user.id)))`
+ - Include relations: accounts, sessions, familyGroups, subscriptions
+4. Implement `user.update`:
+ - Validate input with Zod
+ - Update user record, return updated user
+5. Implement `user.delete`:
+ - Verify user exists
+ - Delete all related records (cascade where defined, manual where not)
+ - Return success
+6. Implement family procedures:
+ - `listFamilyMembers`: query family group where user is owner or member, return members with roles
+ - `inviteFamilyMember`: create `FamilyGroupMember` record with pending status, trigger email notification (task 14)
+ - `removeFamilyMember`: verify caller is owner/admin, delete membership record
+ - `updateFamilyMemberRole`: verify caller is owner, update role enum
+7. Create service layer `web/src/server/services/user.service.ts`:
+ - Extract reusable DB queries
+ - Handle error cases (user not found, unauthorized, duplicate email)
+8. Create `web/src/server/services/family.service.ts`.
+9. Wire router into `web/src/server/api/root.ts`.
+10. Write unit tests for service functions.
+
+steps:
+- Unit: `getUserById` returns user with correct relations
+- Unit: `updateUser` applies changes and returns updated record
+- Unit: `inviteMember` creates record and rejects duplicate invites
+- Unit: `removeMember` rejects non-admin callers
+- Unit: `updateMemberRole` rejects non-owner callers
+- Integration: tRPC `user.me` returns authenticated user
+- Integration: tRPC `user.inviteFamilyMember` creates member record
+
+acceptance_criteria:
+- [ ] `user.me` returns the authenticated user's profile with all relations
+- [ ] `user.update` modifies allowed fields and rejects invalid data
+- [ ] `user.delete` removes the user and all related data
+- [ ] Family member listing returns correct members with roles
+- [ ] Family invites create pending members and validate permissions
+- [ ] Family member removal and role updates enforce authorization rules
+- [ ] All procedures use Zod input validation
+- [ ] Service layer is pure and testable (no request/response logic)
+
+validation:
+- Create a temporary test page or use tRPC playground to call each procedure
+- Verify `user.me` returns seeded user data from task 10
+- Attempt to remove a family member as a regular member → expect TRPCError FORBIDDEN
+- Run `cd web && pnpm test` for unit tests
+
+notes:
+- Reference legacy code: `packages/api/src/routes/` for route patterns, `packages/shared-auth/src/` for auth logic.
+- Family group ownership transfer is a sensitive operation — ensure only the current owner can initiate it.
+- When a user is deleted, consider soft-delete (set `deletedAt`) instead of hard delete to preserve audit trails. Update schema if needed.
+- The `user.delete` operation must handle cascade deletes carefully to avoid foreign key constraint errors. Either rely on Drizzle's cascade definitions or delete in correct order.
+- For invites, consider adding an `Invitation` table with expiration if not already in schema. If not, add it as part of this task.
diff --git a/tasks/shieldai-unified-restructure/13-subscription-billing-router.md b/tasks/shieldai-unified-restructure/13-subscription-billing-router.md
new file mode 100644
index 0000000..c2f4e55
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/13-subscription-billing-router.md
@@ -0,0 +1,96 @@
+# 13. Backend Router — Subscriptions, Billing, and Stripe Webhooks
+
+meta:
+ id: shieldai-unified-restructure-13
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-11]
+ tags: [backend, trpc, billing, stripe, api]
+
+objective:
+- Build the tRPC router for subscription management and Stripe billing integration. Port logic from `packages/shared-billing/` and `packages/api/src/routes/subscription.routes.ts` into a unified `billing` router.
+
+deliverables:
+- `web/src/server/api/routers/billing.ts` — Billing router with procedures:
+ - `billing.getSubscription` — `protectedProcedure` returning current subscription with tier
+ - `billing.createCheckoutSession` — `protectedProcedure` creating Stripe checkout for plan upgrade
+ - `billing.createPortalSession` — `protectedProcedure` creating Stripe Customer Portal
+ - `billing.cancelSubscription` — `protectedProcedure` scheduling cancellation at period end
+ - `billing.reactivateSubscription` — `protectedProcedure` reactivating a canceled subscription
+ - `billing.listInvoices` — `protectedProcedure` returning Stripe invoices for user
+ - `billing.webhook` — `publicProcedure` (or raw endpoint) handling Stripe webhook events
+- `web/src/server/services/billing.service.ts` — Business logic:
+ - `getOrCreateCustomer(userId, email)` — Stripe customer management
+ - `createCheckoutSession(userId, priceId, successUrl, cancelUrl)`
+ - `createPortalSession(customerId, returnUrl)`
+ - `handleWebhookEvent(event)` — processes `checkout.session.completed`, `invoice.paid`, `invoice.payment_failed`, `customer.subscription.updated`, `customer.subscription.deleted`
+ - `updateSubscriptionInDB(subscriptionId, stripeEventData)` — syncs Stripe state to Drizzle
+- `web/src/routes/api/stripe/webhook.ts` — Raw SolidStart API route for Stripe webhooks:
+ - Verifies Stripe signature using `stripe.webhooks.constructEvent`
+ - Forwards to billing service
+- Zod schemas in `web/src/server/api/schemas/billing.ts`
+- Stripe client initialization in `web/src/server/stripe.ts`
+
+steps:
+1. Install `stripe` npm package in `web/`.
+2. Create `web/src/server/stripe.ts`:
+ - Initialize Stripe client with `process.env.STRIPE_SECRET_KEY`
+ - Export `stripe` instance
+3. Create `web/src/server/services/billing.service.ts`:
+ - `getOrCreateCustomer`: query DB for existing `stripeCustomerId`, or create via Stripe API and save to DB
+ - `createCheckoutSession`: create Stripe Checkout session with `subscription` mode, return URL
+ - `createPortalSession`: create Stripe Billing Portal session
+ - `handleWebhookEvent`: switch on event type:
+ - `checkout.session.completed` → create/update subscription record, set tier
+ - `invoice.paid` → update subscription status to active
+ - `invoice.payment_failed` → update status to past_due, send notification
+ - `customer.subscription.updated` → sync tier, status, period dates
+ - `customer.subscription.deleted` → mark as canceled
+4. Create `web/src/server/api/routers/billing.ts`:
+ - `getSubscription`: query user's active subscription, include tier details
+ - `createCheckoutSession`: validate price ID against allowed prices, call service, return session URL
+ - `createPortalSession`: call service, return portal URL
+ - `cancelSubscription`: update Stripe subscription to `cancel_at_period_end`, update DB
+ - `reactivateSubscription`: remove `cancel_at_period_end`, update DB
+ - `listInvoices`: fetch Stripe invoices for customer, return paginated list
+5. Create `web/src/routes/api/stripe/webhook.ts`:
+ - SolidStart API route (not tRPC — Stripe needs raw body for signature verification)
+ - Read raw body from request
+ - Verify signature with `STRIPE_WEBHOOK_SECRET`
+ - Call `billingService.handleWebhookEvent`
+ - Return 200 for success, 400 for invalid signature, 500 for processing errors
+6. Define Zod schemas for all inputs (priceId, successUrl, etc.).
+7. Wire router into `web/src/server/api/root.ts`.
+8. Write unit tests for billing service (mock Stripe API calls).
+
+steps:
+- Unit: `getOrCreateCustomer` returns existing customer or creates new one
+- Unit: `handleWebhookEvent` correctly updates subscription status for each event type
+- Unit: Stripe signature verification rejects invalid signatures
+- Integration: `billing.getSubscription` returns subscription for authenticated user
+- Integration: Checkout session creation returns valid Stripe URL
+
+acceptance_criteria:
+- [ ] `billing.getSubscription` returns current subscription with tier and status
+- [ ] `billing.createCheckoutSession` creates a Stripe checkout session and returns the URL
+- [ ] `billing.createPortalSession` creates a Stripe customer portal session
+- [ ] `billing.cancelSubscription` sets `cancelAtPeriodEnd` in Stripe and DB
+- [ ] `billing.reactivateSubscription` removes cancellation flag
+- [ ] Stripe webhook endpoint correctly handles all relevant event types
+- [ ] Webhook signature verification prevents spoofed requests
+- [ ] Subscription tier changes correctly gate features (enforced in other routers)
+
+validation:
+- Use Stripe CLI to trigger test webhooks: `stripe listen --forward-to localhost:3000/api/stripe/webhook`
+- Verify webhook handler responds 200 and updates DB correctly
+- Test checkout flow with Stripe Test Mode
+- Run `cd web && pnpm test` for billing unit tests
+
+notes:
+- Reference legacy: `packages/shared-billing/src/`, `packages/api/src/routes/subscription.routes.ts`
+- Store Stripe price IDs in environment variables or a config file:
+ - `STRIPE_PRICE_BASIC`, `STRIPE_PRICE_PLUS`, `STRIPE_PRICE_PREMIUM`
+- The webhook endpoint MUST receive the raw request body, not parsed JSON. In SolidStart, use `request.text()` before any parsing.
+- Ensure idempotency: webhook events may be delivered multiple times. Use Stripe event ID to deduplicate.
+- For the tier enum in Drizzle schema, ensure it matches Stripe product metadata or price IDs.
+- Consider adding a `billing.history` procedure that returns payment history from Stripe invoices.
diff --git a/tasks/shieldai-unified-restructure/14-notifications-router.md b/tasks/shieldai-unified-restructure/14-notifications-router.md
new file mode 100644
index 0000000..663ecbf
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/14-notifications-router.md
@@ -0,0 +1,102 @@
+# 14. Backend Router — Email, Push, and SMS Notifications
+
+meta:
+ id: shieldai-unified-restructure-14
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-11]
+ tags: [backend, notifications, email, push, sms, api]
+
+objective:
+- Build the tRPC router and service layer for sending notifications across all channels: email (Resend), push (FCM/APNs), and SMS (Twilio). Port logic from `packages/shared-notifications/` into a unified `notifications` router.
+
+deliverables:
+- `web/src/server/api/routers/notification.ts` — Notification router:
+ - `notification.sendEmail` — `adminProcedure` (or internal service use) sending transactional email
+ - `notification.sendPush` — `protectedProcedure` sending push to user's devices
+ - `notification.sendSMS` — `protectedProcedure` sending SMS
+ - `notification.registerDevice` — `protectedProcedure` registering FCM/APNs token
+ - `notification.unregisterDevice` — `protectedProcedure` removing device token
+ - `notification.listDevices` — `protectedProcedure` listing registered devices
+ - `notification.getPreferences` — `protectedProcedure` getting notification preferences
+ - `notification.updatePreferences` — `protectedProcedure` updating preferences
+- `web/src/server/services/notification.service.ts` — Business logic:
+ - `sendEmail(to, subject, html, text?)` — via Resend API
+ - `sendPush(deviceTokens, title, body, data?)` — via FCM for Android, APNs for iOS
+ - `sendSMS(phoneNumber, message)` — via Twilio
+ - `registerDevice(userId, token, platform, deviceType)` — save to DB
+ - `unregisterDevice(userId, token)` — soft-delete or mark inactive
+ - `getPreferences(userId)` — read from DB
+ - `updatePreferences(userId, prefs)` — write to DB
+- `web/src/server/services/email.templates.ts` — Email template renderer:
+ - Welcome email
+ - Alert notification email
+ - Password reset email
+ - Family invite email
+ - Billing receipt email
+- Provider clients:
+ - `web/src/server/lib/resend.ts` — Resend client
+ - `web/src/server/lib/fcm.ts` — Firebase Admin SDK initialization
+ - `web/src/server/lib/twilio.ts` — Twilio client
+
+steps:
+1. Install dependencies in `web/`:
+ - `resend` (email)
+ - `firebase-admin` (push notifications)
+ - `twilio` (SMS)
+2. Create provider client files:
+ - `resend.ts`: initialize with `RESEND_API_KEY`
+ - `fcm.ts`: initialize Firebase Admin with service account JSON
+ - `twilio.ts`: initialize with `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN`
+3. Create `web/src/server/services/email.templates.ts`:
+ - Each template is a function returning `{ subject, html, text }`
+ - Use simple HTML with inline CSS (or a minimal template engine)
+ - Include ShieldAI branding (logo, colors)
+4. Create `web/src/server/services/notification.service.ts`:
+ - `sendEmail`: call Resend, log result, handle errors
+ - `sendPush`: iterate device tokens, call FCM for Android tokens, APNs for iOS tokens (or use Firebase Admin which handles both)
+ - `sendSMS`: call Twilio Messages API, validate E.164 phone format
+ - `registerDevice`: upsert into `DeviceToken` table
+ - `unregisterDevice`: set `isActive = false`
+ - `getPreferences` / `updatePreferences`: read/write a `NotificationPreferences` table (add to schema if missing)
+5. Create `web/src/server/api/routers/notification.ts`:
+ - Define Zod schemas for all inputs
+ - `sendEmail`: restricted to admin or internal service calls
+ - `sendPush`: accepts `title`, `body`, `data` payload; sends to all active user devices
+ - `sendSMS`: accepts `phoneNumber`, `message`
+ - Device registration/unregistration procedures
+ - Preference get/update procedures
+6. Wire router into `web/src/server/api/root.ts`.
+7. Write unit tests with mocked providers.
+
+steps:
+- Unit: `sendEmail` calls Resend with correct parameters
+- Unit: `sendPush` iterates tokens and calls FCM
+- Unit: `sendSMS` validates E.164 format before calling Twilio
+- Unit: `registerDevice` creates DeviceToken record
+- Unit: `unregisterDevice` marks token inactive
+- Integration: `notification.registerDevice` persists token to DB
+
+acceptance_criteria:
+- [ ] Email can be sent via Resend with branded templates
+- [ ] Push notifications can be sent to registered Android and iOS devices via FCM
+- [ ] SMS can be sent via Twilio to valid E.164 numbers
+- [ ] Device tokens can be registered and unregistered per user
+- [ ] Notification preferences are persisted and respected (e.g., user can disable SMS alerts)
+- [ ] All provider errors are caught and logged without crashing the app
+- [ ] Email templates include ShieldAI branding
+
+validation:
+- Send a test email via Resend to your own address and verify receipt
+- Register a test device token and send a push (use Firebase Console or API)
+- Send a test SMS via Twilio (use test credentials for free)
+- Run `cd web && pnpm test` for notification service tests
+
+notes:
+- Reference legacy: `packages/shared-notifications/src/`, `packages/api/src/routes/notifications.routes.ts`
+- For FCM, Firebase Admin SDK requires a service account JSON. Store path in `FIREBASE_SERVICE_ACCOUNT_PATH` env var.
+- For APNs, Firebase Admin can proxy to APNs if configured correctly. Alternatively, use `apn` npm package for direct APNs integration.
+- Twilio test credentials (`TWILIO_TEST_*`) can be used for integration tests without sending real SMS.
+- Resend has a generous free tier (100 emails/day) perfect for development.
+- Consider adding a `notification.queue` table for reliable delivery (retry failed sends). This can be a follow-up task.
+- The `NotificationPreferences` model may need to be added to the Drizzle schema if not already present. Add it as part of this task.
diff --git a/tasks/shieldai-unified-restructure/15-darkwatch-router.md b/tasks/shieldai-unified-restructure/15-darkwatch-router.md
new file mode 100644
index 0000000..4c52845
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/15-darkwatch-router.md
@@ -0,0 +1,101 @@
+# 15. Backend Router — DarkWatch (Dark Web Monitoring)
+
+meta:
+ id: shieldai-unified-restructure-15
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-12, shieldai-unified-restructure-13, shieldai-unified-restructure-14]
+ tags: [backend, trpc, darkwatch, security, api]
+
+objective:
+- Build the tRPC router for DarkWatch, the dark web monitoring service. Port all logic from `services/darkwatch/` and `packages/api/src/routes/darkwatch.routes.ts` into a unified `darkwatch` router and service layer.
+
+deliverables:
+- `web/src/server/api/routers/darkwatch.ts` — DarkWatch router:
+ - `darkwatch.getWatchlist` — `protectedProcedure` returning user's watchlist items
+ - `darkwatch.addWatchlistItem` — `protectedProcedure` adding email/phone/SSN/domain to watchlist
+ - `darkwatch.removeWatchlistItem` — `protectedProcedure` removing item
+ - `darkwatch.getExposures` — `protectedProcedure` returning detected exposures
+ - `darkwatch.getExposureDetails` — `protectedProcedure` returning single exposure with metadata
+ - `darkwatch.runScan` — `protectedProcedure` triggering manual scan (respects tier limits)
+ - `darkwatch.getScanStatus` — `protectedProcedure` checking scan progress
+ - `darkwatch.getReports` — `protectedProcedure` returning generated PDF reports
+- `web/src/server/services/darkwatch.service.ts` — Core business logic:
+ - `addWatchlistItem(userId, type, value)` — hash value, deduplicate, save to DB
+ - `removeWatchlistItem(userId, itemId)` — delete and cascade
+ - `getExposures(userId, filters?)` — query exposures with pagination, sorting
+ - `runScan(userId)` — orchestrate multi-source scan:
+ - HIBP breach check
+ - SecurityTrails lookup
+ - Censys/Shodan queries
+ - Dark web forum scraping (where applicable)
+ - `generateReport(userId, periodStart, periodEnd)` — compile exposures into PDF report
+ - `checkTierLimits(userId)` — verify scan frequency against subscription tier
+- `web/src/server/services/darkwatch/scan.engine.ts` — Scan orchestration:
+ - `scanHIBP(email)` — query Have I Been Pwned API
+ - `scanSecurityTrails(identifier)` — query SecurityTrails API
+ - `scanCensys(query)` — query Censys API
+ - `scanShodan(query)` — query Shodan API
+ - `scanForums(identifier)` — placeholder for forum scraping logic
+- `web/src/server/services/darkwatch/alert.pipeline.ts` — Exposure-to-alert pipeline:
+ - `processExposure(exposure)` — severity scoring, deduplication, alert creation
+ - `severityScore(exposure)` — calculate severity based on source, data type, recurrence
+
+steps:
+1. Create `web/src/server/api/routers/darkwatch.ts`.
+2. Define Zod schemas:
+ - `addWatchlistItemSchema`: `type: z.enum(['email', 'phoneNumber', 'ssn', 'address', 'domain'])`, `value: z.string()`
+ - `exposureFilterSchema`: `severity: z.enum(['info', 'warning', 'critical']).optional()`, `source: z.enum([...]).optional()`, `page: z.number().default(1)`, `limit: z.number().default(20)`
+3. Implement router procedures:
+ - Watchlist CRUD with user ownership checks
+ - Exposure queries with filtering and pagination
+ - Manual scan with tier limit enforcement
+4. Create `web/src/server/services/darkwatch.service.ts`:
+ - Port logic from `services/darkwatch/src/watchlist.service.ts`
+ - Port logic from `services/darkwatch/src/scan.service.ts`
+ - Port logic from `services/darkwatch/src/alert.pipeline.ts`
+5. Create scan engine modules:
+ - Each scanner is a function that takes an identifier and returns raw results
+ - Use environment variables for API keys (`HIBP_API_KEY`, `SECURITYTRAILS_API_KEY`, etc.)
+ - Implement circuit breaker pattern for external APIs (reference `services/spamshield/test/circuit-breaker.test.ts`)
+6. Create alert pipeline:
+ - `processExposure` creates or updates `Exposure` record
+ - If new or severity increased, create `Alert` record and trigger notification (via task 14 service)
+ - Deduplicate based on `identifierHash` and `source`
+7. Implement tier limit checks:
+ - Basic: 1 manual scan/month
+ - Plus: 1 manual scan/week
+ - Premium: unlimited + real-time monitoring
+8. Wire router into `web/src/server/api/root.ts`.
+9. Write unit tests for service functions with mocked external APIs.
+
+steps:
+- Unit: `addWatchlistItem` hashes and deduplicates values
+- Unit: `runScan` calls all scan engines and aggregates results
+- Unit: `severityScore` returns correct severity for different exposure types
+- Unit: `checkTierLimits` enforces scan frequency correctly
+- Unit: Alert pipeline creates alert only for new/high-severity exposures
+- Integration: tRPC procedures enforce user ownership
+
+acceptance_criteria:
+- [ ] Watchlist items can be added, listed, and removed per user
+- [ ] Exposures are queryable with filtering and pagination
+- [ ] Manual scan orchestrates all data sources and creates exposure records
+- [ ] Tier limits prevent excessive scanning based on subscription level
+- [ ] Alert pipeline creates notifications for new/high-severity exposures
+- [ ] External API failures are handled gracefully (circuit breaker, retries)
+- [ ] All user data is properly scoped (users cannot see other users' exposures)
+
+validation:
+- Add a test watchlist item, run manual scan, verify exposure records created
+- Check that exceeding tier limit returns appropriate error
+- Simulate HIBP API failure and verify circuit breaker opens
+- Run `cd web && pnpm test` for DarkWatch unit tests
+
+notes:
+- Reference legacy: `services/darkwatch/src/`, `packages/api/src/routes/darkwatch.routes.ts`
+- The HIBP API requires an API key for breach lookups. Store in `HIBP_API_KEY`.
+- SecurityTrails, Censys, and Shodan also require API keys. Document all required env vars.
+- SSN values should be hashed before storage (already in schema). Never log raw SSNs.
+- The scan engine should be designed to run both synchronously (manual scan) and asynchronously (scheduled scans via task 22).
+- Consider rate-limiting the `runScan` endpoint independently to prevent abuse.
diff --git a/tasks/shieldai-unified-restructure/16-voiceprint-router.md b/tasks/shieldai-unified-restructure/16-voiceprint-router.md
new file mode 100644
index 0000000..d872d94
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/16-voiceprint-router.md
@@ -0,0 +1,102 @@
+# 16. Backend Router — VoicePrint (Voice Cloning Detection)
+
+meta:
+ id: shieldai-unified-restructure-16
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-12, shieldai-unified-restructure-13, shieldai-unified-restructure-14]
+ tags: [backend, trpc, voiceprint, ml, api]
+
+objective:
+- Build the tRPC router for VoicePrint, the AI voice cloning detection service. Port all logic from `services/voiceprint/` and `packages/api/src/routes/voiceprint.routes.ts` into a unified `voiceprint` router and service layer.
+
+deliverables:
+- `web/src/server/api/routers/voiceprint.ts` — VoicePrint router:
+ - `voiceprint.getEnrollments` — `protectedProcedure` returning voice enrollments
+ - `voiceprint.createEnrollment` — `protectedProcedure` uploading and processing voice sample
+ - `voiceprint.deleteEnrollment` — `protectedProcedure` removing enrollment
+ - `voiceprint.analyzeAudio` — `protectedProcedure` analyzing audio for synthetic voice detection
+ - `voiceprint.getAnalyses` — `protectedProcedure` returning analysis history
+ - `voiceprint.getAnalysisResult` — `protectedProcedure` returning detailed analysis results
+ - `voiceprint.getJobStatus` — `protectedProcedure` checking batch analysis job status
+- `web/src/server/services/voiceprint.service.ts` — Core business logic:
+ - `createEnrollment(userId, name, audioBuffer, metadata)` — save audio, generate embedding hash
+ - `deleteEnrollment(userId, enrollmentId)` — remove audio file and DB record
+ - `analyzeAudio(userId, audioBuffer, enrollmentId?)` — run ML detection:
+ - Preprocess audio (VAD, noise reduction)
+ - Run ECAPA-TDNN model for synthetic detection
+ - If enrollment provided, run FAISS vector matching
+ - Return confidence score and verdict
+ - `getAnalyses(userId, filters?)` — query analysis history
+ - `createBatchJob(userId, audioFilePath)` — create analysis job for async processing
+- `web/src/server/services/voiceprint/ml.engine.ts` — ML inference:
+ - `preprocessAudio(audioBuffer)` — VAD, resampling, noise reduction
+ - `detectSynthetic(audioFeatures)` — ECAPA-TDNN inference
+ - `matchVoice(embedding, enrollmentId)` — FAISS vector index search
+ - `generateEmbedding(audioFeatures)` — create voice embedding vector
+- `web/src/server/services/voiceprint/storage.ts` — Audio file storage:
+ - `saveAudio(userId, audioBuffer)` — save to local disk or S3-compatible storage
+ - `getAudioUrl(userId, audioHash)` — generate signed URL for retrieval
+ - `deleteAudio(audioHash)` — remove file
+
+steps:
+1. Create `web/src/server/api/routers/voiceprint.ts`.
+2. Define Zod schemas:
+ - `createEnrollmentSchema`: `name: z.string().min(1)`, `audioBase64: z.string()` (or multipart handling)
+ - `analyzeAudioSchema`: `audioBase64: z.string()`, `enrollmentId: z.string().uuid().optional()`
+ - `analysisFilterSchema`: `page`, `limit`, `verdict` optional
+3. Implement router procedures:
+ - Enrollment CRUD with user ownership
+ - Audio analysis with optional enrollment matching
+ - Job status queries
+4. Create `web/src/server/services/voiceprint.service.ts`:
+ - Port from `services/voiceprint/src/voiceprint.service.ts`
+ - Handle audio preprocessing pipeline
+ - Integrate with ML engine
+5. Create ML engine:
+ - `preprocessAudio`: use WebRTC VAD logic or a Node.js equivalent (e.g., `node-vad`)
+ - `detectSynthetic`: placeholder for ECAPA-TDNN model integration. If model is not available in JS, create a Python microservice bridge or use ONNX Runtime.
+ - `matchVoice`: placeholder for FAISS integration. If FAISS is not available in JS, use `faiss-node` or a Python bridge.
+ - `generateEmbedding`: create embedding vector for storage
+6. Create storage module:
+ - For local dev: save to `uploads/voiceprint/{userId}/{hash}.wav`
+ - For production: integrate with S3, R2, or similar
+ - Generate presigned URLs for client retrieval
+7. Implement analysis pipeline:
+ - Save audio → preprocess → run detection → store result → create alert if synthetic detected
+ - If enrollment provided, also run matching and include similarity score
+8. Wire router into `web/src/server/api/root.ts`.
+9. Write unit tests for service functions (mock ML engine).
+
+steps:
+- Unit: `createEnrollment` saves audio and creates DB record
+- Unit: `analyzeAudio` returns verdict and confidence
+- Unit: `matchVoice` returns similarity score for enrolled voice
+- Unit: Storage module saves and retrieves files correctly
+- Unit: ML engine placeholders return mock results
+- Integration: tRPC procedures enforce user ownership of enrollments
+
+acceptance_criteria:
+- [ ] Voice enrollments can be created, listed, and deleted per user
+- [ ] Audio analysis returns synthetic/natural/uncertain verdict with confidence score
+- [ ] If enrollment is provided, analysis includes voice matching similarity
+- [ ] Analysis history is queryable with pagination
+- [ ] Batch jobs can be created and their status tracked
+- [ ] Audio files are stored securely with user-scoped access
+- [ ] Synthetic voice detection triggers an alert notification
+
+validation:
+- Upload a test audio file via tRPC client, verify enrollment created
+- Request analysis on test audio, verify result structure (verdict, confidence, metadata)
+- Verify that user A cannot access user B's enrollments or analyses
+- Run `cd web && pnpm test` for VoicePrint unit tests
+
+notes:
+- Reference legacy: `services/voiceprint/src/`, `packages/api/src/routes/voiceprint.routes.ts`
+- The ECAPA-TDNN and FAISS components may require Python or compiled native modules. If they cannot run in the Node.js monolith:
+ - Option A: Create a lightweight Python gRPC/HTTP service for ML inference, call it from the monolith
+ - Option B: Use ONNX Runtime Node.js bindings if a converted model is available
+ - Option C: Keep the Python service separate but unify the API layer in tRPC (the monolith calls the Python service internally)
+- For this task, implement the service layer with a pluggable ML engine interface. Use mock/stub implementations if native ML is not yet available.
+- Audio files can be large. Consider streaming uploads instead of base64 encoding for production.
+- The analysis pipeline should be idempotent: analyzing the same audio twice should return cached results.
diff --git a/tasks/shieldai-unified-restructure/17-spamshield-router.md b/tasks/shieldai-unified-restructure/17-spamshield-router.md
new file mode 100644
index 0000000..05aa18a
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/17-spamshield-router.md
@@ -0,0 +1,102 @@
+# 17. Backend Router — SpamShield (Spam Detection & Call Analysis)
+
+meta:
+ id: shieldai-unified-restructure-17
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-12, shieldai-unified-restructure-13, shieldai-unified-restructure-14]
+ tags: [backend, trpc, spamshield, ml, api]
+
+objective:
+- Build the tRPC router for SpamShield, the spam detection and call analysis service. Port all logic from `services/spamshield/` and `packages/api/src/routes/spamshield.routes.ts` into a unified `spamshield` router and service layer.
+
+deliverables:
+- `web/src/server/api/routers/spamshield.ts` — SpamShield router:
+ - `spamshield.checkNumber` — `publicProcedure` (or API-key protected) checking phone number reputation
+ - `spamshield.classifySMS` — `publicProcedure` classifying SMS text as spam/ham
+ - `spamshield.classifyCall` — `publicProcedure` analyzing call metadata for spam likelihood
+ - `spamshield.getRules` — `protectedProcedure` returning user's spam rules
+ - `spamshield.createRule` — `protectedProcedure` creating a custom spam rule
+ - `spamshield.deleteRule` — `protectedProcedure` deleting a rule
+ - `spamshield.submitFeedback` — `protectedProcedure` submitting false positive/negative feedback
+ - `spamshield.getStats` — `protectedProcedure` returning spam detection statistics
+- `web/src/server/services/spamshield.service.ts` — Core business logic:
+ - `checkNumberReputation(phoneNumber)` — query Hiya/Truecaller/other reputation APIs
+ - `classifySMS(text)` — run BERT-based spam classification
+ - `classifyCall(metadata)` — run rule engine + ML model on call data
+ - `createRule(userId, ruleType, pattern, action)` — save custom rule
+ - `applyRules(userId, phoneNumber, text?)` — evaluate custom rules against input
+ - `submitFeedback(userId, phoneNumber, isSpam, feedbackType)` — log feedback for model retraining
+ - `getStats(userId, period?)` — aggregate detection stats
+- `web/src/server/services/spamshield/ml.engine.ts` — ML inference:
+ - `classifyTextBERT(text)` — BERT model inference for SMS spam
+ - `extractFeatures(metadata)` — feature extraction for call analysis
+ - `ruleEngine(rules, input)` — evaluate user-defined and global rules
+- `web/src/server/services/spamshield/reputation.api.ts` — External reputation lookups:
+ - `lookupHiya(phoneNumber)` — Hiya API
+ - `lookupTruecaller(phoneNumber)` — Truecaller API
+ - `lookupInternalDB(phoneNumber)` — query cached reputation scores
+
+steps:
+1. Create `web/src/server/api/routers/spamshield.ts`.
+2. Define Zod schemas:
+ - `checkNumberSchema`: `phoneNumber: z.string()` (E.164 format validation)
+ - `classifySMSSchema`: `text: z.string().max(2000)`
+ - `classifyCallSchema`: `callerNumber: z.string()`, `duration: z.number().optional()`, `timeOfDay: z.number().optional()`
+ - `createRuleSchema`: `ruleType: z.enum([...])`, `pattern: z.string()`, `action: z.enum([...])`, `priority: z.number().default(0)`
+ - `feedbackSchema`: `phoneNumber: z.string()`, `isSpam: z.boolean()`, `feedbackType: z.enum([...])`
+3. Implement router procedures:
+ - Number reputation check (may be called by extension or mobile apps)
+ - SMS and call classification
+ - Rule CRUD with user scoping
+ - Feedback submission
+4. Create `web/src/server/services/spamshield.service.ts`:
+ - Port from `services/spamshield/src/`
+ - Implement number normalization (E.164)
+ - Implement reputation caching (Redis or in-memory with TTL)
+5. Create ML engine:
+ - `classifyTextBERT`: placeholder for BERT model. If not available in JS, create a Python bridge or use a pre-trained ONNX model.
+ - `extractFeatures`: derive features from call metadata (time patterns, area code, duration)
+ - `ruleEngine`: evaluate regex patterns, area code blocks, prefix blocks, reputation scores
+6. Create reputation API module:
+ - Implement circuit breaker for external APIs (reference legacy `services/spamshield/test/circuit-breaker.test.ts`)
+ - Cache results in DB or Redis for 24 hours
+ - Fallback to internal database if external APIs fail
+7. Implement audit logging:
+ - Every classification decision is logged to `AuditLog` table
+ - Include input, output, confidence, model version, timestamp
+8. Wire router into `web/src/server/api/root.ts`.
+9. Write unit tests with mocked ML engine and reputation APIs.
+
+steps:
+- Unit: `checkNumberReputation` normalizes phone and queries APIs with circuit breaker
+- Unit: `classifySMS` returns spam/ham with confidence
+- Unit: `ruleEngine` evaluates custom rules correctly
+- Unit: `submitFeedback` creates feedback record
+- Unit: Audit logging captures all classification decisions
+- Integration: tRPC `checkNumber` returns reputation for valid E.164 number
+
+acceptance_criteria:
+- [ ] Phone numbers are normalized to E.164 before processing
+- [ ] Number reputation checks query external APIs with circuit breaker and caching
+- [ ] SMS classification returns spam/ham verdict with confidence score
+- [ ] Call analysis evaluates rules and ML model
+- [ ] Users can create, list, and delete custom spam rules
+- [ ] Feedback submissions are logged for model improvement
+- [ ] All classification decisions are audit-logged
+- [ ] Stats endpoint returns aggregated detection metrics per user
+
+validation:
+- Call `spamshield.checkNumber` with a test phone number → verify reputation response
+- Call `spamshield.classifySMS` with known spam text → verify high spam score
+- Create a custom rule and verify it blocks matching numbers
+- Submit feedback and verify record created in DB
+- Run `cd web && pnpm test` for SpamShield unit tests
+
+notes:
+- Reference legacy: `services/spamshield/src/`, `packages/api/src/routes/spamshield.routes.ts`
+- The BERT model for SMS classification may require Python. Use the same approach as VoicePrint: pluggable ML engine with Python bridge or ONNX.
+- Hiya and Truecaller APIs require commercial agreements. For development, mock these or use free alternatives like NumVerify.
+- The `checkNumber` endpoint may receive high traffic from the browser extension. Ensure it is rate-limited and cached aggressively.
+- Consider adding a global spam database that accumulates feedback from all users (anonymized) to improve detection.
+- The rule engine should support both user-specific rules and global admin rules.
diff --git a/tasks/shieldai-unified-restructure/18-hometitle-router.md b/tasks/shieldai-unified-restructure/18-hometitle-router.md
new file mode 100644
index 0000000..9c41e4f
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/18-hometitle-router.md
@@ -0,0 +1,96 @@
+# 18. Backend Router — HomeTitle (Property Monitoring)
+
+meta:
+ id: shieldai-unified-restructure-18
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-12, shieldai-unified-restructure-13, shieldai-unified-restructure-14]
+ tags: [backend, trpc, hometitle, property, api]
+
+objective:
+- Build the tRPC router for HomeTitle, the property fraud monitoring service. Port all logic from `services/hometitle/` and `packages/api/src/routes/hometitle.routes.ts` into a unified `hometitle` router and service layer.
+
+deliverables:
+- `web/src/server/api/routers/hometitle.ts` — HomeTitle router:
+ - `hometitle.getProperties` — `protectedProcedure` returning watched properties
+ - `hometitle.addProperty` — `protectedProcedure` adding property to watchlist
+ - `hometitle.removeProperty` — `protectedProcedure` removing property
+ - `hometitle.getSnapshots` — `protectedProcedure` returning property record snapshots
+ - `hometitle.getChanges` — `protectedProcedure` returning detected property changes
+ - `hometitle.runScan` — `protectedProcedure` triggering manual property scan
+ - `hometitle.getAlerts` — `protectedProcedure` returning property fraud alerts
+- `web/src/server/services/hometitle.service.ts` — Core business logic:
+ - `addProperty(subscriptionId, address, parcelId?, ownerName?)` — geocode address, save record
+ - `removeProperty(userId, propertyId)` — delete and cascade
+ - `getSnapshots(propertyId)` — query snapshot history
+ - `getChanges(propertyId, filters?)` — query changes with severity filtering
+ - `runScan(userId)` — scan all watched properties:
+ - Fetch current county records
+ - Compare with last snapshot
+ - Detect changes: ownership transfer, lien filing, tax change, metadata change
+ - `generateAlert(change)` — create alert if change severity is warning/critical
+- `web/src/server/services/hometitle/scanner.ts` — County record scanning:
+ - `fetchCountyRecords(parcelId, county, state)` — query county assessor/recorder APIs
+ - `parseDeedRecords(html)` — extract ownership, date, lien info from HTML/PDF
+ - `geocodeAddress(address)` — convert address to lat/lng using geocoding API
+- `web/src/server/services/hometitle/change.detector.ts` — Change detection:
+ - `detectChanges(oldSnapshot, newData)` — compare fields and classify changes
+ - `severityForChange(changeType, magnitude)` — determine severity level
+ - `fuzzyMatchNames(name1, name2)` — Levenshtein distance for owner name comparison
+
+steps:
+1. Create `web/src/server/api/routers/hometitle.ts`.
+2. Define Zod schemas:
+ - `addPropertySchema`: `address: z.string()`, `parcelId: z.string().optional()`, `ownerName: z.string().optional()`
+ - `changeFilterSchema`: `severity: z.enum(['info', 'warning', 'critical']).optional()`, `changeType: z.enum([...]).optional()`
+3. Implement router procedures:
+ - Property CRUD with subscription scoping
+ - Snapshot and change queries
+ - Manual scan with tier limit enforcement
+4. Create `web/src/server/services/hometitle.service.ts`:
+ - Port from `services/hometitle/src/`
+ - Implement property geocoding on add
+ - Implement scan orchestration
+5. Create scanner module:
+ - `fetchCountyRecords`: placeholder for county API integration. Many counties lack APIs — document which counties are supported.
+ - `parseDeedRecords`: HTML parsing with Cheerio or similar
+ - `geocodeAddress`: use Google Maps, OpenStreetMap, or similar geocoding service
+6. Create change detector:
+ - Compare snapshot fields: ownerName, address, deedDate, taxAmount, lienCount
+ - Use fuzzy string matching for owner names
+ - Classify changes into: ownership_transfer, lien_filing, tax_change, metadata_change
+7. Implement alert pipeline:
+ - On significant change (warning/critical), create `Alert` and `NormalizedAlert`
+ - Trigger notification via task 14 service
+8. Wire router into `web/src/server/api/root.ts`.
+9. Write unit tests with mocked county data.
+
+steps:
+- Unit: `addProperty` geocodes address and creates record
+- Unit: `detectChanges` identifies ownership transfer and lien filing
+- Unit: `fuzzyMatchNames` handles minor spelling variations
+- Unit: `severityForChange` returns correct severity per change type
+- Integration: tRPC procedures enforce subscription scoping
+
+acceptance_criteria:
+- [ ] Properties can be added with address geocoding and optional parcel ID
+- [ ] Properties are scoped to the user's subscription
+- [ ] Snapshots capture property record state at a point in time
+- [ ] Changes are detected by comparing new data to last snapshot
+- [ ] Manual scan can be triggered and respects tier limits
+- [ ] Alerts are generated for warning/critical changes
+- [ ] Fuzzy matching handles minor variations in owner names
+
+validation:
+- Add a test property, verify geocoding and record creation
+- Simulate a snapshot change, verify change detection identifies the difference
+- Run manual scan and verify scan completion
+- Run `cd web && pnpm test` for HomeTitle unit tests
+
+notes:
+- Reference legacy: `services/hometitle/src/`, `packages/api/src/routes/hometitle.routes.ts`
+- County record APIs are highly fragmented. The scanner should be designed as a plugin system where each county has its own adapter.
+- For unsupported counties, the scan should gracefully degrade and inform the user.
+- Property data may be sensitive. Ensure all records are encrypted at rest if required by compliance.
+- Consider integrating with a third-party property data provider (e.g., CoreLogic, ATTOM) for broader coverage.
+- The change detector should be configurable: users can choose which change types they want alerts for.
diff --git a/tasks/shieldai-unified-restructure/19-removebrokers-router.md b/tasks/shieldai-unified-restructure/19-removebrokers-router.md
new file mode 100644
index 0000000..fcd4034
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/19-removebrokers-router.md
@@ -0,0 +1,99 @@
+# 19. Backend Router — RemoveBrokers (Data Broker Removal)
+
+meta:
+ id: shieldai-unified-restructure-19
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-12, shieldai-unified-restructure-13, shieldai-unified-restructure-14]
+ tags: [backend, trpc, removebrokers, privacy, api]
+
+objective:
+- Build the tRPC router for RemoveBrokers, the data broker removal service. Port all logic from `services/removebrokers/` and `packages/api/src/routes/removebrokers.routes.ts` into a unified `removebrokers` router and service layer.
+
+deliverables:
+- `web/src/server/api/routers/removebrokers.ts` — RemoveBrokers router:
+ - `removebrokers.getBrokerRegistry` — `protectedProcedure` returning list of supported brokers
+ - `removebrokers.getRemovalRequests` — `protectedProcedure` returning user's removal requests
+ - `removebrokers.createRemovalRequest` — `protectedProcedure` initiating removal for a broker
+ - `removebrokers.getRequestStatus` — `protectedProcedure` checking removal progress
+ - `removebrokers.getBrokerListings` — `protectedProcedure` returning found listings
+ - `removebrokers.scanForListings` — `protectedProcedure` scanning brokers for user's data
+ - `removebrokers.getStats` — `protectedProcedure` returning removal statistics
+- `web/src/server/services/removebrokers.service.ts` — Core business logic:
+ - `getBrokerRegistry()` — return all active brokers with metadata
+ - `createRemovalRequest(subscriptionId, brokerId, personalInfo)` — validate, create request, initiate removal
+ - `getRequestStatus(requestId)` — return current status and history
+ - `scanForListings(subscriptionId, brokerId)` — search broker site for user's data
+ - `processRemovals()` — batch processor for pending removals
+ - `updateRequestStatus(requestId, status, metadata)` — update after broker response
+- `web/src/server/services/removebrokers/broker.registry.ts` — Broker definitions:
+ - Static registry of supported data brokers
+ - Each entry: name, domain, category, removalMethod, removalUrl, requiresAccount, estimatedDays
+ - Methods: AUTOMATED, MANUAL_FORM, EMAIL, PHONE, MAIL
+- `web/src/server/services/removebrokers/removal.engine.ts` — Removal automation:
+ - `submitAutomatedRemoval(broker, personalInfo)` — API-based removal where available
+ - `generateFormPayload(broker, personalInfo)` — prepare form data for manual submission
+ - `sendRemovalEmail(broker, personalInfo)` — email-based removal request
+ - `trackRemovalStatus(broker, requestId)` — poll broker for status updates
+
+steps:
+1. Create `web/src/server/api/routers/removebrokers.ts`.
+2. Define Zod schemas:
+ - `createRequestSchema`: `brokerId: z.string().uuid()`, `personalInfo: z.object({ fullName: z.string(), email: z.string().optional(), phone: z.string().optional(), address: z.string().optional(), dob: z.string().optional() })`
+ - `scanSchema`: `brokerId: z.string().uuid().optional()` (if omitted, scan all)
+3. Implement router procedures:
+ - Broker registry listing (public or protected)
+ - Removal request CRUD
+ - Listing scan and results
+ - Stats aggregation
+4. Create `web/src/server/services/removebrokers.service.ts`:
+ - Port from `services/removebrokers/src/`
+ - Implement request lifecycle: PENDING → SUBMITTED → IN_PROGRESS → COMPLETED/FAILED
+5. Create broker registry:
+ - Hardcode initial list of 20-50 major data brokers
+ - Include removal instructions and URLs
+ - Allow admin updates via API (future enhancement)
+6. Create removal engine:
+ - `submitAutomatedRemoval`: call broker API if available (rare)
+ - `generateFormPayload`: create structured data for form filling
+ - `sendRemovalEmail`: use notification service (task 14) to send removal request email
+ - `trackRemovalStatus`: placeholder for polling logic
+7. Implement listing scanner:
+ - Search broker websites for user's name, email, phone
+ - Use web scraping (Cheerio, Playwright) where APIs are unavailable
+ - Store found listings in `BrokerListing` table
+8. Implement scheduler integration:
+ - Pending removals should be picked up by background job (task 22)
+ - Retries for failed removals with exponential backoff
+9. Wire router into `web/src/server/api/root.ts`.
+10. Write unit tests with mocked broker interactions.
+
+steps:
+- Unit: `createRemovalRequest` creates record with PENDING status
+- Unit: `processRemovals` advances eligible requests to SUBMITTED
+- Unit: `scanForListings` creates BrokerListing records for found data
+- Unit: Broker registry returns correct metadata for known brokers
+- Integration: tRPC procedures enforce subscription scoping
+
+acceptance_criteria:
+- [ ] Broker registry lists all supported data brokers with removal methods
+- [ ] Removal requests can be created per broker with personal info
+- [ ] Request status tracks lifecycle from PENDING to COMPLETED/FAILED
+- [ ] Listings scanner finds user's data on broker sites
+- [ ] Automated removals use APIs where available; manual methods generate instructions
+- [ ] Failed removals are retried with exponential backoff
+- [ ] Stats endpoint shows removal progress (total, completed, pending, failed)
+
+validation:
+- List brokers and verify registry data
+- Create a removal request and verify DB record
+- Simulate a scan and verify listing records created
+- Run `cd web && pnpm test` for RemoveBrokers unit tests
+
+notes:
+- Reference legacy: `services/removebrokers/src/`, `packages/api/src/routes/removebrokers.routes.ts`
+- Data broker removal is often a manual process. The automation layer should handle the easy cases and provide clear instructions for manual cases.
+- Web scraping broker sites may violate Terms of Service. Use public APIs where available and document legal considerations.
+- Personal info submitted for removal should be handled carefully. Do not log raw PII.
+- The removal engine should be designed as a plugin system: each broker can have its own adapter for API, form, or email removal.
+- Consider integrating with a third-party service (e.g., DeleteMe, Optery) for broader broker coverage if building individual adapters is impractical.
diff --git a/tasks/shieldai-unified-restructure/20-alert-correlation-router.md b/tasks/shieldai-unified-restructure/20-alert-correlation-router.md
new file mode 100644
index 0000000..1301137
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/20-alert-correlation-router.md
@@ -0,0 +1,102 @@
+# 20. Backend Router — Alert Correlation & Normalization Engine
+
+meta:
+ id: shieldai-unified-restructure-20
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-15, shieldai-unified-restructure-16, shieldai-unified-restructure-17, shieldai-unified-restructure-18, shieldai-unified-restructure-19]
+ tags: [backend, trpc, correlation, alerts, api]
+
+objective:
+- Build the tRPC router and service layer for the cross-service alert correlation and normalization engine. Port logic from `packages/correlation/` into a unified `correlation` router that aggregates alerts from all services into a unified threat view.
+
+deliverables:
+- `web/src/server/api/routers/correlation.ts` — Correlation router:
+ - `correlation.getAlerts` — `protectedProcedure` returning normalized alerts for user
+ - `correlation.getAlertDetails` — `protectedProcedure` returning single alert with correlated data
+ - `correlation.getGroups` — `protectedProcedure` returning correlation groups
+ - `correlation.getGroupDetails` — `protectedProcedure` returning group with all member alerts
+ - `correlation.resolveAlert` — `protectedProcedure` marking alert as resolved or false positive
+ - `correlation.getStats` — `protectedProcedure` returning alert statistics
+- `web/src/server/services/correlation.service.ts` — Core business logic:
+ - `normalizeAlert(source, sourceAlertId, category, severity, userId, title, description, entities)` — create NormalizedAlert
+ - `correlateAlerts(userId)` — group related alerts by shared entities (email, phone, SSN)
+ - `getAlertTimeline(userId, filters?)` — chronological view of all alerts
+ - `resolveAlert(alertId, resolution)` — mark as resolved or false positive
+ - `getThreatScore(userId)` — calculate overall threat score based on alert severity and frequency
+- `web/src/server/services/correlation/engine.ts` — Correlation engine:
+ - `findRelatedAlerts(alert)` — find alerts sharing entities with given alert
+ - `createCorrelationGroup(alerts)` — group related alerts into CorrelationGroup
+ - `updateGroupSeverity(group)` — recalculate highest severity for group
+ - `deduplicateAlerts(alerts)` — remove duplicate alerts based on sourceAlertId
+- `web/src/server/services/correlation/normalizer.ts` — Alert normalization:
+ - `normalizeDarkWatchAlert(exposure)` — convert exposure to NormalizedAlert
+ - `normalizeSpamShieldAlert(detection)` — convert spam detection to NormalizedAlert
+ - `normalizeVoicePrintAlert(analysis)` — convert voice analysis to NormalizedAlert
+ - `normalizeHomeTitleAlert(change)` — convert property change to NormalizedAlert
+ - `normalizeRemoveBrokersAlert(listing)` — convert broker listing to NormalizedAlert
+
+steps:
+1. Create `web/src/server/api/routers/correlation.ts`.
+2. Define Zod schemas:
+ - `alertFilterSchema`: `source: z.enum([...]).optional()`, `severity: z.enum([...]).optional()`, `status: z.enum([...]).optional()`, `page`, `limit`
+ - `resolveSchema`: `alertId: z.string().uuid()`, `resolution: z.enum(['RESOLVED', 'FALSE_POSITIVE'])`
+3. Implement router procedures:
+ - Alert listing with filtering and pagination
+ - Group listing and details
+ - Alert resolution with audit logging
+ - Stats aggregation
+4. Create `web/src/server/services/correlation.service.ts`:
+ - Port from `packages/correlation/src/`
+ - Implement normalization pipeline
+ - Implement correlation grouping
+5. Create correlation engine:
+ - `findRelatedAlerts`: query alerts by shared entities (email, phone, SSN) within time window
+ - `createCorrelationGroup`: create group record, link alerts
+ - `updateGroupSeverity`: aggregate severity of all alerts in group
+ - `deduplicateAlerts`: ensure no duplicate sourceAlertId in normalized table
+6. Create normalizer module:
+ - One function per service domain that converts domain-specific alert to NormalizedAlert
+ - Map domain severity to NormalizedAlertSeverity enum
+ - Extract entities (emails, phones, SSNs) from payload for correlation
+7. Implement threat scoring:
+ - Formula: weighted sum of alert severities over 30-day window
+ - Decay older alerts
+ - Return score 0-100
+8. Integrate with other services:
+ - Call `correlationService.normalizeAlert()` from each service's alert pipeline
+ - DarkWatch (task 15), VoicePrint (task 16), SpamShield (task 17), HomeTitle (task 18), RemoveBrokers (task 19)
+9. Wire router into `web/src/server/api/root.ts`.
+10. Write unit tests for engine functions.
+
+steps:
+- Unit: `normalizeAlert` creates correct NormalizedAlert for each source type
+- Unit: `findRelatedAlerts` groups alerts sharing an email address
+- Unit: `createCorrelationGroup` creates group with correct highest severity
+- Unit: `deduplicateAlerts` prevents duplicate sourceAlertId
+- Unit: `getThreatScore` returns higher score for more severe/recent alerts
+- Integration: tRPC `getAlerts` returns normalized alerts for authenticated user
+
+acceptance_criteria:
+- [ ] Alerts from all 5 services are normalized into a unified schema
+- [ ] Related alerts are grouped by shared entities (email, phone, SSN)
+- [ ] Correlation groups update their severity when new alerts are added
+- [ ] Users can resolve alerts or mark them as false positives
+- [ ] Alert timeline provides chronological view across all services
+- [ ] Threat score accurately reflects user's current risk level
+- [ ] Deduplication prevents duplicate alerts from the same source
+
+validation:
+- Create normalized alerts from different services with shared email
+- Verify correlation engine groups them into a single group
+- Resolve an alert and verify status updated in DB
+- Calculate threat score for a user with mixed alert severities
+- Run `cd web && pnpm test` for correlation unit tests
+
+notes:
+- Reference legacy: `packages/correlation/src/`, `packages/api/src/routes/correlation.routes.ts`
+- The correlation engine is the "brain" that makes ShieldAI feel unified. Invest time in getting the entity extraction and grouping logic right.
+- Entity extraction should use regex patterns for emails, phones, and SSNs. Consider using a library like `compromise` for NLP extraction if payloads are unstructured.
+- Time window for correlation: alerts within 30 days sharing an entity should be grouped. Adjust based on testing.
+- The threat score algorithm should be transparent to users. Consider showing the breakdown (e.g., "+20 from DarkWatch exposure, +15 from SpamShield detection").
+- False positive tracking is important for ML model improvement. Log all false positive marks with context.
diff --git a/tasks/shieldai-unified-restructure/21-report-generation-router.md b/tasks/shieldai-unified-restructure/21-report-generation-router.md
new file mode 100644
index 0000000..5e78e9e
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/21-report-generation-router.md
@@ -0,0 +1,105 @@
+# 21. Backend Router — Security Report Generation
+
+meta:
+ id: shieldai-unified-restructure-21
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-20]
+ tags: [backend, trpc, reports, pdf, api]
+
+objective:
+- Build the tRPC router for security report generation. Port logic from `packages/report/` and `packages/api/src/routes/report.routes.ts` into a unified `reports` router that generates branded PDF and HTML security reports.
+
+deliverables:
+- `web/src/server/api/routers/reports.ts` — Reports router:
+ - `reports.getReports` — `protectedProcedure` returning user's generated reports
+ - `reports.generateReport` — `protectedProcedure` triggering report generation
+ - `reports.getReport` — `protectedProcedure` returning single report with download URL
+ - `reports.deleteReport` — `protectedProcedure` deleting a report
+ - `reports.getScheduledReports` — `protectedProcedure` returning scheduled report settings
+ - `reports.updateSchedule` — `protectedProcedure` updating report schedule
+- `web/src/server/services/reports.service.ts` — Core business logic:
+ - `generateReport(userId, reportType, periodStart, periodEnd)` — compile data and generate report:
+ - Collect alerts, exposures, detections, changes for period
+ - Calculate threat score trend
+ - Generate HTML content
+ - Convert to PDF
+ - Save to storage and update DB
+ - `getReports(userId, filters?)` — query generated reports
+ - `deleteReport(userId, reportId)` — remove file and DB record
+ - `getScheduledReports(userId)` — read schedule preferences
+ - `updateSchedule(userId, schedule)` — write schedule preferences
+- `web/src/server/services/reports/generator.ts` — Report generator:
+ - `compileData(userId, periodStart, periodEnd)` — aggregate data from all services
+ - `renderHTML(data)` — generate branded HTML using template engine
+ - `generatePDF(html)` — convert HTML to PDF using Puppeteer or PDFKit
+ - `uploadPDF(userId, pdfBuffer, filename)` — save to storage, return URL
+- `web/src/server/services/reports/templates/` — HTML templates:
+ - `monthly-plus.html` — Monthly report for Plus tier
+ - `annual-premium.html` — Annual comprehensive report for Premium tier
+ - `weekly-digest.html` — Weekly summary email format
+ - Shared partials: header, footer, alert list, exposure table, trend chart
+
+steps:
+1. Create `web/src/server/api/routers/reports.ts`.
+2. Define Zod schemas:
+ - `generateReportSchema`: `reportType: z.enum(['MONTHLY_PLUS', 'ANNUAL_PREMIUM', 'WEEKLY_DIGEST'])`, `periodStart: z.date().optional()`, `periodEnd: z.date().optional()`
+ - `updateScheduleSchema`: `enabled: z.boolean()`, `frequency: z.enum(['weekly', 'monthly', 'quarterly'])`, `reportType: z.enum([...])`
+3. Implement router procedures:
+ - Report listing with pagination
+ - Report generation (async, returns job ID or report ID)
+ - Report retrieval with presigned download URL
+ - Schedule CRUD
+4. Create `web/src/server/services/reports.service.ts`:
+ - Port from `packages/report/src/`
+ - Implement report lifecycle: PENDING → GENERATING → COMPLETED/FAILED
+5. Create report generator:
+ - `compileData`: query normalized alerts, exposures, voice analyses, spam detections, property changes for the period
+ - Calculate trends: compare current period to previous period
+ - `renderHTML`: use Handlebars or EJS template engine with ShieldAI branding
+ - `generatePDF`: use `puppeteer` (headless Chrome) for high-fidelity HTML-to-PDF, or `pdfkit` for programmatic generation
+ - `uploadPDF`: save to local disk or S3/R2, generate presigned URL
+6. Create HTML templates:
+ - Design consistent report layout with ShieldAI colors and logo
+ - Include: executive summary, threat score trend, alert breakdown by service, exposure details, recommendations
+ - Make templates responsive for HTML email format
+7. Implement scheduling:
+ - Store schedule preferences in DB (may need new table or extend existing)
+ - Background job (task 22) reads schedules and triggers generation
+8. Wire router into `web/src/server/api/root.ts`.
+9. Write unit tests for generator functions with mocked data.
+
+steps:
+- Unit: `compileData` aggregates correct data for a time period
+- Unit: `renderHTML` produces valid HTML with all sections
+- Unit: `generatePDF` creates readable PDF from HTML
+- Unit: `uploadPDF` saves file and returns accessible URL
+- Integration: tRPC `generateReport` creates report record and triggers generation
+
+acceptance_criteria:
+- [ ] Reports can be generated for weekly, monthly, and annual periods
+- [ ] Reports include data from all 5 services with trend analysis
+- [ ] PDF output is branded with ShieldAI colors and logo
+- [ ] HTML email format is responsive and readable
+- [ ] Report generation status is trackable (PENDING → COMPLETED)
+- [ ] Generated reports are stored securely with user-scoped access
+- [ ] Scheduled reports can be configured per user
+- [ ] Report deletion removes both file and DB record
+
+validation:
+- Generate a test report and verify PDF output
+- Open PDF and check branding, data accuracy, and readability
+- Test HTML email template by rendering in browser
+- Verify scheduled report configuration persists in DB
+- Run `cd web && pnpm test` for reports unit tests
+
+notes:
+- Reference legacy: `packages/report/src/`, `packages/api/src/routes/report.routes.ts`
+- Puppeteer adds significant dependency weight (~100MB Chromium). For serverless environments, consider:
+ - `pdfkit` or `jsPDF` for lighter PDF generation (less HTML fidelity)
+ - `@sparticuz/chromium` for serverless-compatible Puppeteer
+ - Offloading PDF generation to a background worker with full Chromium
+- Report generation can be slow. Make the tRPC procedure return immediately with a report ID, and update status asynchronously.
+- For Premium tier, offer annual comprehensive reports. For Plus, monthly. For Basic, only on-demand.
+- Include actionable recommendations in reports (e.g., "Enable two-factor authentication", "Review exposed passwords").
+- Consider adding a "share with advisor" feature that generates a shareable (but limited) report link.
diff --git a/tasks/shieldai-unified-restructure/22-background-jobs.md b/tasks/shieldai-unified-restructure/22-background-jobs.md
new file mode 100644
index 0000000..72b71eb
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/22-background-jobs.md
@@ -0,0 +1,110 @@
+# 22. Background Jobs — Scheduler, Scan Workers, and Reminders
+
+meta:
+ id: shieldai-unified-restructure-22
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-20]
+ tags: [backend, jobs, scheduler, workers, background]
+
+objective:
+- Build the background job system for the unified monolith. Port logic from `packages/jobs/` and all service schedulers into a unified job queue and worker system that handles recurring scans, report generation, and reminder notifications.
+
+deliverables:
+- `web/src/server/jobs/queue.ts` — Job queue abstraction:
+ - Interface for enqueueing, dequeuing, and marking jobs complete/failed
+ - Implementation using `bullmq` with Redis, or lightweight in-memory queue for dev
+ - Job types: `darkwatch.scan`, `voiceprint.batch`, `hometitle.scan`, `removebrokers.process`, `reports.generate`, `notifications.send`
+- `web/src/server/jobs/worker.ts` — Worker process:
+ - Polls queue for jobs
+ - Dispatches to correct handler based on job type
+ - Handles retries with exponential backoff
+ - Logs all job execution
+- `web/src/server/jobs/handlers/` — Job handlers:
+ - `darkwatch.scan.ts` — scheduled dark web scans per subscription tier
+ - `voiceprint.batch.ts` — batch voice analysis jobs
+ - `hometitle.scan.ts` — scheduled property record scans
+ - `removebrokers.process.ts` — process pending removal requests
+ - `reports.generate.ts` — scheduled report generation
+ - `notifications.send.ts` — queued notification delivery
+- `web/src/server/jobs/scheduler.ts` — Cron scheduler:
+ - Registers recurring jobs based on schedules in DB
+ - Tier-based frequencies:
+ - Basic: DarkWatch monthly, HomeTitle monthly
+ - Plus: DarkWatch weekly, HomeTitle weekly, Reports monthly
+ - Premium: DarkWatch daily, HomeTitle daily, Reports weekly, real-time monitoring
+ - Uses `node-cron` or `bullmq` repeatables
+- `web/src/server/jobs/index.ts` — Entry point:
+ - Initializes queue, scheduler, and workers
+ - Graceful shutdown handling
+
+steps:
+1. Install dependencies in `web/`:
+ - `bullmq` (Redis-based queue)
+ - `ioredis` (Redis client)
+ - `node-cron` (cron expressions)
+2. Create `web/src/server/jobs/queue.ts`:
+ - Define `JobType` enum
+ - Define `JobPayload` union type per job type
+ - `enqueue(jobType, payload, options?)` — add job to queue
+ - `dequeue()` — get next job (used by worker)
+ - `markComplete(jobId)`, `markFailed(jobId, error)` — update status
+3. Create `web/src/server/jobs/worker.ts`:
+ - `startWorker()` — begin polling loop
+ - `processJob(job)` — switch on `job.type`, call handler
+ - `stopWorker()` — graceful shutdown
+ - Retry logic: max 3 attempts, exponential backoff (1min, 5min, 15min)
+4. Create job handlers:
+ - Each handler is an async function taking `payload` and returning result
+ - `darkwatch.scan`: query all active watchlists, run scan engine (task 15), create alerts
+ - `voiceprint.batch`: process pending `AnalysisJob` records, run ML inference
+ - `hometitle.scan`: query all watched properties, run county scans (task 18), detect changes
+ - `removebrokers.process`: query PENDING requests, attempt removal, update status
+ - `reports.generate`: compile data, render HTML, generate PDF, save to storage
+ - `notifications.send`: send queued email/push/SMS via notification service (task 14)
+5. Create `web/src/server/jobs/scheduler.ts`:
+ - `registerSchedules()` — read `SchedulerConfig` from DB, create cron jobs
+ - `scheduleForSubscription(subscription)` — create tier-appropriate schedules
+ - `removeSchedulesForSubscription(subscriptionId)` — clean up on cancellation
+6. Create `web/src/server/jobs/index.ts`:
+ - Initialize Redis connection
+ - Start scheduler
+ - Start worker(s)
+ - Handle SIGINT/SIGTERM for graceful shutdown
+7. Integrate with tRPC:
+ - Add `scheduler.runJobNow` admin procedure for manual job triggering
+ - Add `scheduler.getJobStatus` procedure for checking job progress
+8. Write unit tests for handlers with mocked services.
+
+steps:
+- Unit: `enqueue` adds job to queue with correct payload
+- Unit: `processJob` dispatches to correct handler
+- Unit: `darkwatch.scan` handler queries watchlists and triggers scans
+- Unit: Retry logic attempts job 3 times with correct delays
+- Integration: Scheduled job runs and creates expected DB records
+
+acceptance_criteria:
+- [ ] Jobs can be enqueued with type-specific payloads
+- [ ] Worker processes jobs and dispatches to correct handler
+- [ ] Failed jobs are retried up to 3 times with exponential backoff
+- [ ] Scheduled scans run at tier-appropriate frequencies
+- [ ] Manual job triggering works via admin API
+- [ ] Job status is queryable (pending, running, completed, failed)
+- [ ] Graceful shutdown completes in-flight jobs before exiting
+- [ ] All job handlers are tested with mocked dependencies
+
+validation:
+- Enqueue a test job and verify it appears in queue
+- Start worker and verify job is processed
+- Cause a handler to throw and verify retry behavior
+- Verify scheduled jobs are created for a Premium subscription
+- Run `cd web && pnpm test` for job unit tests
+
+notes:
+- Reference legacy: `packages/jobs/src/`, `services/*/src/scheduler.service.ts`
+- Redis is required for BullMQ. For local development without Redis, implement a simple in-memory queue fallback.
+- The worker can run in the same process as the web server (dev) or as a separate process (production). Design for both.
+- Job handlers should be idempotent: running the same job twice should not create duplicates.
+- Consider using `bullmq`'s built-in cron support instead of `node-cron` for better reliability.
+- Monitor queue depth and job failure rates. Add alerting if queue grows unexpectedly.
+- For high-volume operations (e.g., scanning thousands of watchlist items), consider batching jobs or using worker concurrency.
diff --git a/tasks/shieldai-unified-restructure/23-frontend-api-integration.md b/tasks/shieldai-unified-restructure/23-frontend-api-integration.md
new file mode 100644
index 0000000..6c3347f
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/23-frontend-api-integration.md
@@ -0,0 +1,121 @@
+# 23. Frontend Integration — Wire All Pages to tRPC APIs
+
+meta:
+ id: shieldai-unified-restructure-23
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-11, shieldai-unified-restructure-12, shieldai-unified-restructure-13, shieldai-unified-restructure-14, shieldai-unified-restructure-15, shieldai-unified-restructure-16, shieldai-unified-restructure-17, shieldai-unified-restructure-18, shieldai-unified-restructure-19, shieldai-unified-restructure-20, shieldai-unified-restructure-21, shieldai-unified-restructure-22]
+ tags: [frontend, integration, trpc, solidjs]
+
+objective:
+- Wire all frontend pages and components to the tRPC backend, replacing stub data and mock functions with real API calls. Ensure type safety end-to-end from UI to database.
+
+deliverables:
+- `web/src/lib/api.ts` — Updated tRPC client with auth token injection and error handling
+- `web/src/hooks/useAuth.ts` — Auth hook:
+ - Uses `api.user.me` to get current user
+ - Exposes `isAuthenticated`, `user`, `isLoading`, `logout` function
+ - Handles 401 by redirecting to `/login`
+- `web/src/hooks/useSubscription.ts` — Subscription hook:
+ - Uses `api.billing.getSubscription` to get tier
+ - Exposes `tier`, `isLoading`, `hasFeature(feature)` helper
+- `web/src/hooks/useNotifications.ts` — Notification hook:
+ - Uses `api.correlation.getAlerts` for unread count
+ - Exposes `unreadCount`, `alerts`, `markRead(alertId)`
+- Updated pages with real data:
+ - `routes/index.tsx` — Landing page (no API needed, keep static)
+ - `routes/(auth)/login.tsx` — Call auth API (task 11)
+ - `routes/(auth)/signup.tsx` — Call user creation API
+ - `routes/(auth)/onboarding.tsx` — Call user update + watchlist add APIs
+ - `routes/blog.tsx` — Call `api.reports.getReports` or dedicated blog router
+ - `routes/blog/[slug].tsx` — Call blog post API
+ - `routes/(webapp)/dashboard.tsx` — Call correlation, user, subscription APIs
+ - `routes/(webapp)/darkwatch.tsx` — Call darkwatch APIs
+ - `routes/(webapp)/voiceprint.tsx` — Call voiceprint APIs
+ - `routes/(webapp)/spamshield.tsx` — Call spamshield APIs
+ - `routes/(webapp)/hometitle.tsx` — Call hometitle APIs
+ - `routes/(webapp)/removebrokers.tsx` — Call removebrokers APIs
+ - `routes/(webapp)/settings.tsx` — Call user update, notification preference APIs
+- `web/src/components/dashboard/` — Updated widgets with real data:
+ - `StatCard` displays real metrics from API
+ - `ActivityFeed` shows real alerts from correlation API
+ - `QuickActions` triggers real tRPC mutations
+
+steps:
+1. Update `web/src/lib/api.ts`:
+ - Ensure `AppRouter` type is imported from `~/server/api/root`
+ - Add `headers` function that reads auth token from `document.cookie` or `localStorage`
+ - Add `unauthorizedLink` that redirects to `/login` on 401
+2. Create `web/src/hooks/useAuth.ts`:
+ - Use `createAsync` or `createResource` to call `api.user.me`
+ - Return `{ user, isLoading, isAuthenticated: !!user, logout }`
+ - `logout` clears cookie/localStorage and refreshes page
+3. Create `web/src/hooks/useSubscription.ts`:
+ - Call `api.billing.getSubscription`
+ - `hasFeature(feature)` maps feature to required tier (e.g., `darkwatch_realtime` requires Premium)
+4. Create `web/src/hooks/useNotifications.ts`:
+ - Call `api.correlation.getAlerts` with `isRead: false` filter
+ - Poll every 60 seconds for new alerts
+ - `markRead` calls `api.correlation.resolveAlert` with `RESOLVED`
+5. Update auth pages:
+ - Login: call auth mutation, set token/cookie on success, redirect to `/dashboard`
+ - Signup: call user creation mutation, then login
+ - Onboarding: collect data in signals, submit via tRPC mutations at each step
+6. Update dashboard:
+ - Replace mock data in `StatCard` with real API calls
+ - `ActivityFeed` uses `useNotifications` hook
+ - `QuickActions` link to service pages or trigger mutations
+7. Create service pages:
+ - Each page is a SolidStart route under `(webapp)/`
+ - Uses `createResource` to fetch data on mount
+ - Displays loading states and error boundaries
+ - Uses `hasFeature` to gate premium functionality
+8. Update Navbar:
+ - Show user avatar and name from `useAuth`
+ - Show unread notification badge from `useNotifications`
+ - Show/hide Dashboard link based on auth state
+9. Add error handling:
+ - Create `ErrorBoundary` component for tRPC errors
+ - Show toast notifications for mutations (success/error)
+10. Test all pages end-to-end.
+
+steps:
+- E2E: Login flow creates session and redirects to dashboard
+- E2E: Dashboard displays real user data, subscription tier, and alerts
+- E2E: Each service page loads and displays real data
+- E2E: CRUD operations on watchlist, rules, properties work end-to-end
+- E2E: Logout clears session and redirects to landing page
+
+acceptance_criteria:
+- [ ] All auth pages (login, signup, onboarding) use real tRPC mutations
+- [ ] Dashboard displays real user profile, subscription, and alert data
+- [ ] Each service page (DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers) loads real data
+- [ ] Navbar shows authenticated user info and unread alert count
+- [ ] CRUD operations work end-to-end (create watchlist item, run scan, view results)
+- [ ] Error states are handled gracefully with toasts and error boundaries
+- [ ] Unauthenticated users are redirected to login when accessing protected routes
+- [ ] TypeScript compiles without errors (full end-to-end type safety)
+
+validation:
+- Complete full user journey: signup → onboarding → dashboard → add watchlist item → run scan → view alerts → logout
+- Verify data persists across page refreshes
+- Test with different subscription tiers and verify feature gating
+- Run `cd web && pnpm test` for frontend integration tests
+- Run `cd web && pnpm build` and verify no TypeScript errors
+
+notes:
+- This is the "glue" task that makes the app functional. It depends on all backend routers being complete.
+- Use SolidJS `createResource` for data fetching — it handles loading, error, and refresh states automatically.
+- For mutations, use `createMutation` pattern (or manual `api.xxx.mutate()` with signals for loading/error).
+- Consider creating a `useTRPC` hook wrapper that handles common patterns (loading, error, retry).
+- The `hasFeature` helper should be driven by a config map, not hardcoded in components:
+ ```ts
+ const FEATURE_TIERS = {
+ darkwatch_realtime: 'premium',
+ voiceprint_batch: 'plus',
+ hometitle_scan: 'plus',
+ removebrokers_unlimited: 'premium',
+ };
+ ```
+- For pages that don't need tRPC (landing page), keep them static for performance.
+- Ensure all API calls are batched where possible using tRPC's `httpBatchLink`.
diff --git a/tasks/shieldai-unified-restructure/24-dashboard-widgets.md b/tasks/shieldai-unified-restructure/24-dashboard-widgets.md
new file mode 100644
index 0000000..9a15c4d
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/24-dashboard-widgets.md
@@ -0,0 +1,121 @@
+# 24. Dashboard — Unified Widgets for All Services
+
+meta:
+ id: shieldai-unified-restructure-24
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-23]
+ tags: [frontend, dashboard, widgets, solidjs]
+
+objective:
+- Build rich, interactive dashboard widgets that give users a unified view of their security posture across all ShieldAI services. Replace placeholder stat cards with real, actionable widgets.
+
+deliverables:
+- `web/src/components/dashboard/ThreatScoreWidget.tsx` — Circular gauge showing overall threat score (0-100):
+ - Color-coded: green (0-30), amber (31-70), red (71-100)
+ - Trend indicator (up/down vs last week)
+ - Click to view detailed breakdown
+- `web/src/components/dashboard/AlertFeedWidget.tsx` — Real-time alert stream:
+ - List of latest 5-10 alerts with severity icons
+ - Grouped by correlation group
+ - "Mark as read" and "View details" actions
+ - "View all" link to alerts page
+- `web/src/components/dashboard/ExposureWidget.tsx` — DarkWatch summary:
+ - Count of exposures by severity
+ - Latest exposure with source and date
+ - "Run scan" button
+ - Link to full DarkWatch page
+- `web/src/components/dashboard/VoicePrintWidget.tsx` — VoicePrint summary:
+ - Enrollment count
+ - Recent analyses count
+ - Synthetic detection rate
+ - "Analyze audio" button
+- `web/src/components/dashboard/SpamShieldWidget.tsx` — SpamShield summary:
+ - Blocked calls/SMS count (today/this week)
+ - Top spam sources
+ - Custom rules count
+ - "View rules" link
+- `web/src/components/dashboard/HomeTitleWidget.tsx` — HomeTitle summary:
+ - Watched properties count
+ - Recent changes count
+ - Latest change with severity
+ - "View properties" link
+- `web/src/components/dashboard/RemoveBrokersWidget.tsx` — RemoveBrokers summary:
+ - Total brokers in registry
+ - Pending/completed removals count
+ - Progress bar for overall removal completion
+ - "Manage removals" link
+- `web/src/components/dashboard/QuickActionsWidget.tsx` — Shortcut buttons:
+ - "Add to watchlist", "Upload voice sample", "Check number", "Add property", "Start removal"
+- `web/src/routes/(webapp)/dashboard.tsx` — Updated dashboard layout:
+ - Grid layout: 2 columns on desktop, 1 on mobile
+ - Widgets arranged by priority (threat score top-left, quick actions top-right)
+ - Auto-refresh every 60 seconds
+
+steps:
+1. Create `web/src/components/dashboard/` directory.
+2. **ThreatScoreWidget**:
+ - Use SVG for circular gauge with stroke-dasharray animation
+ - Query `api.correlation.getStats` for score and trend
+ - Color transitions based on score value
+3. **AlertFeedWidget**:
+ - Query `api.correlation.getAlerts` with `limit: 10`
+ - Each item: severity icon (shield with color), title, time ago, source badge
+ - Use `Card` primitive with compact padding
+ - "Mark as read" calls `api.correlation.resolveAlert`
+4. **ExposureWidget**:
+ - Query `api.darkwatch.getExposures` with `limit: 1` for latest
+ - Show severity breakdown as small horizontal bars
+ - "Run scan" button triggers `api.darkwatch.runScan` mutation
+5. **VoicePrintWidget**:
+ - Query `api.voiceprint.getEnrollments` and `api.voiceprint.getAnalyses`
+ - Show counts and a mini bar chart of recent analyses
+6. **SpamShieldWidget**:
+ - Query `api.spamshield.getStats`
+ - Show blocked count with trend
+ - List top 3 spam sources as small rows
+7. **HomeTitleWidget**:
+ - Query `api.hometitle.getProperties` and `api.hometitle.getChanges`
+ - Show property count and latest change
+8. **RemoveBrokersWidget**:
+ - Query `api.removebrokers.getRemovalRequests` and `api.removebrokers.getBrokerRegistry`
+ - Calculate completion percentage
+ - Show progress bar using theme colors
+9. **QuickActionsWidget**:
+ - Grid of icon buttons linking to service pages or opening modals
+ - Each button uses `Button` primitive with icon
+10. Update dashboard route:
+ - Compose all widgets in responsive grid
+ - Add `createInterval` or `setInterval` for auto-refresh
+ - Handle loading states with skeleton cards
+
+steps:
+- Unit: Each widget renders correctly with mock data
+- Unit: ThreatScoreWidget displays correct color for score ranges
+- Unit: AlertFeedWidget calls mark-read mutation on click
+- Visual: Dashboard grid is responsive and widgets align correctly
+- E2E: Dashboard loads with real data and auto-refreshes
+
+acceptance_criteria:
+- [ ] Dashboard displays 8 widgets in a responsive grid layout
+- [ ] Threat score gauge animates on load and updates with real data
+- [ ] Alert feed shows latest alerts with correct severity colors
+- [ ] Each service widget summarizes the most important metrics
+- [ ] Quick actions provide one-click access to common tasks
+- [ ] Dashboard auto-refreshes every 60 seconds
+- [ ] Loading states show skeleton cards while data fetches
+- [ ] All widgets use theme tokens and shift correctly in dark mode
+
+validation:
+- Open `/dashboard` and verify all widgets render with real data
+- Resize browser to mobile width and verify single-column layout
+- Wait 60 seconds and verify auto-refresh updates data
+- Click "Mark as read" on an alert and verify it disappears from feed
+- Run `cd web && pnpm test` for widget unit tests
+
+notes:
+- Keep widgets focused on summary data. Deep interactions belong on service-specific pages.
+- The threat score gauge is the visual centerpiece. Invest in making it polished and informative.
+- Consider adding a "Security Tip of the Day" widget that rotates educational content.
+- Widgets should be collapsible or reorderable in a future enhancement. Design them as self-contained units.
+- Use `Suspense` boundaries around widget groups to prevent the entire dashboard from blocking on one slow API call.
diff --git a/tasks/shieldai-unified-restructure/25-realtime-alerts.md b/tasks/shieldai-unified-restructure/25-realtime-alerts.md
new file mode 100644
index 0000000..f3e3380
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/25-realtime-alerts.md
@@ -0,0 +1,104 @@
+# 25. Real-Time Alerts — WebSocket Push Notifications
+
+meta:
+ id: shieldai-unified-restructure-25
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-23]
+ tags: [backend, frontend, websocket, realtime, notifications]
+
+objective:
+- Implement real-time alert push from server to client using WebSockets. When a new alert is generated (exposure detected, synthetic voice found, spam blocked, property changed, broker listing found), the user should receive an immediate notification in the web app.
+
+deliverables:
+- `web/src/server/websocket.ts` — WebSocket server:
+ - Integrates with SolidStart's Nitro server or runs alongside it
+ - Authenticates connections using JWT from query param or cookie
+ - Maintains userId → socket mapping
+ - Broadcasts alerts to connected users
+- `web/src/lib/websocket.ts` — WebSocket client:
+ - Connects to `/ws/alerts` endpoint
+ - Reconnects with exponential backoff on disconnect
+ - Authenticates with JWT token
+ - Exposes `alerts` signal that emits incoming alert objects
+ - Heartbeat/ping-pong to keep connection alive
+- `web/src/hooks/useRealtimeAlerts.ts` — Hook:
+ - Uses websocket client
+ - Triggers toast notification on new alert
+ - Increments unread badge count in Navbar
+ - Plays subtle notification sound (optional, respects reduced motion)
+- `web/src/server/services/alert.publisher.ts` — Alert publisher:
+ - `publishAlert(userId, alert)` — sends alert to user's connected sockets
+ - Called from each service's alert pipeline (DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers)
+ - Falls back to push notification (task 14) if user is not connected
+
+steps:
+1. Install `ws` npm package in `web/` (server-side WebSocket library).
+2. Create `web/src/server/websocket.ts`:
+ - If using Nitro (SolidStart's server), create a custom Nitro plugin or API route that upgrades to WebSocket
+ - If standalone, create a separate WebSocket server on a different port (e.g., 3001)
+ - On connection:
+ - Parse `token` from query string or cookie
+ - Verify JWT, extract `userId`
+ - Store socket in `userSockets` Map
+ - On message: handle ping with pong, ignore other messages (server pushes only)
+ - On disconnect: remove socket from map
+ - `broadcastToUser(userId, data)`: find all sockets for user, send JSON
+3. Create `web/src/lib/websocket.ts`:
+ - `connect()` — create WebSocket, set up event handlers
+ - `disconnect()` — close connection
+ - `onMessage` callback or signal for incoming data
+ - Reconnect logic: 1s, 2s, 5s, 10s, 30s backoff, max 10 attempts
+ - Heartbeat: send ping every 30s, expect pong within 10s
+4. Create `web/src/hooks/useRealtimeAlerts.ts`:
+ - Call `connect()` on mount if user is authenticated
+ - On alert received: show toast via `useToast()`, increment unread count
+ - On disconnect: show "Reconnecting..." indicator
+ - Cleanup on unmount
+5. Create `web/src/server/services/alert.publisher.ts`:
+ - `publishAlert(userId, alert)`:
+ - Try WebSocket broadcast first
+ - If no active sockets, queue push notification (FCM/APNs) via task 14
+ - If push fails, queue email notification
+ - `publishToGroup(userIds, alert)` — for family group alerts
+6. Integrate with service alert pipelines:
+ - In DarkWatch alert pipeline (task 15): call `alertPublisher.publishAlert(userId, alert)`
+ - In VoicePrint analysis result handler (task 16): same
+ - In SpamShield classification (task 17): same
+ - In HomeTitle change detector (task 18): same
+ - In RemoveBrokers listing scanner (task 19): same
+7. Update Navbar:
+ - Add a pulsing dot indicator when WebSocket is connected
+ - Show "Offline" badge when disconnected
+8. Write integration tests for WebSocket flow.
+
+steps:
+- Unit: WebSocket server authenticates connections with valid JWT
+- Unit: WebSocket server rejects connections with invalid JWT
+- Unit: Client reconnects with exponential backoff on disconnect
+- Integration: Triggering an alert in DarkWatch service sends real-time notification to connected client
+- E2E: User receives toast notification within 2 seconds of alert generation
+
+acceptance_criteria:
+- [ ] WebSocket server authenticates connections and maps them to users
+- [ ] Alerts are broadcast to connected users within 1 second of generation
+- [ ] Client reconnects automatically after network interruption
+- [ ] Toast notifications appear for real-time alerts
+- [ ] Unread badge count increments immediately on new alert
+- [ ] If user is offline, alert falls back to push notification
+- [ ] Heartbeat keeps connections alive without timeout
+- [ ] WebSocket does not leak memory on disconnect/reconnect cycles
+
+validation:
+- Open dashboard in two browser tabs, trigger an alert in one, verify both receive notification
+- Disconnect network, reconnect, verify client re-establishes connection
+- Check server memory usage after 100 connect/disconnect cycles
+- Run `cd web && pnpm test` for WebSocket integration tests
+
+notes:
+- Reference legacy: `server/alerts/` (WebSocket alert server), `packages/api/src/routes/websocket.routes.ts`
+- Nitro (SolidStart's server) has experimental WebSocket support. If unstable, run a separate `ws` server on a different port and proxy through nginx in production.
+- For production scaling, consider using Redis Pub/Sub to broadcast alerts across multiple server instances.
+- The WebSocket connection should be lazy: only connect when user is on a protected route and authenticated.
+- Respect `prefers-reduced-motion` for notification sounds and aggressive toast animations.
+- Consider adding a "Do Not Disturb" mode where real-time toasts are suppressed but badge count still updates.
diff --git a/tasks/shieldai-unified-restructure/26-error-loading-states.md b/tasks/shieldai-unified-restructure/26-error-loading-states.md
new file mode 100644
index 0000000..ed3c846
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/26-error-loading-states.md
@@ -0,0 +1,111 @@
+# 26. Polish — Error Boundaries, Loading States, Skeletons, and Transitions
+
+meta:
+ id: shieldai-unified-restructure-26
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-24, shieldai-unified-restructure-25]
+ tags: [frontend, polish, ux, accessibility, solidjs]
+
+objective:
+- Add production polish to the web app: error boundaries for graceful failure handling, skeleton screens for loading states, smooth page transitions, and comprehensive accessibility improvements.
+
+deliverables:
+- `web/src/components/ui/ErrorBoundary.tsx` — Global error boundary:
+ - Catches runtime errors in child components
+ - Displays friendly error page with ShieldAI branding
+ - "Try again" button to reset error boundary
+ - "Report issue" button (placeholder for Sentry integration)
+ - Logs error details to console (and eventually monitoring)
+- `web/src/components/ui/Skeleton.tsx` — Skeleton loading components:
+ - `SkeletonText` — animated pulse lines for text placeholders
+ - `SkeletonCard` — card-shaped skeleton with header, body, footer
+ - `SkeletonAvatar` — circular skeleton for profile images
+ - `SkeletonTable` — table row skeletons
+ - All use `.bg-bg-tertiary` with shimmer animation
+- `web/src/components/ui/PageTransition.tsx` — Page transition wrapper:
+ - Fade-in + slight translate-y on route change
+ - Respects `prefers-reduced-motion`
+ - Uses SolidJS `` or CSS animations
+- Loading states integrated:
+ - Dashboard widgets show skeletons while `createResource` is loading
+ - Service pages show skeleton layouts while data fetches
+ - Buttons show spinner inside during mutation loading
+ - Forms show disabled state with spinner during submission
+- Accessibility improvements:
+ - All images have `alt` text
+ - All interactive elements have focus rings
+ - Color contrast meets WCAG AA (4.5:1 for text)
+ - ARIA labels on icon-only buttons
+ - Skip-to-content link for keyboard navigation
+ - Reduced motion support for all animations
+- `web/src/components/ui/EmptyState.tsx` — Empty state component:
+ - Icon, title, description, and optional action button
+ - Used when lists have no items (e.g., no watchlist items, no alerts)
+
+steps:
+1. Create `web/src/components/ui/ErrorBoundary.tsx`:
+ - Use SolidJS `ErrorBoundary` component as base
+ - Custom fallback UI with ShieldAI logo, error message, retry button
+ - Capture stack trace for debugging
+2. Create `web/src/components/ui/Skeleton.tsx`:
+ - Use Tailwind `animate-pulse` or custom shimmer animation
+ - Each skeleton variant accepts `lines`, `width`, `height` props
+ - Use rounded rectangles that mimic content shape
+3. Create `web/src/components/ui/PageTransition.tsx`:
+ - Wrap route content in transition group
+ - Apply `opacity-0 translate-y-2` → `opacity-100 translate-y-0` on enter
+ - Duration: 200ms, easing: ease-out
+4. Integrate skeletons:
+ - Dashboard: replace `Loading...` text with `SkeletonCard` grids
+ - Service pages: add skeleton layouts matching final content shape
+ - Tables: use `SkeletonTable` with 5 rows
+5. Integrate loading states in buttons:
+ - Update `Button` primitive to show spinner when `loading` prop is true
+ - Disable button and reduce opacity during loading
+6. Add accessibility:
+ - Audit all pages with axe DevTools
+ - Fix any contrast issues (adjust colors in theme if needed)
+ - Add `aria-label` to all icon buttons
+ - Add `aria-live="polite"` regions for toast notifications
+ - Ensure all form inputs have associated labels
+7. Create `EmptyState` component and use it across pages:
+ - DarkWatch: "No watchlist items yet" with "Add first item" button
+ - VoicePrint: "No voice enrollments" with "Enroll voice" button
+ - SpamShield: "No custom rules" with "Create rule" button
+ - etc.
+8. Test all improvements across light/dark modes.
+
+steps:
+- Unit: ErrorBoundary catches thrown errors and renders fallback
+- Unit: Skeleton components render with correct dimensions
+- Unit: PageTransition applies enter animation
+- Accessibility: axe DevTools audit passes with 0 critical violations
+- Visual: All loading states look polished and match content layout
+- Visual: Empty states display correctly when lists are empty
+
+acceptance_criteria:
+- [ ] ErrorBoundary catches and displays friendly error UI for all routes
+- [ ] Skeleton screens match the shape of final content (no layout shift)
+- [ ] All buttons show loading state during mutations
+- [ ] Page transitions are smooth and respect reduced motion
+- [ ] Empty states are informative and provide clear next actions
+- [ ] axe DevTools audit shows 0 critical or serious violations
+- [ ] All color combinations meet WCAG AA contrast standards
+- [ ] Keyboard navigation works for all interactive elements
+
+validation:
+- Throw an error in a component and verify ErrorBoundary catches it
+- Throttle network to 3G and verify skeletons appear during loading
+- Navigate between routes and verify smooth transitions
+- Run axe DevTools on each page and fix any issues
+- Test keyboard-only navigation (Tab, Enter, Escape) on all pages
+- Run `cd web && pnpm test` for polish-related unit tests
+
+notes:
+- Reference Lendair's skeleton components: `~/code/Lendair/web/src/components/skeletons/`
+- The `animate-pulse` Tailwind class is sufficient for skeletons. For a more polished look, consider a custom shimmer animation with a moving gradient.
+- Error boundaries should NOT catch errors in event handlers or async code — only render errors. Use try/catch for async operations.
+- For monitoring integration, consider adding Sentry in a follow-up task. For now, log errors to console.
+- The `EmptyState` component should be reusable across all service pages. Keep it generic but allow customization of icon, title, description, and action.
+- Test reduced motion by enabling "Reduce motion" in OS settings and verifying all animations are suppressed.
diff --git a/tasks/shieldai-unified-restructure/27-browser-extension-move.md b/tasks/shieldai-unified-restructure/27-browser-extension-move.md
new file mode 100644
index 0000000..7a63991
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/27-browser-extension-move.md
@@ -0,0 +1,125 @@
+# 27. Browser Extension — Move to browser-ext/ and Update API Client
+
+meta:
+ id: shieldai-unified-restructure-27
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-23]
+ tags: [extension, browser, api, migration]
+
+objective:
+- Move the browser extension from `packages/extension/` to `browser-ext/`, update its build configuration, and rewrite the API client to communicate with the unified monolith's tRPC endpoints instead of the legacy Fastify API.
+
+deliverables:
+- `browser-ext/` directory with all extension code:
+ - `src/background/index.ts` — Service worker (MV3)
+ - `src/content/index.ts` — Content script for phishing detection
+ - `src/popup/popup.html` + `popup.ts` — Extension popup UI
+ - `src/options/options.html` + `options.ts` — Options page
+ - `src/lib/api-client.ts` — Updated API client
+ - `src/lib/phishing-detector.ts` — Phishing detection logic (preserved)
+ - `src/lib/cache.ts` — Request caching (preserved)
+ - `src/lib/settings.ts` — Extension settings (preserved)
+ - `src/types/index.ts` — TypeScript types
+ - `public/manifest.json` — Manifest V3
+ - `public/icons/` — Extension icons
+ - `vite.config.ts` — Build config
+ - `package.json` — Dependencies
+- Updated `browser-ext/src/lib/api-client.ts`:
+ - Communicates with `https://api.shieldai.com/api/trpc` (or local dev URL)
+ - Uses tRPC HTTP batch link for efficient requests
+ - Authenticates with API key (`x-api-key` header) or JWT
+ - Endpoints:
+ - `spamshield.checkNumber` — check phone number reputation
+ - `spamshield.classifySMS` — classify SMS text
+ - `extension.getAuthStatus` — check if user is linked
+ - `extension.linkDevice` — link extension to user account
+ - Error handling with retry logic
+- Updated `browser-ext/package.json`:
+ - Dependencies: `@trpc/client`, `superjson` (for serialization)
+ - Build scripts for Chrome/Firefox
+- Updated manifest:
+ - Permissions: `activeTab`, `storage`, `declarativeNetRequest`, `notifications`
+ - Host permissions: `https://api.shieldai.com/*`, `https://*.shieldai.com/*`
+
+steps:
+1. Create `browser-ext/` directory at project root.
+2. Move all files from `packages/extension/` to `browser-ext/`:
+ - `git mv packages/extension/src browser-ext/src`
+ - `git mv packages/extension/public browser-ext/public`
+ - `git mv packages/extension/vite.config.ts browser-ext/`
+ - `git mv packages/extension/package.json browser-ext/`
+ - Copy tests: `cp -r packages/extension/tests browser-ext/`
+3. Update `browser-ext/package.json`:
+ - Change name to `@shieldai/browser-ext`
+ - Update dependencies: add `@trpc/client`, remove legacy API client deps
+ - Update build scripts
+4. Update `browser-ext/src/lib/api-client.ts`:
+ - Remove old Fastify REST client code
+ - Create tRPC proxy client:
+ ```ts
+ import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
+ import type { AppRouter } from '../../../web/src/server/api/root';
+
+ export const api = createTRPCProxyClient({
+ links: [httpBatchLink({ url: API_URL, headers: { 'x-api-key': API_KEY } })],
+ });
+ ```
+ - Note: Type import from web/ may need a shared types package or copy types. For now, use a generated type file.
+5. Update background script:
+ - On install: call `api.extension.linkDevice` with extension ID
+ - On SMS received (if API available): call `api.spamshield.classifySMS`
+ - On call received: call `api.spamshield.checkNumber`
+ - Cache results using existing `cache.ts`
+6. Update popup UI:
+ - Show auth status (linked/unlinked)
+ - Show recent spam detections
+ - Quick actions: "Check number", "Report spam"
+ - Use ShieldAI theme colors (import theme tokens or hardcode for now)
+7. Update content script:
+ - Preserve phishing detection logic
+ - Report detected phishing to `api.extension.reportPhishing`
+8. Update options page:
+ - Settings for API key, notification preferences
+ - Link/unlink account flow
+9. Update build config:
+ - `vite.config.ts` should output to `dist/` with correct manifest
+ - Add scripts for `build:chrome` and `build:firefox` if needed
+10. Test extension:
+ - Load unpacked extension in Chrome
+ - Verify popup renders
+ - Test API calls against local dev server
+
+steps:
+- Unit: API client creates correct tRPC requests
+- Unit: Background script caches API responses
+- Integration: Extension popup successfully calls `spamshield.checkNumber`
+- E2E: Install extension, link device, verify API key auth works
+
+acceptance_criteria:
+- [ ] Extension code lives in `browser-ext/` and builds successfully
+- [ ] API client uses tRPC instead of legacy REST endpoints
+- [ ] Background script handles install, SMS, and call events
+- [ ] Popup UI shows auth status and recent detections
+- [ ] Content script preserves phishing detection and reports to tRPC
+- [ ] Options page allows API key configuration and account linking
+- [ ] Extension works with both local dev and production API URLs
+- [ ] All existing tests pass after migration
+
+validation:
+- `cd browser-ext && pnpm build` completes without errors
+- Load `browser-ext/dist/` as unpacked extension in Chrome
+- Open popup and verify UI renders
+- Test API call by entering a phone number → verify tRPC request in Network tab
+- Run `cd browser-ext && pnpm test` for extension tests
+
+notes:
+- Reference legacy: `packages/extension/src/`
+- The extension cannot directly import from `web/` due to build isolation. Options for tRPC types:
+ 1. Generate a standalone type file from the web app's router and copy it to `browser-ext/src/types/trpc.ts`
+ 2. Create a shared `@shieldai/types` package (but we're unifying, so avoid new packages)
+ 3. Use `any` for the router type in extension (not recommended)
+ Recommended: Option 1 — generate types as part of web build.
+- The extension's popup UI is currently plain HTML/TS. Consider using a lightweight framework (e.g., SolidJS for popup) for consistency with the web app, but this is optional.
+- For Manifest V3, service workers have limited lifetime. Ensure API calls complete before worker sleeps.
+- The `declarativeNetRequest` API can block requests at the network level without a persistent background script. Consider using it for known phishing domains.
diff --git a/tasks/shieldai-unified-restructure/28-ios-app-foundation.md b/tasks/shieldai-unified-restructure/28-ios-app-foundation.md
new file mode 100644
index 0000000..cfef981
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/28-ios-app-foundation.md
@@ -0,0 +1,108 @@
+# 28. iOS App — SwiftUI Foundation, Navigation, and Shared Theme
+
+meta:
+ id: shieldai-unified-restructure-28
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-01, shieldai-unified-restructure-02]
+ tags: [ios, swiftui, foundation, navigation, mobile]
+
+objective:
+- Establish the iOS app foundation in `iOS/ShieldAI/`: a SwiftUI project with app entry point, navigation architecture, and a shared theme system that mirrors the web app's ShieldAI brand palette and auto-shifting dark mode.
+
+deliverables:
+- `iOS/ShieldAI/ShieldAIApp.swift` — App entry point:
+ - `@main` app struct with `WindowGroup`
+ - Initializes dependency container (if using DI)
+ - Sets up appearance (tint color, navigation bar style)
+- `iOS/ShieldAI/ContentView.swift` — Root view:
+ - `TabView` with 5 tabs: Dashboard, Services, Alerts, Settings, Account
+ - Or `NavigationStack` with sidebar for iPad
+ - Handles auth state: shows `AuthView` if unauthenticated, `MainView` if authenticated
+- `iOS/ShieldAI/Navigation/` — Navigation infrastructure:
+ - `AppRouter.swift` — Navigation path manager using `NavigationPath`
+ - `Route.swift` — Enum of all app routes with associated values
+ - `RouterViewModifier.swift` — View modifier for deep linking
+- `iOS/ShieldAI/Theme/` — Shared theme system:
+ - `ShieldAITheme.swift` — Theme protocol/struct with colors, fonts, spacing
+ - `Color+ShieldAI.swift` — SwiftUI Color extensions for all brand tokens:
+ - `brandPrimary`, `brandPrimaryLight`, `brandPrimaryDark`
+ - `brandAccent`, `brandAccentLight`, `brandAccentDark`
+ - `bgPrimary`, `bgSecondary`, `bgTertiary`
+ - `textPrimary`, `textSecondary`, `textTertiary`
+ - `border`, `success`, `warning`, `error`
+ - `Font+ShieldAI.swift` — Typography scale matching web app
+ - `ThemeManager.swift` — Observable object managing light/dark/system mode
+- `iOS/ShieldAI/Info.plist` and `Assets.xcassets/`:
+ - App icon set (reuse web app SVGs converted to PNG/AppIcon)
+ - Color assets for dynamic light/dark variants
+ - Launch screen storyboard or SwiftUI launch view
+- Project configuration:
+ - Xcode project or Swift Package Manager setup
+ - Minimum iOS version: 16.0 (SwiftUI 2.0+ features, NavigationStack)
+ - Target devices: iPhone + iPad
+
+steps:
+1. Verify `iOS/ShieldAI/` exists and has a valid Xcode project or Package.swift.
+2. Create `ShieldAIApp.swift`:
+ - Set global accent color to `brandPrimary`
+ - Configure navigation bar appearance
+ - Inject `ThemeManager` as environment object
+3. Create `ContentView.swift`:
+ - Use `@StateObject` for auth state
+ - If unauthenticated: present full-screen `AuthView` (task 30)
+ - If authenticated: show `TabView` with tabs
+4. Create navigation system:
+ - `Route` enum with cases: `.dashboard`, `.service(ShieldService)`, `.alertDetail(Alert)`, `.settings`, `.profile`, etc.
+ - `AppRouter` class with `NavigationPath` and `navigate(to:)`, `pop()`, `popToRoot()`
+ - Support deep linking via URL scheme (`shieldai://dashboard`, `shieldai://alert/123`)
+5. Create theme system:
+ - Define all colors as static properties on `Color` extension
+ - Use `UIColor` dynamic colors for light/dark variants:
+ ```swift
+ static let bgPrimary = Color(UIColor { traitCollection in
+ traitCollection.userInterfaceStyle == .dark ? UIColor(hex: "#111827") : UIColor(hex: "#fafbfc")
+ })
+ ```
+ - Define font scale: `.caption`, `.body`, `.headline`, `.title`, `.largeTitle`
+ - Define spacing scale: `.xs` (4), `.sm` (8), `.md` (16), `.lg` (24), `.xl` (32), `.xxl` (48)
+6. Create `ThemeManager.swift`:
+ - `@Published var colorScheme: ColorScheme?` (nil = system)
+ - Methods: `setLight()`, `setDark()`, `setSystem()`
+ - Persist preference in `UserDefaults`
+ - Apply to root view using `.preferredColorScheme()`
+7. Set up assets:
+ - Convert ShieldAI logo SVG to PDF or PNG for Xcode assets
+ - Create color assets for all theme tokens with "Any, Dark" variants
+ - Add AppIcon set for all required sizes
+8. Build and run on iOS Simulator to verify app launches with correct theme.
+
+steps:
+- Unit: ThemeManager correctly toggles between light/dark/system
+- Unit: Color extensions return correct values for light and dark traits
+- Unit: AppRouter pushes and pops routes correctly
+- Visual: App launches with ShieldAI branding and correct tab structure
+- Visual: Theme shifts correctly when device dark mode toggles
+
+acceptance_criteria:
+- [ ] iOS app launches without crashes on iPhone and iPad simulators
+- [ ] Tab navigation has 5 tabs with correct icons and labels
+- [ ] Theme colors match web app palette in both light and dark modes
+- [ ] Theme manager persists user preference across app restarts
+- [ ] Navigation stack works for pushing and popping views
+- [ ] Deep linking supports `shieldai://` URL scheme
+- [ ] App icon and launch screen display correctly
+
+validation:
+- Build and run on iPhone 15 Pro simulator (iOS 17+)
+- Toggle device dark mode and verify all colors shift
+- Test navigation by tapping through all tabs and sub-pages
+- Verify deep link: `xcrun simctl openurl booted shieldai://dashboard`
+- Run unit tests via Xcode Cmd+U
+
+notes:
+- The iOS app should feel like a native sibling to the web app, not a port. Use native iOS patterns (tab bar, navigation bar, sheets, context menus).
+- SwiftUI's `NavigationStack` (iOS 16+) replaces the older `NavigationView`. Use it for proper path-based navigation.
+- For iPad, consider a sidebar (`NavigationSplitView`) instead of tab bar for better space utilization.
+- The theme system should be the single source of truth for all visual values. No hardcoded colors anywhere in the app.
+- Use SF Symbols for icons where possible. For custom icons (ShieldAI logo), use vector PDFs in assets.
diff --git a/tasks/shieldai-unified-restructure/29-ios-design-system.md b/tasks/shieldai-unified-restructure/29-ios-design-system.md
new file mode 100644
index 0000000..6cc3b24
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/29-ios-design-system.md
@@ -0,0 +1,135 @@
+# 29. iOS App — Design System Components Matching Web Theme
+
+meta:
+ id: shieldai-unified-restructure-29
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-28]
+ tags: [ios, swiftui, design-system, components, mobile]
+
+objective:
+- Build a comprehensive set of reusable SwiftUI components that mirror the web app's UI primitives (Button, Card, Input, Badge, Modal, Toast) using the iOS theme system from task 28. These components should feel native to iOS while maintaining visual consistency with the web app.
+
+deliverables:
+- `iOS/ShieldAI/Components/ShieldButton.swift` — Button component:
+ - Styles: primary (filled), secondary (outlined), ghost (text only), danger
+ - Sizes: small, medium, large
+ - States: enabled, disabled, loading (shows `ProgressView`)
+ - Supports icons (leading/trailing)
+- `iOS/ShieldAI/Components/ShieldCard.swift` — Card container:
+ - Gradient background matching web `.gradient-card`
+ - Border with corner radius
+ - Optional header and footer content
+ - Tap gesture support
+- `iOS/ShieldAI/Components/ShieldTextField.swift` — Text input:
+ - Label, placeholder, validation state
+ - Secure text entry toggle for passwords
+ - Error message display
+ - Focus state styling
+- `iOS/ShieldAI/Components/ShieldBadge.swift` — Status badge:
+ - Variants: default, success, warning, error, info
+ - Small rounded pill shape
+ - Icon + text or text only
+- `iOS/ShieldAI/Components/ShieldModal.swift` — Modal/sheet presentation:
+ - `.sheet` wrapper with consistent styling
+ - Title, content, and action buttons
+ - Dismiss gesture support
+- `iOS/ShieldAI/Components/ShieldToast.swift` — Toast notification:
+ - Slide-in from top or bottom
+ - Auto-dismiss after 3-4 seconds
+ - Variants: success, error, warning, info
+ - Tap to dismiss
+ - Uses `overlay` modifier on root view
+- `iOS/ShieldAI/Components/ShieldAvatar.swift` — User avatar:
+ - Displays image or initials fallback
+ - Sizes: small, medium, large
+ - Online/away status indicator dot
+- `iOS/ShieldAI/Components/ShieldProgressBar.swift` — Progress indicator:
+ - Linear progress bar with percentage label
+ - Color variants
+- `iOS/ShieldAI/Components/ShieldEmptyState.swift` — Empty state view:
+ - Icon, title, description, action button
+- `iOS/ShieldAI/Components/ShieldSkeleton.swift` — Skeleton loading:
+ - Shimmer animation using `LinearGradient` mask
+ - Text line and rectangle shapes
+
+steps:
+1. Create `iOS/ShieldAI/Components/` directory.
+2. **ShieldButton**:
+ - Use `Button` with custom label
+ - Primary: `brandPrimary` background, white text
+ - Secondary: transparent with `brandPrimary` border and text
+ - Ghost: transparent, `brandPrimary` text
+ - Danger: red gradient background
+ - Loading: replace label with `ProgressView` + reduce opacity
+ - Use `.frame(maxWidth: .infinity)` for full-width buttons
+3. **ShieldCard**:
+ - `ZStack` with gradient background shape
+ - `.overlay` for border
+ - `VStack` for content with padding
+ - Corner radius: 16pt (matching web `rounded-2xl`)
+4. **ShieldTextField**:
+ - `VStack` with label + `TextField`/`SecureField` + error text
+ - Custom background using `bgSecondary` with border
+ - Focus ring using `.overlay` with `brandPrimary` stroke on focus
+ - Eye icon button for password visibility toggle
+5. **ShieldBadge**:
+ - `Text` inside `Capsule()` background
+ - Colors from semantic tokens
+ - Small padding and font size
+6. **ShieldModal**:
+ - View extension `.shieldModal(isPresented:content:)`
+ - Wraps `.sheet` with consistent header and button bar
+ - Uses `ShieldCard` styling for sheet background
+7. **ShieldToast**:
+ - Create `ToastManager` as `@Observable` class (iOS 17+) or `ObservableObject`
+ - `showToast(message, variant, duration)` method
+ - Root view applies `.overlay` with `ToastContainer`
+ - `ToastContainer` animates toast in/out with `withAnimation`
+8. **ShieldAvatar**:
+ - `AsyncImage` or `Image` with circular clip
+ - Fallback: `Text` with initials on `brandPrimary` circle
+ - Status dot overlay
+9. **ShieldProgressBar**:
+ - `GeometryReader` with filled `Rectangle`
+ - Percentage label overlay
+10. **ShieldEmptyState**:
+ - `VStack` with icon (`Image(systemName:)`), title, description, optional `ShieldButton`
+11. **ShieldSkeleton**:
+ - `Rectangle` or `RoundedRectangle` with shimmer animation
+ - Use `LinearGradient` masked with `mask` modifier
+ - Animate gradient position with `withAnimation(.linear(duration: 1.5).repeatForever())`
+12. Create a preview/test view showing all components in light and dark mode.
+
+steps:
+- Unit: Each component renders correctly in previews
+- Unit: ShieldButton action fires on tap
+- Unit: ShieldTextField validation shows error state
+- Unit: ShieldToast auto-dismisses after specified duration
+- Visual: All components match web app appearance in both color schemes
+- Visual: Components adapt to Dynamic Type (accessibility font sizes)
+
+acceptance_criteria:
+- [ ] All 10 component types exist in `iOS/ShieldAI/Components/`
+- [ ] Each component supports light and dark modes
+- [ ] Button supports all 4 styles, 3 sizes, and loading state
+- [ ] TextField supports validation, secure entry, and focus styling
+- [ ] Toast system can queue and auto-dismiss multiple toasts
+- [ ] Card uses gradient background matching web theme
+- [ ] Skeleton has smooth shimmer animation
+- [ ] All components use theme tokens (no hardcoded colors)
+- [ ] Components adapt to accessibility font sizes
+
+validation:
+- Open preview canvas for each component and verify appearance
+- Run app on simulator and navigate to component test screen
+- Toggle dark mode and verify all components shift colors
+- Enable large text in Accessibility settings and verify layouts don't break
+- Run unit tests via Xcode Cmd+U
+
+notes:
+- SwiftUI previews are invaluable for component development. Create a `ComponentsPreview.swift` that shows all variants side by side.
+- Use `@ViewBuilder` closures for content slots in `ShieldCard`, `ShieldModal`, etc. to allow flexible content.
+- For the toast system, consider using the new `@Observable` macro (iOS 17+) instead of `ObservableObject` for better performance.
+- The shimmer animation can be implemented using a `LinearGradient` that moves across the view. Reference: https://swiftuirecipes.com/blog/swiftui-shimmer-effect
+- Keep components generic — they should not import service-specific types or business logic.
diff --git a/tasks/shieldai-unified-restructure/30-ios-auth-onboarding.md b/tasks/shieldai-unified-restructure/30-ios-auth-onboarding.md
new file mode 100644
index 0000000..ade6cdd
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/30-ios-auth-onboarding.md
@@ -0,0 +1,125 @@
+# 30. iOS App — Authentication, Onboarding, and Account Setup
+
+meta:
+ id: shieldai-unified-restructure-30
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-29]
+ tags: [ios, swiftui, auth, onboarding, mobile]
+
+objective:
+- Build the authentication and onboarding flow for the iOS app: login, signup, password reset, and a multi-step onboarding experience. Use native iOS authentication where possible (Sign in with Apple) and maintain visual consistency with the web app.
+
+deliverables:
+- `iOS/ShieldAI/Views/Auth/AuthView.swift` — Auth container:
+ - Switches between login and signup modes
+ - ShieldAI branding at top
+ - Social auth buttons (Sign in with Apple, Google)
+- `iOS/ShieldAI/Views/Auth/LoginView.swift` — Login screen:
+ - Email and password fields using `ShieldTextField`
+ - "Remember me" toggle
+ - "Forgot password?" link
+ - "Sign In" `ShieldButton`
+ - "Don't have an account? Sign up" link
+- `iOS/ShieldAI/Views/Auth/SignupView.swift` — Signup screen:
+ - Name, email, password, confirm password fields
+ - Password strength indicator
+ - Terms of service agreement toggle
+ - "Create Account" button
+- `iOS/ShieldAI/Views/Auth/ForgotPasswordView.swift` — Password reset:
+ - Email input + submit
+ - Success state with instructions
+- `iOS/ShieldAI/Views/Onboarding/OnboardingView.swift` — Onboarding flow:
+ - Page-based `TabView` with 4 steps
+ - Step 1: Welcome + plan selection (Basic, Plus, Premium cards)
+ - Step 2: Add first watchlist item (email or phone)
+ - Step 3: Invite family members (optional)
+ - Step 4: Setup complete → transition to main app
+- `iOS/ShieldAI/Views/Auth/BiometricAuthView.swift` — Face ID / Touch ID prompt:
+ - Triggered after first successful login
+ - Uses `LocalAuthentication` framework
+ - Stores preference in Keychain
+- `iOS/ShieldAI/Services/AuthService.swift` — Auth business logic:
+ - `login(email, password)` → calls API (task 31)
+ - `signup(name, email, password)` → calls API
+ - `resetPassword(email)` → calls API
+ - `signInWithApple()` → handles Apple ID credential
+ - `signInWithGoogle()` → handles Google Sign-In
+ - `enableBiometricAuth()` → stores credential in Keychain
+ - `logout()` → clears tokens and Keychain
+
+steps:
+1. Create `iOS/ShieldAI/Views/Auth/` and `iOS/ShieldAI/Views/Onboarding/` directories.
+2. **AuthView**:
+ - Use `@State` to toggle between login and signup
+ - Show ShieldAI logo and tagline at top
+ - Use `ShieldCard` for the form container
+3. **LoginView**:
+ - `ShieldTextField` for email and password
+ - `Toggle` for "Remember me"
+ - `ShieldButton` for submit
+ - Link to ForgotPasswordView
+ - On success: store JWT in Keychain, navigate to main app or onboarding
+4. **SignupView**:
+ - Additional fields: name, confirm password
+ - Password strength: check length, uppercase, number, special char
+ - Visual strength bar using `ShieldProgressBar`
+ - Terms toggle (required)
+ - On success: store JWT, show onboarding
+5. **ForgotPasswordView**:
+ - Simple email field + submit
+ - Success message: "Check your email for reset instructions"
+6. **OnboardingView**:
+ - `TabView` with `.tabViewStyle(.page)` for swipeable steps
+ - Step 1: Plan cards with feature lists, highlight recommended tier
+ - Step 2: Input for email/phone, "Add" button, shows added items list
+ - Step 3: Email inputs for family invites, "Skip" button
+ - Step 4: Success animation (checkmark), "Get Started" button
+ - Progress dots at bottom showing current step
+7. **BiometricAuthView**:
+ - `LAContext` for Face ID / Touch ID evaluation
+ - On success: store "useBiometric" preference and credential in Keychain
+ - On failure: fall back to password
+8. **AuthService**:
+ - Protocol-based for testability
+ - Implementation calls API client (task 31)
+ - Keychain wrapper for secure token storage
+ - Apple Sign-In: use `AuthenticationServices` framework (`ASAuthorizationAppleIDButton`)
+ - Google Sign-In: use `GoogleSignIn` SDK
+9. Wire auth state to root view:
+ - `ContentView` observes `AuthService.isAuthenticated`
+ - On logout: clear state and show AuthView
+
+steps:
+- Unit: AuthService stores and retrieves tokens from Keychain
+- Unit: Password strength calculator returns correct level
+- Unit: Onboarding stepper advances and collects data
+- Integration: Sign in with Apple button triggers ASAuthorizationController
+- E2E: Complete login → onboarding → main app flow
+
+acceptance_criteria:
+- [ ] Login screen accepts email/password and authenticates via API
+- [ ] Signup screen validates inputs and creates account via API
+- [ ] Password reset flow sends request to API and shows success state
+- [ ] Onboarding has 4 steps with progress indicator and swipe navigation
+- [ ] Sign in with Apple button works and authenticates user
+- [ ] Biometric auth (Face ID/Touch ID) can be enabled after first login
+- [ ] Tokens are stored securely in iOS Keychain
+- [ ] Logout clears all auth state and returns to login screen
+- [ ] Auth flow matches web app visual style
+
+validation:
+- Run app on simulator, complete login with test credentials
+- Verify JWT stored in Keychain (use Keychain utility or debug print)
+- Test Sign in with Apple flow (simulator supports mock Apple ID)
+- Complete onboarding and verify data sent to API
+- Test Face ID prompt on device (simulator can simulate with Features menu)
+- Run unit tests via Xcode Cmd+U
+
+notes:
+- Sign in with Apple is required for App Store approval if you offer other third-party sign-in options.
+- Use `ASAuthorizationController` for Apple Sign-In. Handle both `.signIn` and `.credentialRevokedNotification`.
+- For Google Sign-In, the `GoogleSignIn` SDK requires a `GoogleService-Info.plist`. Add setup instructions to README.
+- Keychain token storage should use `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` for security.
+- The onboarding data (plan, watchlist items, family invites) should be collected in a local model and submitted to the API at the final step (or incrementally).
+- Consider using SwiftUI's `.fullScreenCover` for the auth flow so it covers the entire screen without tab bar.
diff --git a/tasks/shieldai-unified-restructure/31-ios-api-client.md b/tasks/shieldai-unified-restructure/31-ios-api-client.md
new file mode 100644
index 0000000..dc9faff
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/31-ios-api-client.md
@@ -0,0 +1,128 @@
+# 31. iOS App — API Client, tRPC Bridge, and Offline Support
+
+meta:
+ id: shieldai-unified-restructure-31
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-29, shieldai-unified-restructure-30]
+ tags: [ios, swift, api, networking, offline, mobile]
+
+objective:
+- Build the API client layer for the iOS app that communicates with the unified monolith's tRPC endpoints. Since tRPC is TypeScript-native, we'll use a thin HTTP bridge approach: the iOS client calls tRPC procedures as typed HTTP JSON requests. Add offline support with local caching and request queuing.
+
+deliverables:
+- `iOS/ShieldAI/Services/APIClient.swift` — HTTP client:
+ - Wraps `URLSession` with async/await (`async throws` methods)
+ - Base URL from environment/config (`https://api.shieldai.com` or local)
+ - Automatic auth header injection (JWT from Keychain)
+ - Request/response logging in debug builds
+ - Retry logic with exponential backoff for network errors
+ - Timeout configuration
+- `iOS/ShieldAI/Services/TRPCBridge.swift` — tRPC procedure caller:
+ - `callProcedure(path: String, input: Encodable?) async throws -> T`
+ - Serializes input to JSON, sends POST to `/api/trpc/{path}`
+ - Deserializes response JSON to Swift `Decodable` type
+ - Handles tRPC error format (`{ error: { message, code } }`)
+ - Type-safe wrappers for common procedures:
+ - `user.me() -> User`
+ - `billing.getSubscription() -> Subscription`
+ - `darkwatch.getWatchlist() -> [WatchlistItem]`
+ - etc.
+- `iOS/ShieldAI/Models/` — Swift data models:
+ - `User.swift`, `Subscription.swift`, `WatchlistItem.swift`, `Exposure.swift`, `Alert.swift`, `VoiceEnrollment.swift`, `VoiceAnalysis.swift`, `SpamRule.swift`, `PropertyWatchlistItem.swift`, `RemovalRequest.swift`, `BrokerListing.swift`, `NormalizedAlert.swift`, `CorrelationGroup.swift`, `SecurityReport.swift`
+ - All models conform to `Codable`, `Identifiable`, `Equatable`
+ - Enum types for Swift (e.g., `SubscriptionTier`, `AlertSeverity`, `ExposureSource`)
+- `iOS/ShieldAI/Services/CacheManager.swift` — Offline cache:
+ - `CacheEntry` with data, timestamp, TTL
+ - Stores in `UserDefaults` (small data) or file system (large data)
+ - `getCached(key: String) -> T?`
+ - `setCached(key: String, value: T, ttl: TimeInterval)`
+ - Automatic cache invalidation on TTL expiry
+- `iOS/ShieldAI/Services/OfflineQueue.swift` — Request queue:
+ - Queues mutations when offline
+ - Retries queued requests when connectivity restored
+ - Persists queue to disk
+ - `addToQueue(request: QueuedRequest)`
+ - `processQueue()` — called when network becomes available
+- `iOS/ShieldAI/Services/NetworkMonitor.swift` — Connectivity monitoring:
+ - Uses `NWPathMonitor` to track network state
+ - Publishes `isConnected` boolean
+ - Triggers queue processing when connection restored
+
+steps:
+1. Create `iOS/ShieldAI/Services/` and `iOS/ShieldAI/Models/` directories.
+2. **APIClient**:
+ - Create `APIClient` class as `@Observable` or singleton
+ - `request(endpoint: String, method: String, body: Data?) async throws -> T`
+ - Add request interceptor for auth header
+ - Add response interceptor for error parsing
+ - Use `JSONEncoder`/`JSONDecoder` with custom date formatting
+3. **TRPCBridge**:
+ - `callProcedure` constructs tRPC batch request format:
+ ```json
+ { "0": { "json": { "email": "test@example.com" } } }
+ ```
+ - Parse tRPC response format:
+ ```json
+ { "0": { "result": { "data": { ... } } } }
+ ```
+ - Map tRPC error codes to Swift `APIError` enum
+ - Create convenience methods for each router procedure
+4. **Models**:
+ - Define Swift structs matching Drizzle schema shapes
+ - Use `CodingKeys` where JSON keys differ from Swift naming
+ - Define enums for all database enums (e.g., `SubscriptionTier: String, Codable`)
+ - Add computed properties where needed (e.g., `Alert.isCritical: Bool`)
+5. **CacheManager**:
+ - Use `JSONEncoder` to serialize values
+ - Store in `UserDefaults` with key prefix `shieldai.cache.`
+ - TTL check: compare `Date()` to stored timestamp
+ - Clear all cache on logout
+6. **OfflineQueue**:
+ - `QueuedRequest` struct: `endpoint`, `method`, `body`, `timestamp`, `retryCount`
+ - Store array in `UserDefaults` or JSON file
+ - `processQueue`: iterate requests, call APIClient, remove on success, increment retry on failure
+ - Max retries: 3, then mark as failed and notify user
+7. **NetworkMonitor**:
+ - `NWPathMonitor` with `.queue = .main`
+ - `@Published var isConnected: Bool`
+ - On change to connected: trigger `OfflineQueue.processQueue()`
+8. Create `APIConfig.swift`:
+ - `baseURL`, `timeout`, `maxRetries` from environment or plist
+ - Different values for debug/release builds
+9. Test all components with mocked URLSession.
+
+steps:
+- Unit: APIClient injects auth header correctly
+- Unit: TRPCBridge parses tRPC response format correctly
+- Unit: CacheManager stores and retrieves values with TTL
+- Unit: OfflineQueue persists requests and processes them in order
+- Unit: NetworkMonitor publishes correct connectivity state
+- Integration: APIClient successfully calls `user.me` against local dev server
+
+acceptance_criteria:
+- [ ] APIClient makes authenticated HTTP requests to tRPC endpoints
+- [ ] TRPCBridge correctly serializes tRPC batch input and deserializes responses
+- [ ] All common API procedures have type-safe Swift wrappers
+- [ ] Network errors trigger retry with exponential backoff
+- [ ] CacheManager stores GET responses and returns cached data when offline
+- [ ] OfflineQueue persists mutations and retries when connectivity restored
+- [ ] NetworkMonitor accurately tracks connectivity state
+- [ ] All models are Codable and match backend schema shapes
+- [ ] API configuration supports different environments (dev, staging, prod)
+
+validation:
+- Point APIClient to local dev server (`http://localhost:3000`)
+- Call `user.me()` and verify response parsed into `User` model
+- Disconnect network, attempt a mutation, verify it queues
+- Reconnect network, verify queued mutation executes automatically
+- Verify cache hit by calling same endpoint twice with network disabled
+- Run unit tests via Xcode Cmd+U
+
+notes:
+- Since tRPC is TypeScript-native, the iOS client cannot use tRPC's type-safe client directly. The HTTP bridge is the pragmatic approach.
+- Consider generating Swift models and API wrappers automatically from the tRPC router types using a code generation tool (e.g., custom script parsing tRPC router exports). For now, manual definitions are fine.
+- The tRPC batch link sends multiple procedures in one HTTP request. For simplicity, the iOS client can use single-procedure requests (`/api/trpc/user.me`) instead of batching.
+- Use `OSLog` for structured logging in debug builds. Avoid printing sensitive data (tokens, passwords).
+- For large responses (e.g., full alert history), consider pagination and storing pages in cache separately.
+- The offline queue should only persist safe mutations (idempotent or retry-safe). Avoid queuing destructive operations without user confirmation.
diff --git a/tasks/shieldai-unified-restructure/32-ios-service-screens.md b/tasks/shieldai-unified-restructure/32-ios-service-screens.md
new file mode 100644
index 0000000..76fc44d
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/32-ios-service-screens.md
@@ -0,0 +1,136 @@
+# 32. iOS App — Dashboard and Service Screens (DarkWatch, VoicePrint, SpamShield, etc.)
+
+meta:
+ id: shieldai-unified-restructure-32
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-29, shieldai-unified-restructure-30, shieldai-unified-restructure-31]
+ tags: [ios, swiftui, dashboard, services, mobile]
+
+objective:
+- Build the main dashboard and all service-specific screens for the iOS app. These should mirror the web app's functionality while using native iOS UI patterns (lists, forms, sheets, charts).
+
+deliverables:
+- `iOS/ShieldAI/Views/Dashboard/DashboardView.swift` — Main dashboard:
+ - Scrollable vertical stack of widgets
+ - Threat score gauge (circular progress ring)
+ - Recent alerts list (top 5)
+ - Service summary cards (DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers)
+ - Quick action buttons
+ - Pull-to-refresh
+- `iOS/ShieldAI/Views/Dashboard/AlertDetailView.swift` — Alert detail:
+ - Full alert information
+ - Correlated alerts section
+ - "Mark as resolved" and "False positive" actions
+- `iOS/ShieldAI/Views/Services/DarkWatchView.swift` — DarkWatch screen:
+ - Watchlist items list with add/remove
+ - Exposures list with severity badges
+ - Exposure detail with source info and recommendations
+ - "Run scan" button
+- `iOS/ShieldAI/Views/Services/VoicePrintView.swift` — VoicePrint screen:
+ - Voice enrollments list with play/delete
+ - "Enroll voice" button → opens recording sheet
+ - Analysis history list
+ - Analysis detail with confidence and verdict
+- `iOS/ShieldAI/Views/Services/SpamShieldView.swift` — SpamShield screen:
+ - Blocked calls/SMS statistics
+ - Custom rules list with add/edit/delete
+ - Number check input with result
+ - "Report spam" button
+- `iOS/ShieldAI/Views/Services/HomeTitleView.swift` — HomeTitle screen:
+ - Watched properties list with add/remove
+ - Property detail with snapshots and changes
+ - Map view showing property location (MapKit)
+ - "Run scan" button
+- `iOS/ShieldAI/Views/Services/RemoveBrokersView.swift` — RemoveBrokers screen:
+ - Broker registry list with search/filter
+ - Removal requests list with status
+ - Request detail with progress timeline
+ - "Start removal" button
+- `iOS/ShieldAI/Views/Settings/SettingsView.swift` — Settings:
+ - Account info, subscription details
+ - Notification preferences toggle
+ - Theme selection (light/dark/system)
+ - Biometric auth toggle
+ - Family group management
+ - Logout button
+
+steps:
+1. Create view directories: `Dashboard/`, `Services/`, `Settings/`.
+2. **DashboardView**:
+ - `ScrollView` with `VStack`
+ - Threat score: `Canvas` or custom `View` with circular progress ring
+ - Alerts: `ForEach` over `alerts` array, each as `ShieldCard` row
+ - Service cards: horizontal scroll or grid of compact cards
+ - Quick actions: `HStack` of icon buttons
+ - `.refreshable` modifier for pull-to-refresh
+3. **AlertDetailView**:
+ - `ScrollView` with sections
+ - Severity badge at top
+ - Description and metadata
+ - Correlated alerts in a nested list
+ - Action buttons at bottom
+4. **DarkWatchView**:
+ - `List` with sections: Watchlist, Exposures
+ - Watchlist items: swipe-to-delete, tap to edit
+ - Add button → sheet with form
+ - Exposures: tap to open detail
+5. **VoicePrintView**:
+ - Enrollments section with audio playback (AVFoundation)
+ - "Enroll" button → `RecordingView` sheet with waveform visualization
+ - Analysis list with verdict color coding
+6. **SpamShieldView**:
+ - Stats cards at top
+ - Rules list with toggle for active/inactive
+ - Number check: `ShieldTextField` + check button → result card
+7. **HomeTitleView**:
+ - Properties list with address and status
+ - Add property: address search with geocoding
+ - Property detail: `Map` view, snapshot history, changes list
+8. **RemoveBrokersView**:
+ - Broker list with category filters
+ - Request list with status badges and progress bars
+ - Start removal: sheet with broker selection and personal info form
+9. **SettingsView**:
+ - `Form` with sections: Account, Preferences, Family, Danger Zone
+ - Use native `Toggle`, `Picker`, `NavigationLink`
+ - Subscription info with upgrade button
+10. Wire all views to API client (task 31):
+ - Use `@StateObject` view models that call TRPCBridge
+ - Show `ShieldSkeleton` while loading
+ - Show `ShieldEmptyState` when lists are empty
+ - Handle errors with `ShieldToast` or inline messages
+
+steps:
+- Unit: Each view model fetches data correctly from mocked API
+- Unit: DashboardView displays correct widget count
+- Visual: All screens use theme tokens and adapt to dark mode
+- E2E: Navigate through all service screens and verify data loads
+- E2E: Perform CRUD operations (add watchlist item, delete enrollment, create rule)
+
+acceptance_criteria:
+- [ ] Dashboard displays threat score, alerts, service summaries, and quick actions
+- [ ] All 5 service screens (DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers) load and display data
+- [ ] Each service screen supports core CRUD operations
+- [ ] Alert detail shows full information and correlation group
+- [ ] Settings screen allows managing account, preferences, and family
+- [ ] Pull-to-refresh updates dashboard data
+- [ ] All screens show loading skeletons and empty states appropriately
+- [ ] Navigation between screens works smoothly with native iOS transitions
+
+validation:
+- Launch app, login, and verify dashboard renders with real data
+- Tap each service tab and verify screen loads
+- Add a watchlist item in DarkWatch and verify it appears in list
+- Delete a voice enrollment and verify it disappears
+- Create a spam rule and verify it applies
+- Toggle settings and verify preferences persist
+- Run unit tests via Xcode Cmd+U
+
+notes:
+- Use native iOS patterns: `List` for scrollable data, `Form` for settings, `Sheet` for modals, `.contextMenu` for actions.
+- For the threat score gauge, consider using a SwiftUI charting library like `SwiftUI Charts` (iOS 16+) or a custom `Canvas` drawing.
+- The map in HomeTitle should use `Map` (iOS 17+) or `MKMapView` wrapped in `UIViewRepresentable` for older versions.
+- Voice recording requires microphone permission. Add `NSMicrophoneUsageDescription` to Info.plist.
+- Keep view models separate from views for testability. View models should own the `@Published` state and API calls.
+- Consider using `Task` and `.task` modifier for async data loading instead of `onAppear` to handle cancellation correctly.
diff --git a/tasks/shieldai-unified-restructure/33-ios-native-features.md b/tasks/shieldai-unified-restructure/33-ios-native-features.md
new file mode 100644
index 0000000..ab53515
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/33-ios-native-features.md
@@ -0,0 +1,114 @@
+# 33. iOS App — Push Notifications, Biometrics, Voice Enrollment, Camera
+
+meta:
+ id: shieldai-unified-restructure-33
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-29, shieldai-unified-restructure-30, shieldai-unified-restructure-31, shieldai-unified-restructure-32]
+ tags: [ios, swiftui, native-features, push, biometrics, camera, mobile]
+
+objective:
+- Implement native iOS features that differentiate the mobile experience: push notifications via APNs, biometric authentication, voice enrollment with audio recording, and camera integration for document scanning or QR codes.
+
+deliverables:
+- `iOS/ShieldAI/Services/PushNotificationService.swift` — Push notification handling:
+ - Registers for remote notifications with APNs
+ - Handles device token registration with backend (task 14)
+ - Processes incoming notifications in foreground and background
+ - Deep links to relevant screen based on notification payload
+ - Rich notification content (images, actions) via Notification Service Extension
+- `iOS/ShieldAI/Services/BiometricAuthService.swift` — Biometric authentication:
+ - Evaluates Face ID / Touch ID availability
+ - Prompts for biometric authentication
+ - Stores credential in Keychain for biometric-protected access
+ - Fallback to device passcode
+- `iOS/ShieldAI/Views/VoicePrint/RecordingView.swift` — Voice recording UI:
+ - Real-time waveform visualization using `AVAudioRecorder` + `CAShapeLayer`
+ - Record / stop / playback controls
+ - Duration timer
+ - Quality check (minimum duration, signal level)
+ - Submit enrollment to API
+- `iOS/ShieldAI/Views/Common/DocumentScannerView.swift` — Document scanning:
+ - Uses `VisionKit` `VNDocumentCameraViewController` (iOS 13+)
+ - Captures ID documents or property deeds
+ - OCR text extraction using `VNRecognizeTextRequest`
+ - Review and retake functionality
+- `iOS/ShieldAI/Services/CameraService.swift` — Camera access wrapper:
+ - Checks and requests camera/microphone permissions
+ - Handles permission denied states with guidance to Settings
+- `iOS/ShieldAI/ShieldAI.entitlements` — App entitlements:
+ - Push notifications (`aps-environment`)
+ - Sign in with Apple (`com.apple.developer.applesignin`)
+ - Keychain sharing (if needed)
+
+steps:
+1. **Push Notifications**:
+ - Add `UIApplicationDelegate` methods or `UNUserNotificationCenterDelegate`
+ - `registerForRemoteNotifications()` in `ShieldAIApp.swift` on launch
+ - `didRegisterForRemoteNotificationsWithDeviceToken` → send token to backend via `api.notification.registerDevice`
+ - `didReceiveRemoteNotification` → parse payload, show local notification if in foreground
+ - Handle notification tap: extract `screen` and `id` from payload, navigate via `AppRouter`
+ - Create Notification Service Extension target for rich notifications (image attachments)
+2. **Biometric Auth**:
+ - `LAContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics)`
+ - `evaluatePolicy` with localized reason: "Authenticate to access ShieldAI"
+ - On success: unlock Keychain item containing refresh token
+ - On failure: show password fallback
+ - Add toggle in Settings to enable/disable biometric auth
+3. **Voice Recording**:
+ - Request microphone permission (`AVAudioSession.requestRecordPermission`)
+ - Configure `AVAudioSession` for recording
+ - `AVAudioRecorder` with `.wav` format, 16kHz, mono
+ - Real-time waveform: read `averagePower(forChannel:)` in a timer, update `Path` in `Canvas`
+ - Minimum duration: 5 seconds
+ - On stop: playback with `AVAudioPlayer`, then submit to `api.voiceprint.createEnrollment`
+4. **Document Scanner**:
+ - `VNDocumentCameraViewController` for capture
+ - `VNRecognizeTextRequest` for OCR on captured image
+ - Display recognized text for user review
+ - Use for: ID verification, property deed upload, broker opt-out form scanning
+5. **Camera Service**:
+ - Centralized permission manager for camera and microphone
+ - `AVCaptureDevice.authorizationStatus(for: .video/audio)`
+ - Show explanatory UI before requesting permission
+ - Handle `.denied` and `.restricted` states
+6. Update `Info.plist`:
+ - `NSMicrophoneUsageDescription`: "ShieldAI needs microphone access to enroll your voice for clone detection."
+ - `NSCameraUsageDescription`: "ShieldAI uses the camera to scan documents and verify your identity."
+ - `NSFaceIDUsageDescription`: "Use Face ID to securely access your ShieldAI account."
+ - `UIBackgroundModes`: `remote-notification`
+7. Test on physical device (simulator cannot test push notifications or biometrics accurately).
+
+steps:
+- Unit: BiometricAuthService returns correct availability state
+- Unit: CameraService returns correct permission status
+- Integration: Push token registration sends correct data to backend
+- E2E: Receive test push notification and verify deep link navigation
+- E2E: Record voice sample and submit enrollment successfully
+- E2E: Scan document and verify OCR text extraction
+
+acceptance_criteria:
+- [ ] App registers for push notifications and sends device token to backend
+- [ ] Incoming push notifications display correctly in foreground and background
+- [ ] Tapping a notification deep links to the correct screen
+- [ ] Face ID / Touch ID authentication works for app unlock
+- [ ] Voice recording captures audio, shows waveform, and submits enrollment
+- [ ] Document scanner captures images and extracts text via OCR
+- [ ] All permission requests include explanatory descriptions
+- [ ] Denied permissions show helpful guidance to Settings app
+- [ ] Native features work on both iPhone and iPad
+
+validation:
+- Test push notifications using APNs test tool or Firebase Console
+- Verify biometric auth on device with Face ID/Touch ID
+- Record a 10-second voice sample and verify enrollment created in backend
+- Scan a printed document and verify OCR text matches
+- Run unit tests via Xcode Cmd+U
+
+notes:
+- Push notifications require an Apple Developer account and APNs certificate/key. For development, use the sandbox environment.
+- The Notification Service Extension runs in a separate process and has limited memory (~24MB). Keep image processing lightweight.
+- Voice recording quality matters for ML model accuracy. Use 16kHz mono WAV — this matches the web app's preprocessing pipeline.
+- Document scanning with VisionKit is iOS 13+. For older versions, fall back to `UIImagePickerController`.
+- Biometric auth should be optional. Users can always use password login.
+- Consider adding a "Quick Actions" 3D Touch / Haptic Touch menu for common actions ("Run DarkWatch scan", "Check number").
diff --git a/tasks/shieldai-unified-restructure/34-android-app-foundation.md b/tasks/shieldai-unified-restructure/34-android-app-foundation.md
new file mode 100644
index 0000000..9487567
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/34-android-app-foundation.md
@@ -0,0 +1,139 @@
+# 34. Android App — Jetpack Compose Foundation, Navigation, and Shared Theme
+
+meta:
+ id: shieldai-unified-restructure-34
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-01, shieldai-unified-restructure-02]
+ tags: [android, jetpack-compose, foundation, navigation, mobile]
+
+objective:
+- Establish the Android app foundation in `android/`: a Jetpack Compose project with app entry point, navigation architecture, and a shared theme system that mirrors the web app's ShieldAI brand palette with Material3 and dynamic dark mode support.
+
+deliverables:
+- `android/` directory with complete Android project:
+ - `app/build.gradle.kts` — App module configuration
+ - `app/src/main/AndroidManifest.xml` — App manifest with permissions
+ - `app/src/main/java/com/shieldai/android/MainActivity.kt` — Entry point with `setContent`
+ - `app/src/main/java/com/shieldai/android/ShieldAIApp.kt` — Application class
+- `android/app/src/main/java/com/shieldai/android/ui/theme/` — Theme system:
+ - `Color.kt` — All brand color tokens as `Color` constants:
+ - `BrandPrimary`, `BrandPrimaryLight`, `BrandPrimaryDark`
+ - `BrandAccent`, `BrandAccentLight`, `BrandAccentDark`
+ - `BgPrimary`, `BgSecondary`, `BgTertiary` (light and dark variants)
+ - `TextPrimary`, `TextSecondary`, `TextTertiary`
+ - `Success`, `Warning`, `Error`, `Info`
+ - `Type.kt` — Typography scale using Material3 `Typography`
+ - `Shape.kt` — Corner radius shapes (small, medium, large)
+ - `Theme.kt` — `ShieldAITheme` composable:
+ - `lightColorScheme()` and `darkColorScheme()` using Material3
+ - `dynamicColor` support for Android 12+ (optional)
+ - Manual theme override state
+- `android/app/src/main/java/com/shieldai/android/navigation/` — Navigation:
+ - `AppNavigation.kt` — Navigation host with all routes
+ - `Screen.kt` — Sealed class of all destinations with arguments
+ - `BottomNavBar.kt` — Bottom navigation with 5 items: Dashboard, Services, Alerts, Settings, Account
+ - `NavGraph.kt` — Compose Navigation graph definition
+- `android/app/src/main/res/` — Resources:
+ - `mipmap-xxxhdpi/` — App icons for all densities
+ - `values/themes.xml` — Base theme definitions
+ - `drawable/` — Vector icons and logos
+- Project configuration:
+ - `build.gradle.kts` (project level) with Kotlin 2.0, Compose BOM
+ - `settings.gradle.kts` including app module
+ - Min SDK: 26 (Android 8.0), Target SDK: 35, Compile SDK: 35
+ - Kotlin version: 2.0.0
+ - Compose BOM: 2024.05.00 or latest stable
+
+steps:
+1. Create `android/` directory with standard Android project structure.
+2. Set up Gradle files:
+ - Root `build.gradle.kts`: plugins block with `com.android.application`, `org.jetbrains.kotlin.android`
+ - `gradle/libs.versions.toml` for version catalog (recommended)
+ - App `build.gradle.kts`:
+ ```kotlin
+ android {
+ compileSdk = 35
+ defaultConfig {
+ minSdk = 26
+ targetSdk = 35
+ applicationId = "com.shieldai.android"
+ }
+ buildFeatures { compose = true }
+ composeOptions { kotlinCompilerExtensionVersion = "1.5.14" }
+ }
+ dependencies {
+ implementation(platform("androidx.compose:compose-bom:2024.05.00"))
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.compose.material3:material3")
+ implementation("androidx.navigation:navigation-compose:2.7.7")
+ // ... other deps
+ }
+ ```
+3. Create theme system:
+ - `Color.kt`: Define all colors as `val BrandPrimary = Color(0xFF4F46E5)` etc.
+ - `Type.kt`: Create `Typography` with Inter font (download and add to `res/font/`)
+ - `Shape.kt`: `Shapes` with `small = RoundedCornerShape(8.dp)`, `medium = 12.dp`, `large = 16.dp`
+ - `Theme.kt`:
+ ```kotlin
+ @Composable
+ fun ShieldAITheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ dynamicColor: Boolean = false, // disable for brand consistency
+ content: @Composable () -> Unit
+ ) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= 31 -> {
+ if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
+ else dynamicLightColorScheme(LocalContext.current)
+ }
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ MaterialTheme(colorScheme = colorScheme, typography = Typography, shapes = Shapes, content = content)
+ }
+ ```
+4. Create navigation:
+ - `Screen` sealed class: `Dashboard`, `Services`, `Alerts`, `Settings`, `Account`, `Login`, `Signup`, `Onboarding`, `ServiceDetail(val service: String)`
+ - `AppNavigation.kt`: `NavHost` with `rememberNavController()`
+ - `BottomNavBar.kt`: `NavigationBar` with 5 items, each with icon and label
+ - Handle auth state: if unauthenticated, show Login as start destination
+5. Create `MainActivity.kt`:
+ - `setContent { ShieldAITheme { AppNavigation() } }`
+ - Handle window insets for edge-to-edge display
+6. Create `ShieldAIApp.kt`:
+ - Application class for initialization (dependency injection setup, if used)
+7. Add app icons:
+ - Convert ShieldAI logo SVG to vector drawable (`res/drawable/ic_logo.xml`)
+ - Generate mipmap icons using Android Studio's Image Asset Studio
+8. Build and run on Android Emulator to verify app launches.
+
+steps:
+- Unit: Theme switches correctly between light and dark modes
+- Unit: Navigation graph contains all expected destinations
+- Visual: App launches with correct bottom navigation and branding
+- Visual: Theme colors match web app palette in both modes
+
+acceptance_criteria:
+- [ ] Android app builds and launches without crashes
+- [ ] Bottom navigation has 5 tabs with correct icons and labels
+- [ ] Theme colors match web app palette in both light and dark modes
+- [ ] Navigation between screens works with native Android transitions
+- [ ] App icon displays correctly on launcher
+- [ ] Theme supports manual override (light/dark/system)
+- [ ] Project uses modern Android stack: Kotlin 2.0, Compose, Material3
+
+validation:
+- Build app in Android Studio and run on Pixel 7 emulator (API 34)
+- Toggle device dark mode and verify all colors shift
+- Navigate through all bottom tabs and verify transitions
+- Verify app icon on emulator home screen
+- Run `./gradlew test` for unit tests
+
+notes:
+- The Android app should feel native to Android, not like an iOS port. Use Material3 components, bottom sheets, floating action buttons, and Android-specific patterns.
+- Dynamic colors (Material You) are disabled by default to maintain brand consistency. Can be enabled as a user preference later.
+- Use Jetpack Compose Navigation with type-safe navigation arguments (Kotlin DSL or Navigation Compose 2.8+ typed navigation).
+- Consider using Hilt for dependency injection, but keep it simple for the initial setup.
+- The `android/` directory is not part of the pnpm workspace. It is a standalone Gradle project.
+- For font loading, add Inter font files to `res/font/` and reference them in `Type.kt`.
diff --git a/tasks/shieldai-unified-restructure/35-android-design-system.md b/tasks/shieldai-unified-restructure/35-android-design-system.md
new file mode 100644
index 0000000..750c913
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/35-android-design-system.md
@@ -0,0 +1,138 @@
+# 35. Android App — Design System Components Matching Web Theme
+
+meta:
+ id: shieldai-unified-restructure-35
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-34]
+ tags: [android, jetpack-compose, design-system, components, mobile]
+
+objective:
+- Build a comprehensive set of reusable Jetpack Compose components that mirror the web app's UI primitives (Button, Card, Input, Badge, Modal, Toast) using the Android theme system from task 34. These should feel native to Android while maintaining visual consistency.
+
+deliverables:
+- `android/app/src/main/java/com/shieldai/android/ui/components/` — Component library:
+ - `ShieldButton.kt` — Button component:
+ - Variants: primary (filled), secondary (outlined), ghost (text), danger
+ - Sizes: small, medium, large
+ - States: enabled, disabled, loading (shows `CircularProgressIndicator`)
+ - Icon support (leading/trailing)
+ - `ShieldCard.kt` — Card container:
+ - Gradient background matching web `.gradient-card`
+ - Border with corner radius
+ - Optional header and footer content slots
+ - Click handling
+ - `ShieldTextField.kt` — Text input:
+ - Label, placeholder, error state, helper text
+ - Password visibility toggle
+ - Focus state styling
+ - Validation support
+ - `ShieldBadge.kt` — Status badge:
+ - Variants: default, success, warning, error, info
+ - Small rounded pill shape
+ - Icon + text or text only
+ - `ShieldModal.kt` — Modal bottom sheet / dialog:
+ - `ModalBottomSheet` for mobile-appropriate presentation
+ - Title, content, and action buttons
+ - Swipe-to-dismiss
+ - `ShieldToast.kt` — Toast / Snackbar:
+ - Slide-up from bottom
+ - Auto-dismiss after 3-4 seconds
+ - Variants: success, error, warning, info
+ - Action button support (e.g., "Undo")
+ - `ShieldAvatar.kt` — User avatar:
+ - Async image loading with Coil
+ - Initials fallback
+ - Sizes: small, medium, large
+ - Online status indicator
+ - `ShieldProgressBar.kt` — Progress indicator:
+ - Linear progress bar with percentage
+ - Color variants
+ - `ShieldEmptyState.kt` — Empty state:
+ - Icon, title, description, action button
+ - `ShieldSkeleton.kt` — Skeleton loading:
+ - Shimmer animation using `Brush.linearGradient` with infinite animation
+ - Text line and rectangle shapes
+
+steps:
+1. Create `android/app/src/main/java/com/shieldai/android/ui/components/` directory.
+2. **ShieldButton**:
+ - `@Composable fun ShieldButton(...)`
+ - Primary: `Button` with `containerColor = BrandPrimary`
+ - Secondary: `OutlinedButton` with `borderColor = BrandPrimary`
+ - Ghost: `TextButton` with `contentColor = BrandPrimary`
+ - Danger: `Button` with red gradient
+ - Loading: wrap content in `Box` with `CircularProgressIndicator` overlay
+ - Full-width: `modifier = Modifier.fillMaxWidth()`
+3. **ShieldCard**:
+ - `Card` with custom `colors` using gradient brush background
+ - `border = BorderStroke(1.dp, BorderColor)`
+ - `shape = MaterialTheme.shapes.large`
+ - Header/footer as optional `@Composable` slots
+4. **ShieldTextField**:
+ - `OutlinedTextField` or `TextField` with custom colors
+ - `VisualTransformation` for password toggle
+ - Error state: red border and supporting text
+ - `KeyboardOptions` for appropriate keyboard type per input
+5. **ShieldBadge**:
+ - `Surface` with `shape = RoundedCornerShape(50%)`
+ - Small padding, small text
+ - Background color from semantic tokens
+6. **ShieldModal**:
+ - `ModalBottomSheet` from Material3
+ - Or `AlertDialog` for confirmation dialogs
+ - `SheetState` management
+7. **ShieldToast**:
+ - `SnackbarHost` with custom `Snackbar` composable
+ - `Scaffold` provides `SnackbarHostState`
+ - Show via `scope.launch { snackbarHostState.showSnackbar(...) }`
+ - Custom colors per variant
+8. **ShieldAvatar**:
+ - `AsyncImage` from Coil library
+ - Fallback: `Box` with initials text on brand color circle
+ - Status dot: small `Canvas` circle overlay
+9. **ShieldProgressBar**:
+ - `LinearProgressIndicator` with custom `trackColor` and `progressColor`
+ - Percentage text overlay
+10. **ShieldEmptyState**:
+ - `Column` with icon (`Image` or `Icon`), title, description, optional `ShieldButton`
+ - Centered alignment
+11. **ShieldSkeleton**:
+ - `Brush.linearGradient` with animated offset using `rememberInfiniteTransition`
+ - Apply as background to `Box` shapes
+ - Reference: https://developer.android.com/jetpack/compose/animation/shimmer
+12. Create a preview composable showing all components in light and dark mode.
+
+steps:
+- Unit: Each component renders correctly in Compose previews
+- Unit: ShieldButton click handler fires correctly
+- Unit: ShieldTextField validation shows error state
+- Unit: ShieldToast auto-dismisses after specified duration
+- Visual: All components match web app appearance in both color schemes
+- Visual: Components adapt to different font sizes (accessibility)
+
+acceptance_criteria:
+- [ ] All 10 component types exist in `ui/components/`
+- [ ] Each component supports light and dark modes
+- [ ] Button supports all 4 styles, 3 sizes, and loading state
+- [ ] TextField supports validation, secure entry, and focus styling
+- [ ] Toast/Snackbar system can queue and auto-dismiss multiple messages
+- [ ] Card uses gradient background matching web theme
+- [ ] Skeleton has smooth shimmer animation
+- [ ] All components use theme tokens (no hardcoded colors)
+- [ ] Components adapt to accessibility font sizes
+
+validation:
+- Open Compose preview for each component and verify appearance
+- Run app on emulator and navigate to component test screen
+- Toggle dark mode and verify all components shift colors
+- Enable large text in Accessibility settings and verify layouts don't break
+- Run `./gradlew test` for unit tests
+
+notes:
+- Compose previews are essential for component development. Use `@Preview` with both light and dark themes.
+- Use `CompositionLocalProvider` for theme overrides if needed (e.g., a section that needs inverted colors).
+- The shimmer animation in Compose uses `rememberInfiniteTransition` and `Brush.linearGradient`. This is the standard Android approach.
+- For image loading, Coil (`io.coil-kt:coil-compose`) is the standard. Add it to dependencies.
+- Keep components stateless where possible. State should be hoisted to parent composables or ViewModels.
+- Material3 `ModalBottomSheet` requires `androidx.compose.material3:material3` version 1.2.0+. Ensure BOM includes it.
diff --git a/tasks/shieldai-unified-restructure/36-android-auth-onboarding.md b/tasks/shieldai-unified-restructure/36-android-auth-onboarding.md
new file mode 100644
index 0000000..0e0ad1a
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/36-android-auth-onboarding.md
@@ -0,0 +1,122 @@
+# 36. Android App — Authentication, Onboarding, and Account Setup
+
+meta:
+ id: shieldai-unified-restructure-36
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-34, shieldai-unified-restructure-35]
+ tags: [android, jetpack-compose, auth, onboarding, mobile]
+
+objective:
+- Build the authentication and onboarding flow for the Android app: login, signup, password reset, and a multi-step onboarding experience. Use native Android authentication where possible (Google Sign-In, Credential Manager) and maintain visual consistency with the web app.
+
+deliverables:
+- `android/app/src/main/java/com/shieldai/android/ui/screens/auth/` — Auth screens:
+ - `AuthScreen.kt` — Auth container with login/signup toggle
+ - `LoginScreen.kt` — Email + password login form
+ - `SignupScreen.kt` — Account creation form
+ - `ForgotPasswordScreen.kt` — Password reset request
+ - `ResetPasswordScreen.kt` — Password reset confirmation
+- `android/app/src/main/java/com/shieldai/android/ui/screens/onboarding/` — Onboarding:
+ - `OnboardingScreen.kt` — Horizontal pager with 4 steps
+ - `PlanSelectionStep.kt` — Tier cards (Basic, Plus, Premium)
+ - `WatchlistSetupStep.kt` — Add first email/phone
+ - `FamilyInviteStep.kt` — Invite family members
+ - `CompleteStep.kt` — Success animation and "Get Started"
+- `android/app/src/main/java/com/shieldai/android/ui/screens/auth/BiometricAuthScreen.kt` — Biometric prompt:
+ - Uses `BiometricPrompt` from `androidx.biometric:biometric`
+ - Face/fingerprint authentication
+ - Fallback to device credential
+- `android/app/src/main/java/com/shieldai/android/viewmodel/AuthViewModel.kt` — Auth logic:
+ - `login(email, password)` → calls API (task 37)
+ - `signup(name, email, password)` → calls API
+ - `resetPassword(email)` → calls API
+ - `signInWithGoogle()` → Google Sign-In
+ - `enableBiometricAuth()` → stores credential in EncryptedSharedPreferences
+ - `logout()` → clears tokens and state
+- `android/app/src/main/java/com/shieldai/android/data/repository/AuthRepository.kt` — Data layer:
+ - Token storage in `EncryptedSharedPreferences`
+ - API calls via repository pattern
+
+steps:
+1. Create auth and onboarding screen directories.
+2. **AuthScreen**:
+ - `Column` with ShieldAI logo and tagline
+ - `TabRow` or custom toggle for Login/Signup
+ - `ShieldCard` containing the form
+3. **LoginScreen**:
+ - `ShieldTextField` for email and password
+ - "Remember me" `Switch`
+ - "Forgot password?" `TextButton`
+ - "Sign In" `ShieldButton`
+ - Google Sign-In button (Material `Button` with Google icon)
+ - On success: store JWT in `EncryptedSharedPreferences`, navigate to main app
+4. **SignupScreen**:
+ - Additional fields: name, confirm password
+ - Password strength indicator using `ShieldProgressBar`
+ - Terms `Checkbox` (required)
+ - "Create Account" `ShieldButton`
+5. **ForgotPasswordScreen**:
+ - Email `ShieldTextField` + submit
+ - Success state with instructions
+6. **OnboardingScreen**:
+ - `HorizontalPager` from `androidx.compose.foundation:pager`
+ - 4 pages with `PageIndicator` dots at bottom
+ - Step 1: Plan cards with feature comparison
+ - Step 2: Input for email/phone with "Add" button
+ - Step 3: Email inputs for family invites with "Skip" button
+ - Step 4: Success animation using `Lottie` or Compose animation
+ - "Get Started" button navigates to Dashboard
+7. **BiometricAuthScreen**:
+ - `BiometricPrompt` setup with `BiometricManager`
+ - `setDeviceCredentialAllowed(true)` for fallback
+ - On success: store "useBiometric" flag and credential in `EncryptedSharedPreferences`
+ - Show prompt on app launch if enabled
+8. **AuthViewModel**:
+ - `ViewModel` with `StateFlow` for UI state (`Loading`, `Success`, `Error`)
+ - `login()`, `signup()`, etc. methods
+ - `signInWithGoogle()` using `com.google.android.gms:play-services-auth`
+ - Token management: save to `EncryptedSharedPreferences`, include in API calls
+9. **AuthRepository**:
+ - Interface + implementation
+ - `login(email, password)` returns `Result`
+ - `saveToken(token)`, `getToken()`, `clearToken()`
+10. Wire auth state to navigation:
+ - `MainActivity` observes auth state
+ - Unauthenticated → show LoginScreen
+ - Authenticated but new → show OnboardingScreen
+ - Authenticated → show Dashboard
+
+steps:
+- Unit: AuthViewModel emits correct states for login/signup
+- Unit: Password strength calculator returns correct level
+- Unit: Onboarding pager advances and collects data
+- Integration: Google Sign-In button triggers auth flow
+- E2E: Complete login → onboarding → main app flow
+
+acceptance_criteria:
+- [ ] Login screen accepts email/password and authenticates via API
+- [ ] Signup screen validates inputs and creates account via API
+- [ ] Password reset flow sends request to API and shows success state
+- [ ] Onboarding has 4 steps with pager indicator and swipe navigation
+- [ ] Google Sign-In button works and authenticates user
+- [ ] Biometric auth (face/fingerprint) can be enabled after first login
+- [ ] Tokens are stored securely in EncryptedSharedPreferences
+- [ ] Logout clears all auth state and returns to login screen
+- [ ] Auth flow matches web app visual style
+
+validation:
+- Run app on emulator, complete login with test credentials
+- Verify JWT stored in EncryptedSharedPreferences (use Android Studio Database Inspector or log)
+- Test Google Sign-In flow (emulator supports mock Google account)
+- Complete onboarding and verify data sent to API
+- Test biometric prompt on device (emulator can simulate with Extended Controls)
+- Run `./gradlew test` for unit tests
+
+notes:
+- Credential Manager (`androidx.credentials:credentials`) is the modern replacement for Google Sign-In. Use it if targeting API 34+; otherwise, use `play-services-auth`.
+- `EncryptedSharedPreferences` from `androidx.security:security-crypto` is the standard for secure token storage.
+- The onboarding data should be collected in a ViewModel state and submitted to the API at the final step.
+- For the success animation in onboarding, consider using Lottie (`com.airbnb.android:lottie-compose`) with a checkmark animation.
+- The `BiometricPrompt` should be shown as a system dialog, not a custom screen. The `BiometricAuthScreen.kt` is just a wrapper/helper.
+- Handle configuration changes (rotation) correctly: ViewModel survives, but UI state should be restored.
diff --git a/tasks/shieldai-unified-restructure/37-android-api-client.md b/tasks/shieldai-unified-restructure/37-android-api-client.md
new file mode 100644
index 0000000..964bd00
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/37-android-api-client.md
@@ -0,0 +1,113 @@
+# 37. Android App — API Client, tRPC Bridge, and Offline Support
+
+meta:
+ id: shieldai-unified-restructure-37
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-34, shieldai-unified-restructure-35, shieldai-unified-restructure-36]
+ tags: [android, kotlin, api, networking, offline, mobile]
+
+objective:
+- Build the API client layer for the Android app that communicates with the unified monolith's tRPC endpoints. Use a thin HTTP bridge approach with Retrofit or Ktor Client, plus offline support with Room caching and WorkManager request queuing.
+
+deliverables:
+- `android/app/src/main/java/com/shieldai/android/data/remote/` — Remote data layer:
+ - `TRPCApiService.kt` — Retrofit interface or Ktor client defining all tRPC endpoints:
+ - `@POST("api/trpc/user.me")`, `@POST("api/trpc/darkwatch.getWatchlist")`, etc.
+ - Request/response wrappers for tRPC batch format
+ - `TRPCResponse.kt` / `TRPCError.kt` — Data classes for tRPC response/error parsing
+ - `AuthInterceptor.kt` — OkHttp interceptor injecting JWT from EncryptedSharedPreferences
+ - `ErrorHandler.kt` — Centralized error handling with retry logic
+- `android/app/src/main/java/com/shieldai/android/data/local/` — Local data layer:
+ - `ShieldAIDatabase.kt` — Room database with entities:
+ - `UserEntity`, `SubscriptionEntity`, `WatchlistItemEntity`, `ExposureEntity`, `AlertEntity`, `VoiceEnrollmentEntity`, `VoiceAnalysisEntity`, `SpamRuleEntity`, `PropertyEntity`, `RemovalRequestEntity`, `BrokerListingEntity`
+ - `DAO` interfaces for each entity with CRUD operations
+ - `CacheManager.kt` — TTL-based cache logic using Room
+- `android/app/src/main/java/com/shieldai/android/data/repository/` — Repository layer:
+ - `UserRepository.kt`, `DarkWatchRepository.kt`, `VoicePrintRepository.kt`, etc.
+ - Each repository: remote fetch → cache to Room → return flow/coroutine
+ - `RepositoryModule.kt` — Hilt module for DI
+- `android/app/src/main/java/com/shieldai/android/data/model/` — Data models:
+ - `User.kt`, `Subscription.kt`, `WatchlistItem.kt`, `Exposure.kt`, `Alert.kt`, etc.
+ - All models as Kotlin data classes with `Parcelable` where needed
+ - Enum classes matching backend enums
+- `android/app/src/main/java/com/shieldai/android/data/sync/` — Offline support:
+ - `OfflineWorker.kt` — WorkManager worker for retrying queued requests
+ - `PendingRequestDao.kt` — Room table for queued mutations
+ - `SyncManager.kt` — Manages queue, triggers WorkManager on connectivity restore
+- `android/app/src/main/java/com/shieldai/android/di/` — Dependency injection:
+ - `NetworkModule.kt` — Retrofit/OkHttp setup
+ - `DatabaseModule.kt` — Room database provider
+ - `RepositoryModule.kt` — Repository bindings
+
+steps:
+1. Add dependencies to `build.gradle.kts`:
+ - Retrofit: `com.squareup.retrofit2:retrofit`, `converter-gson` or `converter-kotlinx-serialization`
+ - OkHttp: `logging-interceptor`
+ - Room: `androidx.room:room-runtime`, `room-ktx`, `room-compiler`
+ - WorkManager: `androidx.work:work-runtime-ktx`
+ - Hilt: `com.google.dagger:hilt-android`, `hilt-compiler`
+ - Kotlinx Serialization: `org.jetbrains.kotlinx:kotlinx-serialization-json`
+2. Create `TRPCApiService.kt`:
+ - Define POST endpoints for each tRPC procedure
+ - Request body: `{"0": {"json": { ... }}}` (tRPC batch format for single call)
+ - Response: `TRPCResponse` with `result.data` field
+ - Use Kotlinx Serialization for JSON parsing
+3. Create data models:
+ - Kotlin data classes with `@Serializable` annotation
+ - Enum classes with `@Serializable` and custom serializers if needed
+ - Date parsing using `kotlinx.datetime` or `java.time.Instant`
+4. Create Room database:
+ - `ShieldAIDatabase` abstract class extending `RoomDatabase`
+ - Entity classes with `@Entity`, `@PrimaryKey`, `@ColumnInfo`
+ - DAO interfaces with `@Dao`, `@Query`, `@Insert`, `@Update`, `@Delete`
+ - Type converters for complex types (enums, dates, JSON)
+5. Create repositories:
+ - Each repository has `remoteDataSource` (API service) and `localDataSource` (DAO)
+ - `getData()`: check cache first, if stale or missing → fetch remote → save to cache → return
+ - Return `Flow` or `suspend` functions with `Result`
+6. Create offline support:
+ - `PendingRequest` entity: `endpoint`, `method`, `body`, `timestamp`, `retryCount`
+ - `SyncManager`: add request to pending queue when offline
+ - `OfflineWorker`: periodic WorkManager task that processes pending requests
+ - Network monitor using `ConnectivityManager` to trigger immediate sync
+7. Create DI modules:
+ - `NetworkModule`: provide `Retrofit`, `OkHttpClient` with auth interceptor and logging
+ - `DatabaseModule`: provide `ShieldAIDatabase` singleton
+ - `RepositoryModule`: bind repository interfaces to implementations
+8. Test all layers with mocked dependencies.
+
+steps:
+- Unit: Retrofit service creates correct HTTP requests
+- Unit: Room DAO inserts and retrieves entities correctly
+- Unit: Repository returns cached data when offline
+- Unit: SyncManager queues requests when network unavailable
+- Integration: API client successfully calls `user.me` against local dev server
+
+acceptance_criteria:
+- [ ] Retrofit/Ktor client makes authenticated HTTP requests to tRPC endpoints
+- [ ] tRPC response format is correctly parsed into Kotlin data classes
+- [ ] Room database caches API responses with TTL-based invalidation
+- [ ] Offline mutations are queued and retried when connectivity restored
+- [ ] All common API procedures have type-safe Kotlin wrappers
+- [ ] Network errors trigger retry with exponential backoff
+- [ ] Repository layer provides clean abstraction over remote and local data
+- [ ] Hilt dependency injection is configured for all layers
+- [ ] API configuration supports different environments (dev, staging, prod)
+
+validation:
+- Point API client to local dev server (`http://10.0.2.2:3000` for emulator)
+- Call `user.me()` and verify response parsed into `User` model
+- Disconnect network, attempt a mutation, verify it queues in Room
+- Reconnect network, verify WorkManager processes queued request
+- Verify cache hit by calling same endpoint twice with network disabled
+- Run `./gradlew test` for unit tests
+
+notes:
+- Retrofit with Kotlinx Serialization is the recommended stack. Gson works too but Kotlinx Serialization has better null safety.
+- For tRPC, the Android client cannot use tRPC's type-safe client directly. The HTTP bridge is the pragmatic approach.
+- The tRPC batch link sends multiple procedures in one HTTP request. For simplicity, use single-procedure requests.
+- Room database schema should mirror the Drizzle schema closely, but can be simplified (fewer columns) for caching purposes.
+- WorkManager requires `androidx.work:work-runtime-ktx`. It handles background execution reliably across Android versions.
+- For network monitoring, `ConnectivityManager.registerDefaultNetworkCallback` is the modern approach (API 24+).
+- Use `Result` or sealed classes (`Success`, `Error`, `Loading`) for API state management in ViewModels.
diff --git a/tasks/shieldai-unified-restructure/38-android-service-screens.md b/tasks/shieldai-unified-restructure/38-android-service-screens.md
new file mode 100644
index 0000000..4a862d9
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/38-android-service-screens.md
@@ -0,0 +1,125 @@
+# 38. Android App — Dashboard and Service Screens
+
+meta:
+ id: shieldai-unified-restructure-38
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-34, shieldai-unified-restructure-35, shieldai-unified-restructure-36, shieldai-unified-restructure-37]
+ tags: [android, jetpack-compose, dashboard, services, mobile]
+
+objective:
+- Build the main dashboard and all service-specific screens for the Android app. These should mirror the web app's functionality while using native Android UI patterns (lists, cards, bottom sheets, charts).
+
+deliverables:
+- `android/app/src/main/java/com/shieldai/android/ui/screens/dashboard/` — Dashboard:
+ - `DashboardScreen.kt` — Main dashboard with scrollable content:
+ - Threat score circular gauge (custom Canvas composable)
+ - Recent alerts list (top 5)
+ - Service summary cards in horizontal scroll or grid
+ - Quick action FABs or chips
+ - Pull-to-refresh with `PullRefreshIndicator`
+ - `AlertDetailScreen.kt` — Alert detail with correlated alerts
+- `android/app/src/main/java/com/shieldai/android/ui/screens/services/` — Service screens:
+ - `DarkWatchScreen.kt` — Watchlist and exposures
+ - `VoicePrintScreen.kt` — Enrollments and analysis history
+ - `SpamShieldScreen.kt` — Stats, rules, number check
+ - `HomeTitleScreen.kt` — Properties, map, changes
+ - `RemoveBrokersScreen.kt` — Broker registry, requests
+- `android/app/src/main/java/com/shieldai/android/ui/screens/settings/` — Settings:
+ - `SettingsScreen.kt` — Account, preferences, family, logout
+- `android/app/src/main/java/com/shieldai/android/viewmodel/` — ViewModels:
+ - `DashboardViewModel.kt`, `DarkWatchViewModel.kt`, `VoicePrintViewModel.kt`, etc.
+ - Each exposes `StateFlow` with Loading, Success, Error states
+
+steps:
+1. Create screen directories: `dashboard/`, `services/`, `settings/`.
+2. **DashboardScreen**:
+ - `LazyColumn` for scrollable content
+ - Threat score: custom `Canvas` composable drawing circular arc with gradient
+ - Alerts section: `LazyRow` or vertical list of `ShieldCard` items
+ - Service cards: `LazyHorizontalGrid` or `Row` of compact cards
+ - Quick actions: `FloatingActionButton` or `Row` of icon buttons
+ - `PullRefreshIndicator` with `rememberPullRefreshState`
+3. **AlertDetailScreen**:
+ - `Column` with sections
+ - Severity `ShieldBadge` at top
+ - Description and metadata
+ - Correlated alerts in nested list
+ - Action buttons: "Mark Resolved", "False Positive"
+4. **DarkWatchScreen**:
+ - `LazyColumn` with sticky headers for sections
+ - Watchlist items: swipe-to-delete with `SwipeToDismissBox`
+ - Add button → bottom sheet with form
+ - Exposures: tap to navigate to detail
+5. **VoicePrintScreen**:
+ - Enrollments section with audio playback (ExoPlayer or MediaPlayer)
+ - "Enroll" FAB → bottom sheet with recording UI
+ - Analysis list with verdict color coding
+ - Recording UI: real-time waveform using `Canvas` + `AudioRecord`
+6. **SpamShieldScreen**:
+ - Stats cards at top in `Row`
+ - Rules list with toggle switches
+ - Number check: `ShieldTextField` + check button → result card
+ - "Report spam" FAB
+7. **HomeTitleScreen**:
+ - Properties list with addresses
+ - Add property: address search with geocoding
+ - Property detail: Google Maps Compose or static map image
+ - Snapshot history and changes list
+8. **RemoveBrokersScreen**:
+ - Broker registry with search and category chips
+ - Request list with status badges and progress bars
+ - Start removal: bottom sheet with broker selection and form
+9. **SettingsScreen**:
+ - `LazyColumn` with sections using `ListItem` or custom rows
+ - Account info with avatar
+ - Subscription card with upgrade button
+ - Notification toggles
+ - Theme selection dropdown
+ - Biometric auth toggle
+ - Family group management
+ - Logout button with confirmation dialog
+10. **ViewModels**:
+ - Each screen has a `ViewModel` with `StateFlow`
+ - `UiState` sealed class: `Loading`, `Success(data)`, `Error(message)`
+ - Call repository methods, handle errors, expose state
+ - Use `viewModelScope.launch` for coroutines
+11. Wire navigation in `AppNavigation.kt`:
+ - Bottom nav routes to dashboard and services
+ - Settings accessible from bottom nav or profile menu
+ - Service screens accessible from dashboard or bottom nav submenu
+
+steps:
+- Unit: Each ViewModel emits correct states for loading/success/error
+- Unit: DashboardViewModel aggregates data from multiple repositories
+- Visual: All screens use theme tokens and adapt to dark mode
+- E2E: Navigate through all service screens and verify data loads
+- E2E: Perform CRUD operations (add watchlist item, delete enrollment, create rule)
+
+acceptance_criteria:
+- [ ] Dashboard displays threat score, alerts, service summaries, and quick actions
+- [ ] All 5 service screens (DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers) load and display data
+- [ ] Each service screen supports core CRUD operations
+- [ ] Alert detail shows full information and correlation group
+- [ ] Settings screen allows managing account, preferences, and family
+- [ ] Pull-to-refresh updates dashboard data
+- [ ] All screens show loading skeletons and empty states appropriately
+- [ ] Navigation between screens works with native Android transitions
+
+validation:
+- Launch app, login, and verify dashboard renders with real data
+- Tap each service tab and verify screen loads
+- Add a watchlist item in DarkWatch and verify it appears in list
+- Delete a voice enrollment and verify it disappears
+- Create a spam rule and verify it applies
+- Toggle settings and verify preferences persist
+- Run `./gradlew test` for unit tests
+
+notes:
+- Use native Android patterns: `LazyColumn`/`LazyRow` for lists, `BottomSheet` for modals, `FloatingActionButton` for primary actions, `Chip` for filters.
+- For the threat score gauge, draw an arc on `Canvas` using `drawArc` with a `SweepGradient` brush.
+- The map in HomeTitle can use Google Maps Compose (`com.google.maps.android:maps-compose`) or OpenStreetMap.
+- Voice recording requires microphone permission. Add `RECORD_AUDIO` permission to manifest and request at runtime.
+- Keep ViewModels separate from UI for testability. ViewModels should own the state and business logic.
+- Consider using `Paging 3` (`androidx.paging:paging-compose`) for large lists (e.g., alert history, exposure list).
+- For image loading in lists, use Coil (`io.coil-kt:coil-compose`).
diff --git a/tasks/shieldai-unified-restructure/39-android-native-features.md b/tasks/shieldai-unified-restructure/39-android-native-features.md
new file mode 100644
index 0000000..ecb0bf6
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/39-android-native-features.md
@@ -0,0 +1,135 @@
+# 39. Android App — Push Notifications, Biometrics, Voice Enrollment, Call Screening
+
+meta:
+ id: shieldai-unified-restructure-39
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-34, shieldai-unified-restructure-35, shieldai-unified-restructure-36, shieldai-unified-restructure-37, shieldai-unified-restructure-38]
+ tags: [android, jetpack-compose, native-features, push, biometrics, call-screening, mobile]
+
+objective:
+- Implement native Android features that differentiate the mobile experience: push notifications via FCM, biometric authentication, voice enrollment with audio recording, and call screening integration for SpamShield.
+
+deliverables:
+- `android/app/src/main/java/com/shieldai/android/service/FCMService.kt` — Firebase Cloud Messaging:
+ - Extends `FirebaseMessagingService`
+ - `onMessageReceived` — processes incoming notifications
+ - `onNewToken` — sends token to backend (task 14)
+ - Creates notification channels for different alert types
+ - Rich notifications with images and actions
+ - Deep links to relevant screens based on payload
+- `android/app/src/main/java/com/shieldai/android/ui/screens/auth/BiometricAuthScreen.kt` — Biometric prompt:
+ - Uses `BiometricPrompt` from `androidx.biometric:biometric`
+ - Face/fingerprint authentication
+ - Fallback to device PIN/pattern/password
+ - Stores credential in `EncryptedSharedPreferences`
+- `android/app/src/main/java/com/shieldai/android/ui/screens/voiceprint/RecordingScreen.kt` — Voice recording:
+ - Real-time waveform visualization using `AudioRecord` + `Canvas`
+ - Record / stop / playback controls
+ - Duration timer
+ - Quality check (minimum duration, amplitude threshold)
+ - Submit enrollment to API
+- `android/app/src/main/java/com/shieldai/android/service/CallScreeningService.kt` — Call screening:
+ - Extends `CallScreeningService` (API 29+)
+ - Intercepts incoming calls
+ - Queries `spamshield.checkNumber` via API
+ - Displays caller info overlay with reputation score
+ - Auto-blocks known spam numbers based on user rules
+ - Logs screened calls for history
+- `android/app/src/main/java/com/shieldai/android/util/PermissionManager.kt` — Permission handling:
+ - Centralized manager for all runtime permissions
+ - Camera, microphone, phone, notifications, call screening
+ - Handles permission rationale and denied states
+- `android/app/src/main/AndroidManifest.xml` — Updated permissions and services:
+ - `INTERNET`, `RECORD_AUDIO`, `READ_PHONE_STATE`, `CALL_PHONE`
+ - `BIND_CALL_SCREENING_SERVICE`
+ - `RECEIVE_BOOT_COMPLETED` (for starting services)
+ - FCM service declaration
+ - Call screening service declaration
+
+steps:
+1. **Push Notifications (FCM)**:
+ - Add `google-services.json` to `app/` directory
+ - Add `com.google.gms:google-services` plugin to build.gradle
+ - Create `FCMService.kt` extending `FirebaseMessagingService`
+ - `onNewToken`: send to backend via `api.notification.registerDevice`
+ - `onMessageReceived`:
+ - Parse notification payload
+ - Create notification channel if not exists (Android 8+)
+ - Show notification with `NotificationManager`
+ - Handle data messages (silent pushes) for background sync
+ - Add `FirebaseMessaging.getInstance().subscribeToTopic("alerts")` for broadcast alerts
+ - Handle notification tap: extract `screen` and `id`, navigate via deep link
+2. **Biometric Auth**:
+ - `BiometricPrompt` with `BiometricPrompt.PromptInfo`
+ - `setDeviceCredentialAllowed(true)` for fallback
+ - `setConfirmationRequired(false)` for faster auth
+ - On success: unlock `EncryptedSharedPreferences`
+ - Show prompt on app launch if biometric is enabled
+3. **Voice Recording**:
+ - Request `RECORD_AUDIO` permission at runtime
+ - `AudioRecord` with `MediaRecorder.AudioSource.MIC`
+ - Configuration: 16kHz, mono, 16-bit PCM
+ - Real-time waveform: read amplitude in a coroutine, update `Canvas` path
+ - Minimum duration: 5 seconds
+ - Save as WAV file
+ - Playback with `MediaPlayer`
+ - Submit to API via multipart upload
+4. **Call Screening**:
+ - Extend `CallScreeningService`
+ - `onScreenCall(details)` callback when incoming call arrives
+ - Extract phone number from `Call.Details`
+ - Query API: `spamshield.checkNumber` (use cached result if available)
+ - `respondToCall` with `CallResponse.Builder`:
+ - If spam: `setDisallowCall(true)`, `setRejectCall(true)`, `setSkipCallLog(false)`
+ - If suspicious: `setDisallowCall(false)` but show warning notification
+ - If clean: `setDisallowCall(false)`
+ - Show custom incoming call UI overlay (optional, requires additional permissions)
+ - Log all screened calls to local DB
+5. **Permission Manager**:
+ - `checkPermission(permission)` → boolean
+ - `requestPermission(permission, rationale)` → shows dialog, then system prompt
+ - `handlePermissionResult(requestCode, permissions, grantResults)`
+ - Guides user to Settings if permission permanently denied
+6. Update manifest:
+ - Add all required permissions
+ - Declare FCM service with `android:exported="false"`
+ - Declare call screening service with `android:permission="android.permission.BIND_CALL_SCREENING_SERVICE"`
+7. Test on physical device (emulator cannot test FCM, biometrics, or call screening accurately).
+
+steps:
+- Unit: FCMService parses notification payload correctly
+- Unit: BiometricPrompt configuration is valid
+- Unit: PermissionManager returns correct status for each permission
+- Integration: FCM token registration sends correct data to backend
+- E2E: Receive test push notification and verify deep link navigation
+- E2E: Record voice sample and submit enrollment successfully
+- E2E: Simulate incoming call and verify screening logic
+
+acceptance_criteria:
+- [ ] App registers for FCM and sends device token to backend
+- [ ] Incoming push notifications display correctly with channels and actions
+- [ ] Tapping a notification deep links to the correct screen
+- [ ] Face/fingerprint authentication works for app unlock
+- [ ] Voice recording captures audio, shows waveform, and submits enrollment
+- [ ] Call screening intercepts incoming calls and blocks known spam
+- [ ] All permission requests include explanatory rationale
+- [ ] Denied permissions show helpful guidance to Settings app
+- [ ] Native features work on phones with API 26+
+
+validation:
+- Test push notifications using Firebase Console
+- Verify biometric auth on device with face/fingerprint sensor
+- Record a 10-second voice sample and verify enrollment created in backend
+- Simulate incoming call using `adb shell am start -a android.intent.action.CALL -d tel:1234567890`
+- Run `./gradlew test` for unit tests
+
+notes:
+- FCM requires a `google-services.json` file from Firebase Console. Add setup instructions to README.
+- Call screening (`CallScreeningService`) is only available on Android 10+ (API 29+). For older versions, use a broadcast receiver for `PHONE_STATE` changes as fallback.
+- The call screening service runs in the background and must be lightweight. Offload API calls to a coroutine.
+- For call screening UI, the system provides a default incoming call screen. Custom UI overlays require `SYSTEM_ALERT_WINDOW` permission and are complex to implement correctly.
+- Voice recording quality matters for ML model accuracy. Use 16kHz mono WAV — this matches the web app's preprocessing pipeline.
+- Biometric auth should be optional. Users can always use password login.
+- Consider adding a Quick Settings tile for toggling call screening on/off.
+- For Android 14+ (API 34+), use the new `android.telecom.Call Screening` APIs if available.
diff --git a/tasks/shieldai-unified-restructure/40-shared-mobile-assets.md b/tasks/shieldai-unified-restructure/40-shared-mobile-assets.md
new file mode 100644
index 0000000..bd2a114
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/40-shared-mobile-assets.md
@@ -0,0 +1,136 @@
+# 40. Shared Mobile Assets — Icons, Colors, Typography, and Brand Guidelines
+
+meta:
+ id: shieldai-unified-restructure-40
+ feature: shieldai-unified-restructure
+ priority: P1
+ depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-34]
+ tags: [design, assets, branding, mobile, web]
+
+objective:
+- Create a centralized design token system and asset library that all three platforms (web, iOS, Android) reference. Document brand guidelines and generate platform-specific asset exports from a single source of truth.
+
+deliverables:
+- `design-tokens/` directory at project root:
+ - `colors.json` — All color tokens in JSON format:
+ - Brand: primary, primaryLight, primaryDark, accent, accentLight, accentDark
+ - Semantic: success, warning, error, info
+ - Backgrounds: bg, bgSecondary, bgTertiary (with light and dark variants)
+ - Text: textPrimary, textSecondary, textTertiary
+ - Borders: border, borderDark
+ - All values as hex codes
+ - `typography.json` — Typography scale:
+ - Font family: Inter
+ - Sizes: caption, body, headline, title, largeTitle with pixel sizes and line heights
+ - Weights: regular, medium, semibold, bold
+ - `spacing.json` — Spacing scale:
+ - xs (4), sm (8), md (16), lg (24), xl (32), xxl (48), xxxl (64)
+ - `shadows.json` — Shadow definitions:
+ - sm, md, lg, xl with x, y, blur, spread, color
+ - `radius.json` — Border radius scale:
+ - none, sm, md, lg, xl, full
+- `assets/` directory at project root:
+ - `logo/` — ShieldAI logo in multiple formats:
+ - SVG (source of truth)
+ - PNG exports at 1x, 2x, 3x, 4x
+ - PDF (for iOS vector assets)
+ - XML vector drawable (for Android)
+ - `icons/` — Icon set in SVG format:
+ - All icons used in the app (shield, microphone, phone, home, user, lock, bell, etc.)
+ - Organized by category (navigation, services, actions, status)
+ - Named consistently across platforms
+ - `illustrations/` — Marketing illustrations:
+ - Hero illustration, empty state illustrations, onboarding illustrations
+ - SVG source + PNG exports
+- `docs/BRAND_GUIDELINES.md` — Brand documentation:
+ - Color palette with usage rules
+ - Typography guidelines
+ - Spacing and layout rules
+ - Icon usage guidelines
+ - Voice and tone guidelines
+ - Do's and don'ts with visual examples
+- Generation scripts:
+ - `scripts/generate-assets.sh` — Converts SVGs to platform-specific formats
+ - `scripts/generate-tokens.sh` — Generates platform code from JSON tokens:
+ - `web/src/theme/tokens.ts` from `colors.json`
+ - `iOS/ShieldAI/Theme/GeneratedTokens.swift` from JSON
+ - `android/app/src/main/res/values/generated_tokens.xml` from JSON
+
+steps:
+1. Create `design-tokens/` directory with JSON files.
+2. Define all tokens in JSON with comments/documentation:
+ ```json
+ {
+ "colors": {
+ "brand": {
+ "primary": { "light": "#4F46E5", "dark": "#818CF8" },
+ "primaryDark": { "light": "#4338CA", "dark": "#A5B4FC" },
+ ...
+ }
+ }
+ }
+ ```
+3. Create `assets/logo/`:
+ - Design or export ShieldAI logo as SVG
+ - Generate PNGs at 64px, 128px, 256px, 512px, 1024px
+ - Generate iOS app icon set (all required sizes)
+ - Generate Android mipmap densities (mdpi to xxxhdpi)
+ - Generate favicon sizes for web
+4. Create `assets/icons/`:
+ - Source all icons as 24x24px SVGs with consistent stroke width (1.5px or 2px)
+ - Name them consistently: `icon-shield.svg`, `icon-microphone.svg`, etc.
+ - Generate PNG exports at 1x, 2x, 3x
+5. Create `assets/illustrations/`:
+ - Empty state illustrations (no data, no alerts, etc.)
+ - Onboarding illustrations
+ - Export as SVG + PNG (2x)
+6. Write `docs/BRAND_GUIDELINES.md`:
+ - Introduction to ShieldAI brand identity
+ - Color section: primary palette, semantic colors, accessibility requirements (WCAG AA)
+ - Typography section: Inter font, scale, usage rules
+ - Spacing section: 8px base grid, scale usage
+ - Icon section: style (outlined, 1.5px stroke), sizing rules
+ - Voice and tone: security-focused, empowering, clear, trustworthy
+ - Examples of correct and incorrect usage
+7. Create generation scripts:
+ - `scripts/generate-assets.sh`: use `inkscape` or `rsvg-convert` for SVG→PNG, `svg2android` for vector drawables
+ - `scripts/generate-tokens.sh`: use a Node.js script or Python script to read JSON and output platform-specific code
+ - Web: generate `tokens.ts` with exported const objects
+ - iOS: generate `GeneratedTokens.swift` with `struct Tokens { struct Colors { static let brandPrimary = Color(...) } }`
+ - Android: generate `generated_tokens.xml` with color resources and dimen resources
+8. Run generation scripts and verify outputs in each platform directory.
+9. Update all platform code to import from generated tokens instead of hardcoded values.
+10. Add CI check that fails if tokens are modified without regenerating platform code.
+
+steps:
+- Visual: All generated assets render correctly on each platform
+- Visual: Generated tokens match hand-coded values exactly
+- Documentation: Brand guidelines are comprehensive and include examples
+- Integration: CI script detects token drift
+
+acceptance_criteria:
+- [ ] `design-tokens/` contains complete JSON definitions for colors, typography, spacing, shadows, radius
+- [ ] `assets/` contains logo, icons, and illustrations in all required formats
+- [ ] Brand guidelines document is complete with do's and don'ts
+- [ ] Generation scripts successfully produce platform-specific code from JSON tokens
+- [ ] Web app imports colors from generated `tokens.ts`
+- [ ] iOS app imports colors from generated `GeneratedTokens.swift`
+- [ ] Android app imports colors from generated `generated_tokens.xml`
+- [ ] All platforms use the exact same hex values for corresponding tokens
+- [ ] CI detects when tokens are modified without running generation scripts
+
+validation:
+- Run `scripts/generate-tokens.sh` and verify output files are created
+- Compare a color value across all three platforms and confirm they match
+- Open generated assets in each platform and verify they render correctly
+- Review brand guidelines document for completeness
+- Test CI script by modifying a token without regenerating
+
+notes:
+- The design tokens are the contract between design and engineering. Keep them authoritative and well-documented.
+- Consider using a design token management tool like Style Dictionary, Token Studio, or Tokens Studio for Figma for more advanced workflows.
+- The generation scripts should be run automatically in CI when `design-tokens/` changes.
+- For the logo, ensure it works at small sizes (favicon, app icon) and large sizes (hero, splash).
+- Icons should be designed on a 24x24px grid with 1.5px or 2px stroke for consistency with Material Design and SF Symbols.
+- Illustrations should be simple, on-brand, and optimized for web/mobile performance.
+- The brand voice should be: "We protect you. We're smart about it. We explain things clearly."
diff --git a/tasks/shieldai-unified-restructure/41-cleanup-legacy.md b/tasks/shieldai-unified-restructure/41-cleanup-legacy.md
new file mode 100644
index 0000000..e17e4d5
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/41-cleanup-legacy.md
@@ -0,0 +1,116 @@
+# 41. Cleanup — Remove Legacy packages/, services/, and server/ Directories
+
+meta:
+ id: shieldai-unified-restructure-41
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-26, shieldai-unified-restructure-27]
+ tags: [cleanup, infrastructure, migration]
+
+objective:
+- Safely remove all legacy directories that are no longer needed after the unified restructure. Ensure no functionality is lost and all important code has been migrated to the new monolithic architecture.
+
+deliverables:
+- Archived legacy code (optional but recommended):
+ - Git branch `archive/legacy-pre-restructure` containing full state before cleanup
+ - Or tar.gz archive in `archives/` directory
+- Removed directories:
+ - `packages/` — all subdirectories (api, core, correlation, db, extension, integration-tests, jobs, mobile, mobile-api-client, monitoring, report, shared-analytics, shared-auth, shared-billing, shared-db, shared-notifications, shared-ui, shared-utils, types, web)
+ - `services/` — all subdirectories (darkwatch, hometitle, removebrokers, spamshield, voiceprint)
+ - `server/` — alerts and webrtc subdirectories
+ - `infra/` — infrastructure configs (if not moved to web/ or docs/)
+ - `load-tests/` — load testing scripts (if not moved)
+ - `docs/` — evaluate and either move to `web/docs/` or archive
+- Updated root configuration:
+ - `package.json` with clean scripts referencing only `web/` and `browser-ext/`
+ - `pnpm-workspace.yaml` with only `web` and `browser-ext`
+ - `turbo.json` with only relevant tasks
+ - `.gitignore` cleaned of legacy entries
+ - `README.md` updated to reflect new architecture
+- Verification checklist confirming:
+ - All Fastify routes migrated to tRPC routers
+ - All service logic migrated to `web/src/server/services/`
+ - All Prisma schema migrated to Drizzle
+ - All web pages migrated to new `web/` app
+ - All extension code migrated to `browser-ext/`
+ - No broken imports or references to removed directories
+
+steps:
+1. Create archive:
+ - `git branch archive/legacy-pre-restructure` (from current HEAD)
+ - Or `tar -czf archives/legacy-$(date +%Y%m%d).tar.gz packages/ services/ server/ infra/ load-tests/`
+2. Verify migration completeness:
+ - Checklist: go through each legacy directory and confirm its contents have been migrated
+ - `packages/api/src/routes/` → `web/src/server/api/routers/`
+ - `packages/db/prisma/schema.prisma` → `web/src/server/db/schema.ts`
+ - `packages/web/src/` → `web/src/` (pages, components, hooks)
+ - `packages/extension/src/` → `browser-ext/src/`
+ - `services/*/src/` → `web/src/server/services/*/`
+ - `packages/jobs/src/` → `web/src/server/jobs/`
+ - `packages/shared-*/src/` → merged into `web/src/server/services/` or `web/src/lib/`
+3. Remove directories:
+ - `rm -rf packages/`
+ - `rm -rf services/`
+ - `rm -rf server/`
+ - `rm -rf infra/` (or move relevant files)
+ - `rm -rf load-tests/` (or move relevant files)
+ - `rm -rf docs/` (or move to `web/docs/`)
+4. Clean root files:
+ - Remove `check-identity.js`, `test-classifier.ts`, `test-maxpayload.ts`, `test-ws-maxpayload*.js` (if not moved)
+ - Remove `docker-compose.yml`, `docker-compose.prod.yml`, `Dockerfile` (will be recreated in task 42)
+ - Remove `vite.config.ts` at root (only needed in `web/`)
+ - Remove `vitest.config.ts` at root (only needed in `web/`)
+ - Remove `tsconfig.base.json`, `tsconfig.json` at root (only needed in `web/`)
+5. Update root configs:
+ - `package.json`: update scripts, remove legacy dependencies
+ - `pnpm-workspace.yaml`: confirm only `web` and `browser-ext`
+ - `turbo.json`: update tasks for new structure
+ - `.gitignore`: remove entries for deleted directories
+6. Update `README.md`:
+ - New architecture diagram (unified monolith)
+ - Updated directory structure
+ - Updated setup instructions
+ - Migration notes
+7. Run full verification:
+ - `pnpm install` from root
+ - `pnpm dev` starts web app
+ - `pnpm build` in `web/` succeeds
+ - `pnpm test` in `web/` passes
+ - `cd browser-ext && pnpm build` succeeds
+ - No references to `packages/`, `services/`, or `server/` in any file
+8. Commit cleanup with clear message.
+
+steps:
+- Integration: `pnpm install` completes without errors
+- Integration: `pnpm dev` starts unified web app
+- Integration: `pnpm build` produces valid output
+- Integration: `pnpm test` passes all tests
+- Search: `grep -r "packages/\|services/\|server/" --include="*.ts" --include="*.tsx" --include="*.json" --include="*.md" .` returns only expected references (e.g., in README migration notes)
+
+acceptance_criteria:
+- [ ] Legacy directories are removed from working tree
+- [ ] Archive branch or tar.gz exists with full legacy state
+- [ ] Root `package.json` only references `web/` and `browser-ext/`
+- [ ] `pnpm-workspace.yaml` only includes `web` and `browser-ext`
+- [ ] `pnpm install` resolves dependencies cleanly
+- [ ] `pnpm dev` starts the unified app without errors
+- [ ] `pnpm build` succeeds for web app
+- [ ] `pnpm test` passes for web app
+- [ ] No broken imports or references to removed directories
+- [ ] README is updated with new architecture and setup instructions
+
+validation:
+- `ls packages/ services/ server/` → "No such file or directory"
+- `git branch -a | grep archive` → shows archive branch
+- `pnpm install && pnpm dev` → web app starts on localhost:3000
+- `cd web && pnpm build` → completes successfully
+- `cd web && pnpm test` → all tests pass
+- `grep -r "@shieldai/" --include="*.ts" --include="*.tsx" .` → no references to old workspace packages
+
+notes:
+- Do NOT delete git history. The archive branch preserves everything.
+- If any legacy file is discovered to be needed after cleanup, it can be retrieved from the archive branch.
+- The `docs/` directory may contain valuable documentation. Review contents before deleting — move relevant docs to `web/docs/` or root `README`.
+- `examples/` and `assets/` at root should also be reviewed and either moved or deleted.
+- This is a destructive operation. Double-check the migration checklist before executing.
+- Consider keeping `plans/` directory if it contains active project plans. Move to `docs/plans/` if needed.
diff --git a/tasks/shieldai-unified-restructure/42-deployment-config.md b/tasks/shieldai-unified-restructure/42-deployment-config.md
new file mode 100644
index 0000000..5cc8526
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/42-deployment-config.md
@@ -0,0 +1,177 @@
+# 42. Deployment — Update Docker, CI/CD, and Environment Configuration
+
+meta:
+ id: shieldai-unified-restructure-42
+ feature: shieldai-unified-restructure
+ priority: P0
+ depends_on: [shieldai-unified-restructure-41]
+ tags: [deployment, docker, ci-cd, infrastructure]
+
+objective:
+- Create production-ready deployment configuration for the unified monolith. Update Docker containers, CI/CD pipelines, and environment configuration to support the new single-app architecture plus native mobile builds.
+
+deliverables:
+- `web/Dockerfile` — Production container for the web app:
+ - Multi-stage build: Node.js builder → runtime
+ - Installs pnpm, dependencies, builds app
+ - Exposes port 3000
+ - Health check endpoint
+ - Non-root user for security
+- `docker-compose.yml` — Local development orchestration:
+ - `web` service: builds from `web/Dockerfile`, ports `3000:3000`
+ - `postgres` service: PostgreSQL 16 with volume for data persistence
+ - `redis` service: Redis 7 for job queues and caching
+ - `nginx` service: reverse proxy with SSL termination (optional)
+ - Environment variables from `.env` file
+- `docker-compose.prod.yml` — Production orchestration:
+ - Similar to dev but with production-optimized settings
+ - Volume mounts for uploads/logs
+ - Restart policies
+ - Resource limits
+- `.github/workflows/ci.yml` — GitHub Actions CI pipeline:
+ - Lint and type check (TypeScript)
+ - Unit tests for web app
+ - Build verification
+ - Dependency audit (`pnpm audit`)
+- `.github/workflows/deploy.yml` — GitHub Actions CD pipeline:
+ - Build Docker image on release tag
+ - Push to container registry (GitHub Packages, Docker Hub, or ECR)
+ - Deploy to staging on push to `main`
+ - Deploy to production on release tag
+ - Run database migrations before deployment
+ - Health check after deployment
+- `web/.env.example` — Complete environment variable documentation:
+ - Database: `DATABASE_URL`
+ - Redis: `REDIS_URL`
+ - Auth: `JWT_SECRET`, `NEXTAUTH_SECRET`
+ - Stripe: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_PRICE_*`
+ - Notifications: `RESEND_API_KEY`, `FIREBASE_SERVICE_ACCOUNT_PATH`, `TWILIO_*`
+ - External APIs: `HIBP_API_KEY`, `SECURITYTRAILS_API_KEY`, etc.
+ - Monitoring: `SENTRY_DSN`, `DATADOG_API_KEY` (optional)
+ - App: `NODE_ENV`, `PORT`, `API_URL`, `APP_URL`
+- `scripts/deploy.sh` — Deployment helper script:
+ - Database backup before migration
+ - Migration runner
+ - Health check verification
+ - Rollback on failure
+- `scripts/backup.sh` — Database backup script:
+ - `pg_dump` to timestamped file
+ - Upload to S3 or similar storage
+
+steps:
+1. Create `web/Dockerfile`:
+ ```dockerfile
+ # Build stage
+ FROM node:20-alpine AS builder
+ WORKDIR /app
+ RUN npm install -g pnpm
+ COPY web/package.json web/pnpm-lock.yaml ./
+ RUN pnpm install --frozen-lockfile
+ COPY web/ .
+ RUN pnpm build
+
+ # Runtime stage
+ FROM node:20-alpine
+ WORKDIR /app
+ RUN npm install -g pnpm
+ COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./
+ COPY --from=builder /app/node_modules ./node_modules
+ COPY --from=builder /app/.output ./.output
+ COPY --from=builder /app/drizzle ./drizzle
+ ENV NODE_ENV=production
+ EXPOSE 3000
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:3000/health || exit 1
+ USER node
+ CMD ["node", ".output/server/index.mjs"]
+ ```
+2. Create `docker-compose.yml`:
+ - Define services with appropriate environment variables
+ - PostgreSQL with `volumes: postgres_data:/var/lib/postgresql/data`
+ - Redis with `volumes: redis_data:/data`
+ - Network configuration
+3. Create `docker-compose.prod.yml`:
+ - Add restart policies: `unless-stopped`
+ - Add resource limits: `mem_limit`, `cpus`
+ - Add logging driver configuration
+ - Remove port bindings for internal services (postgres, redis)
+4. Create `.github/workflows/ci.yml`:
+ - Trigger: push to any branch, pull requests
+ - Jobs:
+ - `lint`: `pnpm lint`
+ - `typecheck`: `tsc --noEmit`
+ - `test`: `pnpm test`
+ - `build`: `pnpm build`
+ - `audit`: `pnpm audit --audit-level=high`
+5. Create `.github/workflows/deploy.yml`:
+ - Trigger: release tags (`v*`), manual dispatch
+ - Jobs:
+ - `build`: build Docker image, tag with version
+ - `push`: push to registry
+ - `migrate`: SSH into server, run `pnpm db:migrate`
+ - `deploy`: update Docker Compose stack
+ - `healthcheck`: verify `/health` endpoint responds 200
+ - Use GitHub secrets for SSH keys, registry credentials
+6. Update `web/.env.example`:
+ - Document every environment variable used by the app
+ - Include description, example value, and whether required
+ - Group by category (Database, Auth, Payments, APIs, etc.)
+7. Create `scripts/deploy.sh`:
+ - `#!/bin/bash` with error handling (`set -euo pipefail`)
+ - Backup database: `docker exec postgres pg_dump ...`
+ - Run migrations: `docker compose exec web pnpm db:migrate`
+ - Deploy: `docker compose -f docker-compose.prod.yml up -d`
+ - Health check: `curl -f http://localhost:3000/health`
+ - Rollback on failure: `docker compose rollback` or restore backup
+8. Create `scripts/backup.sh`:
+ - Generate timestamped dump
+ - Compress with gzip
+ - Upload to S3 using AWS CLI or rclone
+ - Retain last 30 backups
+9. Test deployment locally:
+ - `docker compose up --build`
+ - Verify app accessible at `http://localhost:3000`
+ - Verify database migrations run
+ - Verify health check passes
+10. Document deployment process in `docs/DEPLOYMENT.md`.
+
+steps:
+- Integration: `docker compose up --build` starts all services successfully
+- Integration: App is accessible and functional inside Docker
+- Integration: CI pipeline passes on GitHub Actions
+- Integration: Deployment script completes without errors
+- Security: Dockerfile uses non-root user
+- Security: No secrets committed to repository
+
+acceptance_criteria:
+- [ ] `web/Dockerfile` builds a production-ready container
+- [ ] `docker-compose.yml` orchestrates web, postgres, and redis for local dev
+- [ ] `docker-compose.prod.yml` is optimized for production with restart policies and resource limits
+- [ ] CI pipeline runs lint, type check, tests, build, and audit on every PR
+- [ ] CD pipeline builds and deploys on release tags
+- [ ] Database migrations run automatically before deployment
+- [ ] Health check endpoint verifies app is ready
+- [ ] Rollback script restores previous version on deployment failure
+- [ ] Backup script creates and stores database dumps
+- [ ] `.env.example` documents all required environment variables
+- [ ] No secrets are present in repository (verified by `git-secrets` or manual audit)
+
+validation:
+- `docker compose up --build -d` → verify `docker ps` shows all containers running
+- `curl http://localhost:3000/health` → returns `{"status":"ok"}`
+- `docker logs shieldai-web` → no startup errors
+- Push to a test branch and verify GitHub Actions CI pipeline runs
+- Create a test release tag and verify CD pipeline triggers
+- Run `scripts/backup.sh` and verify dump file created
+- Run `scripts/deploy.sh` and verify deployment succeeds
+
+notes:
+- The unified monolith simplifies deployment significantly: one container instead of 5+ microservices.
+- For high availability, run multiple web container instances behind a load balancer (nginx, AWS ALB, etc.).
+- Consider using a managed database (RDS, Supabase, Neon) instead of self-hosted PostgreSQL for production.
+- For Redis, consider Upstash or ElastiCache for managed service.
+- The web app uses SolidStart with Nitro, which can run as a standalone server. Ensure the `.output/server/index.mjs` entry point is correct.
+- For SSL, use Let's Encrypt with nginx or a managed load balancer. Document certificate renewal.
+- Monitor disk space for logs and uploads. Configure log rotation in Docker.
+- For mobile app deployment (iOS/Android), set up separate CI workflows for building and signing apps. This is outside the scope of the web deployment but should be documented.
+- Consider using Terraform or Pulumi for infrastructure as code if managing cloud resources.
diff --git a/tasks/shieldai-unified-restructure/README.md b/tasks/shieldai-unified-restructure/README.md
new file mode 100644
index 0000000..5881205
--- /dev/null
+++ b/tasks/shieldai-unified-restructure/README.md
@@ -0,0 +1,89 @@
+# ShieldAI Unified Restructure
+
+Objective: Restructure ShieldAI from a fragmented microservices/packages architecture into a unified, cohesive SolidStart monolith with a Lendair-inspired landing page, auto-shifting theme, and all backend functionality preserved — plus native iOS and Android apps.
+
+Status legend: [ ] todo, [~] in-progress, [x] done
+
+Tasks
+- [ ] 01 — Project Foundation — Root Config & Directory Cleanup → `01-project-foundation-cleanup.md`
+- [ ] 02 — Theme System — Auto-Shifting CSS with ShieldAI Brand Palette → `02-theme-system-brand-palette.md`
+- [ ] 03 — UI Primitive Library — Button, Card, Input, Badge, Modal, Toast → `03-ui-primitive-library.md`
+- [ ] 04 — Layout Components — Navbar, Footer, PageContainer, AppShell → `04-layout-components.md`
+- [ ] 05 — Landing Page — Hero Section with Animated Background → `05-landing-page-hero.md`
+- [ ] 06 — Landing Page — Features, How It Works, CTA Sections → `06-landing-page-features.md`
+- [ ] 07 — Auth Pages — Login, Signup, Password Reset, Onboarding → `07-auth-pages.md`
+- [ ] 08 — Migrate & Redesign Existing Pages — Blog, Ads, Dashboard Shell → `08-migrate-existing-pages.md`
+- [ ] 09 — Database — Migrate Full Prisma Schema to Drizzle ORM → `09-drizzle-schema-migration.md`
+- [ ] 10 — Database — PostgreSQL Connection, Migrations, and Seed Data → `10-db-connection-migrations.md`
+- [ ] 11 — tRPC Foundation — Auth Context, Middleware, and Protected Procedures → `11-trpc-auth-context.md`
+- [ ] 12 — Backend Router — User & Family Group Management → `12-user-family-router.md`
+- [ ] 13 — Backend Router — Subscriptions, Billing, and Stripe Webhooks → `13-subscription-billing-router.md`
+- [ ] 14 — Backend Router — Email, Push, and SMS Notifications → `14-notifications-router.md`
+- [ ] 15 — Backend Router — DarkWatch (Dark Web Monitoring) → `15-darkwatch-router.md`
+- [ ] 16 — Backend Router — VoicePrint (Voice Cloning Detection) → `16-voiceprint-router.md`
+- [ ] 17 — Backend Router — SpamShield (Spam Detection & Call Analysis) → `17-spamshield-router.md`
+- [ ] 18 — Backend Router — HomeTitle (Property Monitoring) → `18-hometitle-router.md`
+- [ ] 19 — Backend Router — RemoveBrokers (Data Broker Removal) → `19-removebrokers-router.md`
+- [ ] 20 — Backend Router — Alert Correlation & Normalization Engine → `20-alert-correlation-router.md`
+- [ ] 21 — Backend Router — Security Report Generation → `21-report-generation-router.md`
+- [ ] 22 — Background Jobs — Scheduler, Scan Workers, and Reminders → `22-background-jobs.md`
+- [ ] 23 — Frontend Integration — Wire All Pages to tRPC APIs → `23-frontend-api-integration.md`
+- [ ] 24 — Dashboard — Unified Widgets for All Services → `24-dashboard-widgets.md`
+- [ ] 25 — Real-Time Alerts — WebSocket Push Notifications → `25-realtime-alerts.md`
+- [ ] 26 — Polish — Error Boundaries, Loading States, Skeletons, and Transitions → `26-error-loading-states.md`
+- [ ] 27 — Browser Extension — Move to browser-ext/ and Update API Client → `27-browser-extension-move.md`
+- [ ] 28 — iOS App — SwiftUI Foundation, Navigation, and Shared Theme → `28-ios-app-foundation.md`
+- [ ] 29 — iOS App — Design System Components Matching Web Theme → `29-ios-design-system.md`
+- [ ] 30 — iOS App — Authentication, Onboarding, and Account Setup → `30-ios-auth-onboarding.md`
+- [ ] 31 — iOS App — API Client, tRPC Bridge, and Offline Support → `31-ios-api-client.md`
+- [ ] 32 — iOS App — Dashboard and Service Screens (DarkWatch, VoicePrint, SpamShield, etc.) → `32-ios-service-screens.md`
+- [ ] 33 — iOS App — Push Notifications, Biometrics, Voice Enrollment, Camera → `33-ios-native-features.md`
+- [ ] 34 — Android App — Jetpack Compose Foundation, Navigation, and Shared Theme → `34-android-app-foundation.md`
+- [ ] 35 — Android App — Design System Components Matching Web Theme → `35-android-design-system.md`
+- [ ] 36 — Android App — Authentication, Onboarding, and Account Setup → `36-android-auth-onboarding.md`
+- [ ] 37 — Android App — API Client, tRPC Bridge, and Offline Support → `37-android-api-client.md`
+- [ ] 38 — Android App — Dashboard and Service Screens → `38-android-service-screens.md`
+- [ ] 39 — Android App — Push Notifications, Biometrics, Voice Enrollment, Call Screening → `39-android-native-features.md`
+- [ ] 40 — Shared Mobile Assets — Icons, Colors, Typography, and Brand Guidelines → `40-shared-mobile-assets.md`
+- [ ] 41 — Cleanup — Remove Legacy packages/, services/, and server/ Directories → `41-cleanup-legacy.md`
+- [ ] 42 — Deployment — Update Docker, CI/CD, and Environment Configuration → `42-deployment-config.md`
+
+Dependencies
+- 01 depends on nothing (root task)
+- 02, 03, 04, 28, 34 depend on 01
+- 05, 06 depend on 02, 03, 04
+- 07, 08 depend on 03, 04
+- 08 also depends on 05, 06
+- 09 is independent (backend schema work)
+- 10 depends on 09
+- 11 depends on 10
+- 12, 13, 14 depend on 11
+- 15, 16, 17, 18, 19 depend on 12, 13, 14
+- 20 depends on 15, 16, 17, 18, 19
+- 21, 22 depend on 20
+- 23 depends on 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
+- 24, 25 depend on 23
+- 26 depends on 24, 25
+- 27 depends on 23
+- 28, 29, 34, 35 depend on 02
+- 29, 30, 31, 32, 33 depend on 28
+- 30, 32, 33 depend on 29
+- 32, 33 depend on 31
+- 35, 36, 37, 38, 39 depend on 34
+- 36, 38, 39 depend on 35
+- 38, 39 depend on 37
+- 40 depends on 28, 34
+- 31, 37 depend on 23
+- 41 depends on 26, 27
+- 42 depends on 41
+
+Exit criteria
+- The `web/` directory contains a fully functional SolidStart app with a beautiful, cohesive ShieldAI-branded frontend
+- All 5 service domains are accessible via tRPC routers with zero functionality loss
+- The landing page matches Lendair's visual quality with ShieldAI-specific content and auto-shifting theme
+- The browser extension lives in `browser-ext/` and communicates with the monolith
+- `iOS/ShieldAI` is a functional SwiftUI app with service dashboard, auth, and native features
+- `android/` is a functional Jetpack Compose app with service dashboard, auth, and native features
+- All three platforms (web, iOS, Android) share a consistent visual identity and color palette
+- Legacy `packages/`, `services/`, and `server/` directories are removed or archived
+- `pnpm dev` from root starts the unified app; all tests pass