Slice · AI prompt
Manifest-driven macOS-style window manager + iOS-style mobile surface in one slice.
# Install `appshell` — AppShell — Desktop + Mobile OS Shell
> Manifest-driven macOS-style window manager + iOS-style mobile surface in one slice.
📚 Knowledge base : https://resource.rahmanef.com/llms.txt
📦 Slice detail : https://resource.rahmanef.com/slices/appshell
🧠 JSON catalog : https://resource.rahmanef.com/api/knowledge?slice=appshell
🔗 Source : https://github.com/rahmanef63/resource-site/tree/main/frontend/slices/appshell
## 1. Install
```bash
npx rahman-resources add appshell
# alias: npx rr add appshell
```
The CLI copies `frontend/slices/appshell/` into your project + augments `.env.example` + installs npm deps automatically. Run it from your project root.
## 2. What it ships
- npm: `lucide-react`, `class-variance-authority`, `clsx`, `tailwind-merge`, `vaul`
- shadcn primitives: `button`, `tooltip`, `scroll-area`, `sheet`, `drawer`, `dialog`, `alert-dialog`, `dropdown-menu`
## 3. Wire it up
Stack required: Next 16 (App Router) + React 19 + Tailwind 4 + shadcn/ui. The slice is self-contained — it imports only @/components/ui/* + @/lib/utils (cn); everything project-specific arrives via the manifest. Follow ALL steps; the ⚠ ones are where installs break.
STEP 1 — Install. `npx rr add appshell` (alias `npx rahman-resources add appshell`). It copies to your slices dir. Ensure `@/features/appshell` resolves in tsconfig paths (point it at that dir), and that Tailwind's content globs SCAN the slice folder (else the shell renders unstyled).
STEP 2 — shadcn + npm deps. Add any missing shadcn primitives: `npx shadcn@latest add button tooltip scroll-area sheet drawer dialog alert-dialog dropdown-menu`. npm: lucide-react class-variance-authority clsx tailwind-merge vaul.
STEP 3 — ⚠ Theme. Import the slice's tokens ONCE in the root layout: `import "@/features/appshell/appshell.css"`. These are the glass/dock/window/wallpaper CSS variables the shell needs — they are NOT shadcn defaults, so skipping this = an unstyled, broken-looking shell. It pairs with your shadcn tokens (--background etc.). Dark mode = toggle the `.dark` class on <html> (appshell.css ships light + dark).
STEP 4 — Mount full-bleed. Render <AppShell manifest={manifest} /> from a CLIENT component that owns one full viewport (the page is h-dvh w-screen / the root). AppShell auto-picks the macOS desktop on wide viewports and the iOS surface on narrow — you write nothing extra for mobile.
STEP 5 — Build the ShellManifest:
• brand: { name, logo (string or ReactNode), idleAppName?, wallpaper?: "aurora"|"dusk"|"mist"|"noir" }.
• apps: AppDescriptor[] — { id, title, icon (a lucide-react icon component), gradient (a CSS gradient string for the glossy icon), load: async () => ({ default: YourAppComponent }), slug?, defaultSize?: {w,h}, multi?: true (spawn a new window per open, e.g. a file manager), noDock?: true }. Your app component receives props { payload }.
• features: the fastest path is `features: DEFAULT_FEATURES` — the bundled default system-feature set (all five, generic + brand-free) exported from "@/features/appshell". Or import individually and list only what you want: searchFeature (⌘K Spotlight), inspectorFeature (⌘I AI/context panel), notificationsFeature (toasts + iOS dynamic island), controlCenterFeature (iOS control center), widgetsFeature (iOS Today widgets). The surfaces are slot-driven, so spreading/trimming DEFAULT_FEATURES just mounts/omits a feature — `features: [...DEFAULT_FEATURES.filter(f => f.id !== "widgets")]`.
• capabilities: ShellCapabilities — your data/auth/AI injection seam. useAppearance() and useCpuPercent() are REQUIRED; useSearch/useSystemStats/useChat/useServerToggle are optional (defaults degrade gracefully). ⚠ CRITICAL: every capability hook MUST return a REFERENTIALLY STABLE value — a module-level const, or useMemo/useCallback. Returning a fresh object/closure each render makes Spotlight's search effect re-fire forever ("Maximum update depth exceeded"). e.g. define APPEARANCE once at module scope and `useAppearance: () => APPEARANCE`.
• persistKey?: localStorage namespace for the saved window layout (default "appshell:layout").
• routing?: defaults TRUE — it mirrors the focused app to the URL via the History API (window.history, NOT router.push). ⚠ If true you MUST add a catch-all route `app/[[...slug]]/page.tsx` that renders the mount AND calls notFound() for reserved paths (slug[0] === "_next"), or missing chunks return wrong-MIME 200s. SIMPLEST first install: set `routing: false` to skip the catch-all entirely.
Extending: add an app = one manifest entry; add a shell feature = a new defineFeature({id, slots}) listed in features[]. No surface edits ever (open/closed). exampleCode ships BOTH variants: Variant A = routing:false mount in app/page.tsx (simplest); Variant B = catch-all app/[[...slug]]/page.tsx with routing on + app slugs for addressable, deep-linkable URLs (the catch-all MUST notFound() "_next").
## Rules of engagement
- shadcn-only UI primitives. No raw `<button>` / `<dialog>` / native date or file inputs.
- 200-line hard cap per source file (extract neighbours when over).
- All Convex queries hit an index (`.withIndex(...)`); never bare `.collect()`.
- Public mutations/queries declare `args:` validators + authz.
- Full ruleset: https://resource.rahmanef.com/best-practice
The agent will fetch /llms.txt for the full ruleset and use /api/knowledge for the JSON catalog.