| name | new-lit-pwa |
| description | Scaffold a new Vite + LitElement + IDB PWA app hosted on Netlify with a private GitHub repo. Use when the user wants to create a new personal web app following this stack. |
| disable-model-invocation | true |
| user-invocable | true |
You are scaffolding a new personal PWA app. These apps follow a strict set of conventions:
- Vite build tool
- LitElement web components (TypeScript)
- Data stored locally in IndexedDB via the
idb library
- PWA wrapper via
vite-plugin-pwa (installable on phone)
- Hosted on Netlify
- A "Export backup" button that downloads all data as a JSON file
- Private GitHub repo created with the
gh CLI
- Dark theme with CSS custom properties
Step 1: Gather information
If $ARGUMENTS is not empty, parse it as: <app-name> "<Display Name>" "<Short Name>" "<description>" "<theme-color>"
Otherwise, ask the user for:
- app-name — kebab-case, used for directory name, package name, and GitHub repo name
- Display Name — full title shown in browser tab and manifest (e.g. "Train Controller")
- Short Name — short name for home screen icon label (e.g. "Trains")
- Description — one sentence describing what the app does
- Theme color — hex color for PWA theme/background (default:
#1a1a2e)
- Data model — what are the main things this app stores? Describe the fields/types briefly.
Confirm all values with the user before proceeding.
Step 2: Create project
Create the project at ~/git/<app-name>/.
package.json
{
"name": "<app-name>",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"idb": "^8.0.0",
"lit": "^3.0.0"
},
"devDependencies": {
"typescript": "^5.4.0",
"vite": "^5.0.0",
"vite-plugin-pwa": "^0.20.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"experimentalDecorators": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
vite.config.ts
Fill in Display Name, Short Name, Description, and theme color from user input.
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['icons/*.png'],
manifest: {
name: '<Display Name>',
short_name: '<Short Name>',
description: '<Description>',
theme_color: '<theme-color>',
background_color: '<theme-color>',
display: 'standalone',
orientation: 'portrait',
start_url: '/',
icons: [
{ src: 'icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: 'icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' },
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
runtimeCaching: [],
},
}),
],
});
netlify.toml
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
index.html
Fill in Display Name, Short Name, and theme color.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="<Short Name>" />
<meta name="theme-color" content="<theme-color>" />
<title><Display Name></title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="stylesheet" href="/src/styles/tokens.css" />
<script type="module" src="/src/main.ts"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
.gitignore
node_modules
dist
.netlify
src/main.ts
import './components/app-root.js';
src/styles/tokens.css
Use the user's theme color as --color-bg and --color-surface. Derive a slightly lighter shade for --color-surface-raised. Use a contrasting accent color. Include the full design token set from the reference app:
- Color tokens:
--color-bg, --color-surface, --color-surface-raised, --color-accent, --color-accent-dim, --color-text, --color-text-muted, --color-border, --color-success, --color-warning, --color-danger
- Spacing scale:
--space-xs (4px) through --space-xl (32px)
- Border radius:
--radius-sm (6px), --radius-md (12px), --radius-lg (20px)
- Typography: system font stack, size scale from 0.85rem to 1.4rem
- Safe area vars using
env(safe-area-inset-*) for mobile notch support
--tap-target: 44px minimum touch target
src/types.ts
Define TypeScript interfaces based on the user's described data model. Each record should have:
id: string (UUID)
createdAt: string (ISO date)
updatedAt: string (ISO date)
- All user-specified fields with appropriate types
src/services/db.ts
IDB service using the idb library. Database name: <app-name>. Version 1.
- One object store per main entity (keyPath:
id)
- Add indexes for any fields the user will search/filter on
- Functions:
getAll<Entity>(), save<Entity>(item), get<Entity>(id), delete<Entity>(id)
exportData() — serializes all stores to JSON and triggers a file download via a temporary <a> element
importData(json) — parses JSON and bulk-writes all records
src/components/app-root.ts
LitElement root component. Should include:
- A header with the app title and an "Export" button that calls
exportData()
- A FAB or primary button to add a new item
- The main list/grid view
- Modal pattern for add/edit forms (show/hide based on state property)
- Load all data on
connectedCallback
Additional components
Generate sensible LitElement components for the data model — at minimum:
- A list component showing all items
- A card/row component for a single item
- A form component (modal) for add/edit
Keep components focused. Use @property() for public props, @state() for internal state. Emit custom events (this.dispatchEvent(new CustomEvent(...))) to communicate up to the root.
All components should:
- Use the CSS tokens from
tokens.css via :host { } and standard CSS custom property references
- Have
static styles defined with css\...`(imported fromlit`)
- Be mobile-first, touch-friendly (44px tap targets)
Step 3: Create placeholder icons
In public/icons/, create placeholder files named icon-192.png and icon-512.png. Also create placeholder favicon.ico, favicon-16x16.png, favicon-32x32.png, and apple-touch-icon.png in public/.
Tell the user: "You'll need to replace the placeholder icons in public/ with real ones before deploying."
Step 4: Install dependencies
Run npm install in the project directory.
Step 5: Initialize git and create GitHub repo
Run these commands in order:
cd ~/git/<app-name>
git init
git add .
git commit -m "Initial scaffold"
gh repo create <app-name> --private --source=. --remote=origin --push
Step 6: Final instructions for the user
Tell the user:
- Run locally:
cd ~/git/<app-name> && npm run dev
- Deploy to Netlify: Go to app.netlify.com → "Add new site" → "Import an existing project" → connect the GitHub repo
<app-name>. Build command is npm run build, publish directory is dist.
- Replace icons in
public/ with real PNG files at the correct sizes.
- Customize the data model in
src/types.ts and src/services/db.ts if the generated schema needs adjusting.