start-vibing-stacks 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -146,18 +146,8 @@ async function main() {
146
146
  })),
147
147
  },
148
148
  ]);
149
- // ─── Step 6: Deploy Target ─────────────────────────────────────────────
150
- const { deploy } = await inquirer.prompt([
151
- {
152
- type: 'list',
153
- name: 'deploy',
154
- message: 'Deployment target?',
155
- choices: stackConfig.deployTargets.map((d) => ({
156
- name: `${d.icon} ${d.name}`,
157
- value: d.id,
158
- })),
159
- },
160
- ]);
149
+ // ─── Step 6: Deploy GitHub (default) ──────────────────────────────────
150
+ const deploy = 'github';
161
151
  // ─── Step 6b: MCP Servers ────────────────────────────────────────────
162
152
  const selectedMcps = FLAGS.noMcp ? [] : await selectMcpServers(stackId, database);
163
153
  // ─── Step 7: Show Summary & Confirm ────────────────────────────────────
@@ -180,7 +170,7 @@ async function main() {
180
170
  'Framework': framework,
181
171
  'Database': database,
182
172
  'Frontend': frontend,
183
- 'Deploy': deploy,
173
+ 'Deploy': '🐙 GitHub (git push)',
184
174
  'Agents': '6 universal',
185
175
  'Skills': `${stackConfig.skills.length} stack + shared`,
186
176
  'Hooks': 'stop-validator + prompt-inject',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,90 @@
1
+ # Playwright Automation — E2E Testing
2
+
3
+ **ALWAYS invoke when writing E2E tests or browser automation.**
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ tests/e2e/
9
+ ├── fixtures/ # Auth, DB cleanup, custom fixtures
10
+ ├── pages/ # Page Object Model
11
+ ├── flows/ # User journey tests
12
+ ├── api/ # API-only tests
13
+ └── playwright.config.ts
14
+ ```
15
+
16
+ ## Page Object Model
17
+
18
+ ```typescript
19
+ // tests/e2e/pages/base.page.ts
20
+ import { type Page, type Locator, expect } from '@playwright/test';
21
+
22
+ export abstract class BasePage {
23
+ protected readonly page: Page;
24
+ constructor(page: Page) { this.page = page; }
25
+
26
+ get loadingSpinner(): Locator { return this.page.getByTestId('loading-spinner'); }
27
+ get errorMessage(): Locator { return this.page.getByTestId('error-message'); }
28
+
29
+ async waitForLoad(): Promise<void> {
30
+ await this.loadingSpinner.waitFor({ state: 'hidden' });
31
+ }
32
+
33
+ async goto(path: string): Promise<void> {
34
+ await this.page.goto(path);
35
+ await this.waitForLoad();
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Auth Fixture
41
+
42
+ ```typescript
43
+ export function generateTestUser(): TestUser {
44
+ const ts = Date.now();
45
+ const rand = Math.random().toString(36).substring(7);
46
+ return {
47
+ name: `Test User ${ts}`,
48
+ email: `testuser_${ts}_${rand}@test.com`,
49
+ password: 'TestPassword123!',
50
+ };
51
+ }
52
+ ```
53
+
54
+ ## Multi-Viewport Testing
55
+
56
+ ```typescript
57
+ const viewports = [
58
+ { name: 'mobile', width: 375, height: 667 },
59
+ { name: 'tablet', width: 768, height: 1024 },
60
+ { name: 'desktop', width: 1280, height: 800 },
61
+ ] as const;
62
+
63
+ for (const viewport of viewports) {
64
+ test.describe(`Responsive - ${viewport.name}`, () => {
65
+ test.use({ viewport: { width: viewport.width, height: viewport.height } });
66
+ test('navigation adapts', async ({ page }) => { /* ... */ });
67
+ });
68
+ }
69
+ ```
70
+
71
+ ## Required data-testid
72
+
73
+ ```html
74
+ <input data-testid="email-input" />
75
+ <input data-testid="password-input" />
76
+ <button data-testid="submit-button" />
77
+ <div data-testid="error-message" />
78
+ <div data-testid="success-message" />
79
+ <div data-testid="loading-spinner" />
80
+ <nav data-testid="sidebar" />
81
+ <button data-testid="hamburger-menu" />
82
+ ```
83
+
84
+ ## FORBIDDEN
85
+
86
+ 1. **Hardcoded test data** — generate unique data with timestamps
87
+ 2. **`.skip()` or `.only()`** — never in committed code
88
+ 3. **No cleanup** — always track + clean created data
89
+ 4. **Mocked auth** — use real authentication flows
90
+ 5. **Single viewport** — test mobile + tablet + desktop
@@ -0,0 +1,67 @@
1
+ # Test Coverage — Testing Management
2
+
3
+ **ALWAYS invoke AFTER implementing any feature. Do NOT skip.**
4
+
5
+ ## Critical Rules
6
+
7
+ 1. **CLEANUP ALL TEST DATA** — fixture-based tracking
8
+ 2. **VERIFY IN DATABASE** — check DB state after UI actions
9
+ 3. **TEST ALL VIEWPORTS** — desktop, tablet, mobile minimum
10
+ 4. **REAL AUTH ONLY** — never mock authentication
11
+ 5. **UNIQUE DATA** — timestamps in emails/names
12
+ 6. **NO `.skip()`** — never in committed code
13
+
14
+ ## Files That NEED Tests
15
+
16
+ | Type | Test Expected | Required |
17
+ |------|--------------|----------|
18
+ | API Route | Unit + E2E | **YES** |
19
+ | Model/Entity | Unit | **YES** |
20
+ | Page/View | E2E flow | **YES** |
21
+ | Component (interactive) | E2E | YES |
22
+ | Hook/Service | Unit | YES |
23
+ | Utility (exported) | Unit | YES |
24
+
25
+ ## Required E2E Flows
26
+
27
+ - [ ] Registration — create user, verify in DB
28
+ - [ ] Login/Logout — auth state changes
29
+ - [ ] CRUD Create — item created, visible, in DB
30
+ - [ ] CRUD Read — item displayed correctly
31
+ - [ ] CRUD Update — changes reflected in DB
32
+ - [ ] CRUD Delete — removed from DB
33
+ - [ ] Permissions — forbidden requests blocked
34
+ - [ ] Responsive — works on all viewports
35
+
36
+ ## Stack-Specific Commands
37
+
38
+ ### PHP (PHPUnit + Pest)
39
+ ```bash
40
+ ./vendor/bin/phpunit
41
+ ./vendor/bin/pest --coverage --min=70
42
+ ```
43
+
44
+ ### Node.js (Vitest + Playwright)
45
+ ```bash
46
+ npx vitest run --coverage
47
+ npx playwright test
48
+ npx playwright test --ui
49
+ ```
50
+
51
+ ## Before Commit Checklist
52
+
53
+ - [ ] All new features have tests?
54
+ - [ ] Tests use fixtures for cleanup?
55
+ - [ ] Database state verified after UI actions?
56
+ - [ ] Tests run on all viewports?
57
+ - [ ] Coverage threshold met (≥70%)?
58
+ - [ ] No `.skip()` in tests?
59
+ - [ ] All tests passing?
60
+
61
+ ## FORBIDDEN
62
+
63
+ 1. **Skipping tests** — `test.skip()`, `.only()`
64
+ 2. **Mocking auth** — use real authentication
65
+ 3. **Fixed test users** — `test@test.com` → generate unique
66
+ 4. **No cleanup** — orphaned test data
67
+ 5. **No DB validation** — trusting UI without checking DB
@@ -0,0 +1,113 @@
1
+ # React Patterns — Modern Component Architecture
2
+
3
+ **ALWAYS invoke when writing React components, hooks, or state management.**
4
+
5
+ ## Component Patterns
6
+
7
+ ### Compound Components
8
+ ```tsx
9
+ const TabsContext = createContext<TabsContextValue | null>(null);
10
+
11
+ function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
12
+ const [activeTab, setActiveTab] = useState(defaultTab);
13
+ return (
14
+ <TabsContext.Provider value={{ activeTab, setActiveTab }}>
15
+ <div className="tabs">{children}</div>
16
+ </TabsContext.Provider>
17
+ );
18
+ }
19
+ // + TabList, Tab, TabPanel components using useContext(TabsContext)
20
+ ```
21
+
22
+ ### Generic List Component
23
+ ```tsx
24
+ interface ListProps<T> {
25
+ items: T[];
26
+ renderItem: (item: T) => ReactNode;
27
+ keyExtractor: (item: T) => string;
28
+ }
29
+
30
+ function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
31
+ return <ul>{items.map(item => <li key={keyExtractor(item)}>{renderItem(item)}</li>)}</ul>;
32
+ }
33
+ ```
34
+
35
+ ## Custom Hooks
36
+
37
+ ```tsx
38
+ // useLocalStorage
39
+ function useLocalStorage<T>(key: string, initial: T) {
40
+ const [value, setValue] = useState<T>(() => {
41
+ if (typeof window === 'undefined') return initial;
42
+ try { return JSON.parse(window.localStorage.getItem(key) ?? '') } catch { return initial }
43
+ });
44
+ const set = useCallback((v: T | ((val: T) => T)) => {
45
+ setValue(prev => {
46
+ const next = v instanceof Function ? v(prev) : v;
47
+ window.localStorage.setItem(key, JSON.stringify(next));
48
+ return next;
49
+ });
50
+ }, [key]);
51
+ return [value, set] as const;
52
+ }
53
+
54
+ // useDebounce
55
+ function useDebounce<T>(value: T, delay: number): T {
56
+ const [debounced, setDebounced] = useState(value);
57
+ useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]);
58
+ return debounced;
59
+ }
60
+ ```
61
+
62
+ ## State Management
63
+
64
+ ### useReducer for Complex State
65
+ ```tsx
66
+ type Action =
67
+ | { type: 'FETCH_START' }
68
+ | { type: 'FETCH_SUCCESS'; payload: Item[] }
69
+ | { type: 'FETCH_ERROR'; payload: string };
70
+
71
+ function reducer(state: State, action: Action): State {
72
+ switch (action.type) {
73
+ case 'FETCH_START': return { ...state, loading: true, error: null };
74
+ case 'FETCH_SUCCESS': return { ...state, loading: false, items: action.payload };
75
+ case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload };
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Context (avoid prop drilling)
81
+ ```tsx
82
+ const AppContext = createContext<AppContextValue | undefined>(undefined);
83
+ export function useApp() {
84
+ const ctx = useContext(AppContext);
85
+ if (!ctx) throw new Error('useApp must be used within AppProvider');
86
+ return ctx;
87
+ }
88
+ ```
89
+
90
+ ## Performance
91
+
92
+ ```tsx
93
+ // memo — prevent re-renders
94
+ const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) { ... });
95
+
96
+ // useMemo — expensive calculations
97
+ const processed = useMemo(() => data.map(d => expensiveCalc(d)), [data]);
98
+
99
+ // useCallback — stable refs
100
+ const handleClick = useCallback(() => setCount(c => c + 1), []);
101
+
102
+ // Code splitting
103
+ const Heavy = lazy(() => import('./HeavyComponent'));
104
+ <Suspense fallback={<Loading />}><Heavy /></Suspense>
105
+ ```
106
+
107
+ ## FORBIDDEN
108
+
109
+ 1. **Class components** — function components only
110
+ 2. **Prop drilling** — use context or composition
111
+ 3. **Inline objects/functions in JSX** — causes re-renders
112
+ 4. **useEffect for derived state** — use useMemo
113
+ 5. **Mutating state directly** — always setState/dispatch
@@ -0,0 +1,94 @@
1
+ # Tailwind Patterns — Utility-First CSS
2
+
3
+ **ALWAYS invoke when writing Tailwind CSS classes.**
4
+
5
+ ## Mobile-First Breakpoints
6
+
7
+ ```
8
+ sm: 640px → md: 768px → lg: 1024px → xl: 1280px → 2xl: 1536px
9
+ ```
10
+
11
+ ```tsx
12
+ <div className="w-full md:w-1/2 lg:w-1/3 xl:w-1/4">
13
+ {/* Mobile: full → Tablet: half → Desktop: third → Large: quarter */}
14
+ </div>
15
+ ```
16
+
17
+ ## Layout Patterns
18
+
19
+ ### Flexbox
20
+ ```tsx
21
+ // Center
22
+ <div className="flex items-center justify-center h-screen" />
23
+
24
+ // Responsive direction
25
+ <div className="flex flex-col md:flex-row gap-4" />
26
+ ```
27
+
28
+ ### Grid
29
+ ```tsx
30
+ // Responsive grid
31
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" />
32
+
33
+ // Auto-fit
34
+ <div className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-4" />
35
+ ```
36
+
37
+ ## Dark Mode
38
+
39
+ ```tsx
40
+ <div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
41
+ <p className="text-gray-600 dark:text-gray-300">Adapts to theme</p>
42
+ </div>
43
+ ```
44
+
45
+ ## Component Patterns
46
+
47
+ ### Card
48
+ ```tsx
49
+ <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
50
+ ```
51
+
52
+ ### Button
53
+ ```tsx
54
+ <button className="bg-primary text-primary-foreground px-4 py-2 rounded-md font-medium hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
55
+ ```
56
+
57
+ ### Input
58
+ ```tsx
59
+ <input className="w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-md text-gray-900 dark:text-white placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" />
60
+ ```
61
+
62
+ ## Animations
63
+
64
+ ```tsx
65
+ <Loader className="h-5 w-5 animate-spin" /> // Spinner
66
+ <div className="h-4 w-24 bg-gray-200 animate-pulse" /> // Skeleton
67
+ ```
68
+
69
+ ### Custom (tailwind.config.js)
70
+ ```js
71
+ animation: {
72
+ 'fade-in': 'fadeIn 0.3s ease-in-out',
73
+ 'slide-up': 'slideUp 0.3s ease-out',
74
+ },
75
+ keyframes: {
76
+ fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' } },
77
+ slideUp: { '0%': { transform: 'translateY(10px)', opacity: '0' }, '100%': { transform: 'translateY(0)', opacity: '1' } },
78
+ }
79
+ ```
80
+
81
+ ## Show/Hide Responsive
82
+
83
+ ```tsx
84
+ <div className="hidden lg:block">Desktop only</div>
85
+ <div className="block lg:hidden">Mobile only</div>
86
+ <span className="sr-only">Screen reader only</span>
87
+ ```
88
+
89
+ ## FORBIDDEN
90
+
91
+ 1. **`!important`** — fix specificity properly
92
+ 2. **Arbitrary values when utilities exist** — use the scale
93
+ 3. **Mixing raw CSS with Tailwind** — stick to utilities
94
+ 4. **Inconsistent spacing** — follow the 4px scale (p-1=4px, p-2=8px, p-4=16px)
@@ -0,0 +1,90 @@
1
+ # TypeScript Strict — Type Safety Patterns
2
+
3
+ **ALWAYS invoke when writing .ts/.tsx files.**
4
+
5
+ ## Required tsconfig.json
6
+
7
+ ```json
8
+ {
9
+ "compilerOptions": {
10
+ "strict": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "noImplicitAny": true,
13
+ "strictNullChecks": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "exactOptionalPropertyTypes": true
17
+ }
18
+ }
19
+ ```
20
+
21
+ ## Index Access
22
+
23
+ ```typescript
24
+ // WRONG
25
+ const port = process.env.PORT;
26
+
27
+ // CORRECT — bracket notation
28
+ const port = process.env['PORT'];
29
+ const host = process.env['HOST'] ?? 'localhost';
30
+ ```
31
+
32
+ ## Null Handling
33
+
34
+ ```typescript
35
+ // Optional chaining + nullish coalescing
36
+ const avatar = user.profile?.avatar ?? '/default.png';
37
+
38
+ // WRONG — || treats 0, '', false as falsy
39
+ const count = input || 10;
40
+ // CORRECT — ?? only null/undefined
41
+ const count = input ?? 10;
42
+ ```
43
+
44
+ ## Type Guards
45
+
46
+ ```typescript
47
+ function isUser(value: unknown): value is User {
48
+ return typeof value === 'object' && value !== null && 'id' in value && 'email' in value;
49
+ }
50
+
51
+ // Assertion function
52
+ function assertDefined<T>(value: T | undefined | null, msg: string): asserts value is T {
53
+ if (value == null) throw new Error(msg);
54
+ }
55
+ ```
56
+
57
+ ## Const Assertions
58
+
59
+ ```typescript
60
+ // Exact types (not widened)
61
+ const endpoints = {
62
+ users: '/api/users',
63
+ posts: '/api/posts',
64
+ } as const;
65
+
66
+ // Discriminated unions
67
+ type Result<T> =
68
+ | { success: true; data: T }
69
+ | { success: false; error: string };
70
+ ```
71
+
72
+ ## Generic Patterns
73
+
74
+ ```typescript
75
+ // Constrained
76
+ function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
77
+ return items.find(item => item.id === id);
78
+ }
79
+
80
+ // RequireFields utility
81
+ type RequireFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
82
+ ```
83
+
84
+ ## FORBIDDEN
85
+
86
+ 1. **`any`** — use `unknown` instead
87
+ 2. **Non-null assertion without certainty** — check first
88
+ 3. **Type assertion to hide errors** — fix the actual issue
89
+ 4. **`@ts-ignore`** — use `@ts-expect-error` with comment
90
+ 5. **Ignoring strict errors** — always fix properly
@@ -1,13 +1,29 @@
1
1
  {
2
2
  "id": "nodejs",
3
3
  "name": "Node.js / TypeScript",
4
- "icon": "📦",
4
+ "icon": "\ud83d\udce6",
5
5
  "runtime": "Bun / Node.js 20+",
6
6
  "minVersion": "20.0.0",
7
7
  "packageManager": "bun|npm|pnpm",
8
- "extensions": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
9
- "testExtensions": ["*.test.ts", "*.spec.ts", "*.test.tsx"],
10
- "detectFiles": ["package.json", "tsconfig.json", "bun.lockb", "next.config.js"],
8
+ "extensions": [
9
+ ".ts",
10
+ ".tsx",
11
+ ".js",
12
+ ".jsx",
13
+ ".mjs",
14
+ ".cjs"
15
+ ],
16
+ "testExtensions": [
17
+ "*.test.ts",
18
+ "*.spec.ts",
19
+ "*.test.tsx"
20
+ ],
21
+ "detectFiles": [
22
+ "package.json",
23
+ "tsconfig.json",
24
+ "bun.lockb",
25
+ "next.config.js"
26
+ ],
11
27
  "commands": {
12
28
  "test": "bun run test",
13
29
  "lint": "bun run lint",
@@ -17,41 +33,139 @@
17
33
  "typecheck": "bun run typecheck"
18
34
  },
19
35
  "qualityGates": [
20
- { "name": "TypeCheck", "command": "bun run typecheck", "required": true, "order": 1 },
21
- { "name": "Lint", "command": "bun run lint", "required": true, "order": 2 },
22
- { "name": "Tests", "command": "bun run test", "required": true, "order": 3 },
23
- { "name": "Build", "command": "bun run build", "required": true, "order": 4 }
36
+ {
37
+ "name": "TypeCheck",
38
+ "command": "bun run typecheck",
39
+ "required": true,
40
+ "order": 1
41
+ },
42
+ {
43
+ "name": "Lint",
44
+ "command": "bun run lint",
45
+ "required": true,
46
+ "order": 2
47
+ },
48
+ {
49
+ "name": "Tests",
50
+ "command": "bun run test",
51
+ "required": true,
52
+ "order": 3
53
+ },
54
+ {
55
+ "name": "Build",
56
+ "command": "bun run build",
57
+ "required": true,
58
+ "order": 4
59
+ }
24
60
  ],
25
61
  "frameworks": [
26
- { "id": "nextjs", "name": "Next.js (App Router)", "icon": "▲", "detectFiles": ["next.config.js", "next.config.ts", "next.config.mjs"] },
27
- { "id": "nuxt", "name": "Nuxt", "icon": "💚", "detectFiles": ["nuxt.config.ts"] },
28
- { "id": "astro", "name": "Astro", "icon": "🚀", "detectFiles": ["astro.config.mjs"] },
29
- { "id": "express", "name": "Express", "icon": "" },
30
- { "id": "fastify", "name": "Fastify", "icon": "🏎️" },
31
- { "id": "vanilla", "name": "Vanilla Node.js", "icon": "📄" }
62
+ {
63
+ "id": "nextjs",
64
+ "name": "Next.js (App Router)",
65
+ "icon": "\u25b2",
66
+ "detectFiles": [
67
+ "next.config.js",
68
+ "next.config.ts",
69
+ "next.config.mjs"
70
+ ]
71
+ },
72
+ {
73
+ "id": "nuxt",
74
+ "name": "Nuxt",
75
+ "icon": "\ud83d\udc9a",
76
+ "detectFiles": [
77
+ "nuxt.config.ts"
78
+ ]
79
+ },
80
+ {
81
+ "id": "astro",
82
+ "name": "Astro",
83
+ "icon": "\ud83d\ude80",
84
+ "detectFiles": [
85
+ "astro.config.mjs"
86
+ ]
87
+ },
88
+ {
89
+ "id": "express",
90
+ "name": "Express",
91
+ "icon": "\u26a1"
92
+ },
93
+ {
94
+ "id": "fastify",
95
+ "name": "Fastify",
96
+ "icon": "\ud83c\udfce\ufe0f"
97
+ },
98
+ {
99
+ "id": "vanilla",
100
+ "name": "Vanilla Node.js",
101
+ "icon": "\ud83d\udcc4"
102
+ }
32
103
  ],
33
104
  "databases": [
34
- { "id": "mongodb", "name": "MongoDB", "icon": "🍃" },
35
- { "id": "postgresql", "name": "PostgreSQL", "icon": "🐘" },
36
- { "id": "mysql", "name": "MySQL / MariaDB", "icon": "🐬" },
37
- { "id": "sqlite", "name": "SQLite (Turso / libSQL)", "icon": "📁" },
38
- { "id": "redis", "name": "Redis (Upstash)", "icon": "🔴" },
39
- { "id": "none", "name": "None", "icon": "❌" }
105
+ {
106
+ "id": "mongodb",
107
+ "name": "MongoDB",
108
+ "icon": "\ud83c\udf43"
109
+ },
110
+ {
111
+ "id": "postgresql",
112
+ "name": "PostgreSQL",
113
+ "icon": "\ud83d\udc18"
114
+ },
115
+ {
116
+ "id": "mysql",
117
+ "name": "MySQL / MariaDB",
118
+ "icon": "\ud83d\udc2c"
119
+ },
120
+ {
121
+ "id": "sqlite",
122
+ "name": "SQLite (Turso / libSQL)",
123
+ "icon": "\ud83d\udcc1"
124
+ },
125
+ {
126
+ "id": "redis",
127
+ "name": "Redis (Upstash)",
128
+ "icon": "\ud83d\udd34"
129
+ },
130
+ {
131
+ "id": "none",
132
+ "name": "None",
133
+ "icon": "\u274c"
134
+ }
40
135
  ],
41
136
  "frontendOptions": [
42
- { "id": "react-tailwind", "name": "React 19+ / TailwindCSS 4+", "icon": "⚛️" },
43
- { "id": "vue", "name": "Vue.js / Nuxt", "icon": "💚" },
44
- { "id": "svelte", "name": "Svelte / SvelteKit", "icon": "🔥" },
45
- { "id": "shadcn", "name": "shadcn/ui + Tailwind", "icon": "🎨" },
46
- { "id": "none", "name": "API only — no frontend", "icon": "❌" }
137
+ {
138
+ "id": "react-tailwind",
139
+ "name": "React 19+ / TailwindCSS 4+",
140
+ "icon": "\u269b\ufe0f"
141
+ },
142
+ {
143
+ "id": "vue",
144
+ "name": "Vue.js / Nuxt",
145
+ "icon": "\ud83d\udc9a"
146
+ },
147
+ {
148
+ "id": "svelte",
149
+ "name": "Svelte / SvelteKit",
150
+ "icon": "\ud83d\udd25"
151
+ },
152
+ {
153
+ "id": "shadcn",
154
+ "name": "shadcn/ui + Tailwind",
155
+ "icon": "\ud83c\udfa8"
156
+ },
157
+ {
158
+ "id": "none",
159
+ "name": "API only \u2014 no frontend",
160
+ "icon": "\u274c"
161
+ }
47
162
  ],
48
163
  "deployTargets": [
49
- { "id": "vercel", "name": "Vercel", "icon": "▲" },
50
- { "id": "docker", "name": "Docker", "icon": "🐳" },
51
- { "id": "vps-ssh", "name": "VPS (SSH)", "icon": "☁️" },
52
- { "id": "railway", "name": "Railway", "icon": "🚂" },
53
- { "id": "fly", "name": "Fly.io", "icon": "✈️" },
54
- { "id": "netlify", "name": "Netlify", "icon": "🌐" }
164
+ {
165
+ "id": "github",
166
+ "name": "GitHub (git push)",
167
+ "icon": "\ud83d\udc19"
168
+ }
55
169
  ],
56
170
  "skills": [
57
171
  "typescript-strict",
@@ -150,34 +150,9 @@
150
150
  ],
151
151
  "deployTargets": [
152
152
  {
153
- "id": "shared-hosting",
154
- "name": "Shared Hosting (FTP)",
155
- "icon": "\ud83c\udf10"
156
- },
157
- {
158
- "id": "docker",
159
- "name": "Docker",
160
- "icon": "\ud83d\udc33"
161
- },
162
- {
163
- "id": "vps-ssh",
164
- "name": "VPS (SSH)",
165
- "icon": "\u2601\ufe0f"
166
- },
167
- {
168
- "id": "cpanel",
169
- "name": "cPanel",
170
- "icon": "\ud83d\udce6"
171
- },
172
- {
173
- "id": "forge",
174
- "name": "Laravel Forge",
175
- "icon": "\ud83d\udd28"
176
- },
177
- {
178
- "id": "vapor",
179
- "name": "Laravel Vapor (AWS)",
180
- "icon": "\u2601\ufe0f"
153
+ "id": "github",
154
+ "name": "GitHub (git push)",
155
+ "icon": "\ud83d\udc19"
181
156
  }
182
157
  ],
183
158
  "skills": [