rmnr
DocsTourSlicesBest PracticeAgentsInstall

Command Palette

Search for a command to run...

New

Slice · AI prompt

AppShell — Desktop + Mobile OS Shell

Manifest-driven macOS-style window manager + iOS-style mobile surface in one slice.

Resource detail View source
Copy this prompt into Claude / Codex / Cursor
appshell.prompt.mdmarkdown
# 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.