@kennethsolomon/shipkit 3.2.0 → 3.3.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.
@@ -0,0 +1,287 @@
1
+ # React + Vite + Tailwind — Stack Reference
2
+
3
+ ## Scaffold
4
+
5
+ ```bash
6
+ npm create vite@latest {project-name} -- --template react-ts
7
+ cd {project-name}
8
+ npm install
9
+ npm install -D tailwindcss @tailwindcss/vite
10
+ npm install react-router-dom
11
+ ```
12
+
13
+ ## Directory Structure
14
+
15
+ ```
16
+ {project-name}/
17
+ ├── src/
18
+ │ ├── main.tsx ← entry point (router setup)
19
+ │ ├── App.tsx ← route definitions
20
+ │ ├── index.css ← Tailwind directives + custom CSS
21
+ │ ├── pages/
22
+ │ │ ├── Landing.tsx ← landing page
23
+ │ │ ├── Dashboard.tsx ← dashboard
24
+ │ │ ├── {Feature1}.tsx
25
+ │ │ ├── {Feature2}.tsx
26
+ │ │ └── Settings.tsx
27
+ │ ├── components/
28
+ │ │ ├── landing/
29
+ │ │ │ ├── Navbar.tsx
30
+ │ │ │ ├── Hero.tsx
31
+ │ │ │ ├── Features.tsx
32
+ │ │ │ ├── HowItWorks.tsx
33
+ │ │ │ ├── Pricing.tsx
34
+ │ │ │ ├── Testimonials.tsx
35
+ │ │ │ ├── WaitlistForm.tsx
36
+ │ │ │ └── Footer.tsx
37
+ │ │ ├── app/
38
+ │ │ │ ├── Sidebar.tsx
39
+ │ │ │ ├── AppLayout.tsx
40
+ │ │ │ ├── DashboardCards.tsx
41
+ │ │ │ └── {feature components}
42
+ │ │ └── ui/
43
+ │ │ ├── Button.tsx
44
+ │ │ ├── Input.tsx
45
+ │ │ ├── Card.tsx
46
+ │ │ ├── Modal.tsx
47
+ │ │ └── Toast.tsx
48
+ │ ├── data/
49
+ │ │ └── mock.ts ← all fake data centralized
50
+ │ └── lib/
51
+ │ └── utils.ts ← shared helpers (cn, etc.)
52
+ ├── public/
53
+ │ └── {static assets}
54
+ ├── index.html
55
+ ├── vite.config.ts
56
+ ├── tailwind.config.ts
57
+ └── package.json
58
+ ```
59
+
60
+ ## Vite Config
61
+
62
+ `vite.config.ts`:
63
+
64
+ ```ts
65
+ import { defineConfig } from 'vite'
66
+ import react from '@vitejs/plugin-react'
67
+ import tailwindcss from '@tailwindcss/vite'
68
+
69
+ export default defineConfig({
70
+ plugins: [react(), tailwindcss()],
71
+ })
72
+ ```
73
+
74
+ ## Tailwind Config
75
+
76
+ `tailwind.config.ts`:
77
+
78
+ ```ts
79
+ export default {
80
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
81
+ theme: {
82
+ extend: {
83
+ colors: {
84
+ bg: 'var(--color-bg)',
85
+ fg: 'var(--color-fg)',
86
+ accent: 'var(--color-accent)',
87
+ muted: 'var(--color-muted)',
88
+ },
89
+ fontFamily: {
90
+ display: ['var(--font-display)', 'serif'],
91
+ body: ['var(--font-body)', 'sans-serif'],
92
+ },
93
+ },
94
+ },
95
+ }
96
+ ```
97
+
98
+ CSS variables and font imports in `src/index.css`:
99
+
100
+ ```css
101
+ @import url('https://fonts.googleapis.com/css2?family={DisplayFont}:wght@400;600;700;800&family={BodyFont}:wght@400;500;600&display=swap');
102
+
103
+ @tailwind base;
104
+ @tailwind components;
105
+ @tailwind utilities;
106
+
107
+ :root {
108
+ --color-bg: #xxxxxx;
109
+ --color-fg: #xxxxxx;
110
+ --color-accent: #xxxxxx;
111
+ --color-muted: #xxxxxx;
112
+ --font-display: '{DisplayFont}', serif;
113
+ --font-body: '{BodyFont}', sans-serif;
114
+ }
115
+
116
+ body {
117
+ font-family: var(--font-body);
118
+ }
119
+ ```
120
+
121
+ ## Router Setup
122
+
123
+ `src/App.tsx`:
124
+
125
+ ```tsx
126
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom'
127
+ import Landing from './pages/Landing'
128
+ import AppLayout from './components/app/AppLayout'
129
+ import Dashboard from './pages/Dashboard'
130
+ import Feature1 from './pages/{Feature1}'
131
+ import Feature2 from './pages/{Feature2}'
132
+ import Settings from './pages/Settings'
133
+
134
+ const router = createBrowserRouter([
135
+ { path: '/', element: <Landing /> },
136
+ {
137
+ element: <AppLayout />,
138
+ children: [
139
+ { path: '/dashboard', element: <Dashboard /> },
140
+ { path: '/{feature-1}', element: <Feature1 /> },
141
+ { path: '/{feature-2}', element: <Feature2 /> },
142
+ { path: '/settings', element: <Settings /> },
143
+ ],
144
+ },
145
+ ])
146
+
147
+ export default function App() {
148
+ return <RouterProvider router={router} />
149
+ }
150
+ ```
151
+
152
+ ## App Layout
153
+
154
+ `src/components/app/AppLayout.tsx`:
155
+
156
+ ```tsx
157
+ import { Outlet } from 'react-router-dom'
158
+ import Sidebar from './Sidebar'
159
+
160
+ export default function AppLayout() {
161
+ return (
162
+ <div className="flex min-h-screen bg-bg text-fg">
163
+ <Sidebar />
164
+ <main className="flex-1 p-6 lg:p-8">
165
+ <Outlet />
166
+ </main>
167
+ </div>
168
+ )
169
+ }
170
+ ```
171
+
172
+ ## Waitlist — Formspree Integration
173
+
174
+ Since React + Vite has no backend, use Formspree for email collection.
175
+
176
+ `src/components/landing/WaitlistForm.tsx`:
177
+
178
+ ```tsx
179
+ import { useState, FormEvent } from 'react'
180
+
181
+ // Replace YOUR_FORM_ID with your Formspree form ID
182
+ // Create one free at https://formspree.io
183
+ const FORMSPREE_URL = 'https://formspree.io/f/YOUR_FORM_ID'
184
+
185
+ export default function WaitlistForm() {
186
+ const [email, setEmail] = useState('')
187
+ const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
188
+ const [message, setMessage] = useState('')
189
+
190
+ async function handleSubmit(e: FormEvent) {
191
+ e.preventDefault()
192
+ setStatus('loading')
193
+
194
+ try {
195
+ const res = await fetch(FORMSPREE_URL, {
196
+ method: 'POST',
197
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
198
+ body: JSON.stringify({ email }),
199
+ })
200
+
201
+ if (res.ok) {
202
+ setStatus('success')
203
+ setMessage("You're on the list! We'll notify you when we launch.")
204
+ } else {
205
+ throw new Error()
206
+ }
207
+ } catch {
208
+ setStatus('error')
209
+ setMessage('Something went wrong. Please try again.')
210
+ }
211
+ }
212
+
213
+ if (status === 'success') {
214
+ return <p className="text-green-600 font-medium text-lg">{message}</p>
215
+ }
216
+
217
+ return (
218
+ <form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 max-w-md">
219
+ <input
220
+ type="email"
221
+ value={email}
222
+ onChange={e => setEmail(e.target.value)}
223
+ placeholder="you@example.com"
224
+ required
225
+ className="flex-1 px-4 py-3 rounded-lg border border-muted/30 bg-bg focus:ring-2 focus:ring-accent focus:outline-none"
226
+ />
227
+ <button
228
+ type="submit"
229
+ disabled={status === 'loading'}
230
+ className="px-6 py-3 bg-accent text-white rounded-xl font-medium hover:opacity-90 transition-all disabled:opacity-50"
231
+ >
232
+ {status === 'loading' ? 'Joining...' : 'Join Waitlist'}
233
+ </button>
234
+ {status === 'error' && <p className="text-red-500 text-sm">{message}</p>}
235
+ </form>
236
+ )
237
+ }
238
+ ```
239
+
240
+ ## Mock Data
241
+
242
+ Centralize all fake data in `src/data/mock.ts`:
243
+
244
+ ```ts
245
+ export const features = [
246
+ { icon: '🎯', title: 'Feature One', description: 'Short benefit-driven description.' },
247
+ { icon: '⚡', title: 'Feature Two', description: 'Short benefit-driven description.' },
248
+ { icon: '🔒', title: 'Feature Three', description: 'Short benefit-driven description.' },
249
+ ]
250
+
251
+ export const testimonials = [
252
+ { quote: 'Realistic testimonial here.', name: 'Jane Smith', role: 'CTO, TechCo' },
253
+ // ...
254
+ ]
255
+
256
+ export const pricingPlans = [
257
+ { name: 'Free', price: '$0', features: ['Feature A', 'Feature B'], cta: 'Get Started' },
258
+ { name: 'Pro', price: '$29/mo', features: ['Everything in Free', 'Feature C'], cta: 'Get Pro', popular: true },
259
+ { name: 'Enterprise', price: 'Custom', features: ['Everything in Pro', 'Priority support'], cta: 'Contact Us' },
260
+ ]
261
+
262
+ // Dashboard mock data
263
+ export const dashboardStats = [
264
+ { label: 'Total Users', value: '2,847', change: '+12%' },
265
+ // ...
266
+ ]
267
+
268
+ export const recentActivity = [
269
+ { user: 'Jane Smith', action: 'created a new project', time: '2 hours ago' },
270
+ // ...
271
+ ]
272
+ ```
273
+
274
+ ## Component Patterns
275
+
276
+ - Use functional components with TypeScript.
277
+ - Use `useState` for local state, no state management library needed.
278
+ - Navigation: `<Link to="/dashboard">` from react-router-dom.
279
+ - Keep components focused — one file per component.
280
+ - Props typed with interfaces or inline.
281
+
282
+ ## Dev Server
283
+
284
+ ```bash
285
+ npm run dev
286
+ # Runs on http://localhost:5173
287
+ ```
@@ -145,16 +145,54 @@ If a frontend stack was detected, generate FE test files:
145
145
 
146
146
  Skip this step if no FE stack was detected.
147
147
 
148
- ### 8b. Playwright-Specific (conditional)
148
+ ### 8b. Write E2E Spec Files (conditional)
149
+
150
+ **Only if `playwright.config.ts` or `playwright.config.js` is detected in the project root:**
151
+
152
+ Write `e2e/<feature>.spec.ts` files covering the acceptance criteria from `tasks/todo.md`. Follow these rules:
153
+
154
+ - Use `test.describe` / `test` blocks — not `describe`/`it`
155
+ - Use role-based locators: `getByRole`, `getByLabel`, `getByText`, `getByPlaceholder` — never CSS selectors
156
+ - Use `test.beforeEach` for shared setup (auth, navigation)
157
+ - Use `test.skip(!email, 'ENV_VAR not set — skipping')` guards for credential-dependent tests
158
+ - Auth credentials from env vars via `e2e/helpers/auth.ts` — never hardcode credentials
159
+ - Soft assertions (`expect.soft`) for non-critical checks; hard `expect` for gate conditions
160
+
161
+ E2E spec example structure:
162
+ ```ts
163
+ import { test, expect } from '@playwright/test'
164
+ import { signIn, TEST_USERS } from './helpers/auth'
165
+
166
+ test.describe('[Feature] — [scenario]', () => {
167
+ test.beforeEach(async ({ page }) => {
168
+ const { email, password } = TEST_USERS.regular
169
+ test.skip(!email, 'E2E_USER_EMAIL not set — skipping')
170
+ await signIn(page, email, password)
171
+ })
172
+
173
+ test('[behavior description]', async ({ page }) => {
174
+ await page.goto('/dashboard/feature')
175
+ await expect(page.getByRole('heading', { name: /title/i })).toBeVisible()
176
+ })
177
+ })
178
+ ```
179
+
180
+ Create `e2e/helpers/auth.ts` if it doesn't exist (see `/sk:e2e` Playwright Setup Reference).
181
+
182
+ **Run the E2E spec to confirm tests fail or skip** (they should fail until implementation, or skip if env vars aren't set — both are acceptable for the RED phase):
183
+ ```bash
184
+ npx playwright test e2e/<feature>.spec.ts --reporter=list
185
+ ```
186
+
187
+ ### 8c. Playwright MCP Inspection (optional)
149
188
 
150
- **Only if `@playwright/sk:test` is detected:**
189
+ **Only if the Playwright MCP plugin is active in the session AND live selectors are needed:**
151
190
 
152
191
  Use the Playwright MCP plugin to inspect live page state for more accurate selectors:
153
192
 
154
193
  1. Navigate to target URL
155
194
  2. Capture accessibility snapshot for role-based selectors
156
195
  3. Screenshot for visual reference
157
- 4. Optionally run inline assertions for complex interactions
158
196
 
159
197
  ### 9. Verify Tests Fail (Red Phase)
160
198
 
@@ -170,6 +208,7 @@ Output:
170
208
  ```
171
209
  BE tests written: X tests in Y files ([framework])
172
210
  FE tests written: X tests in Y files ([framework]) ← omit if no FE stack
211
+ E2E specs written: X tests in Y files (Playwright) ← omit if no playwright.config.ts
173
212
  Existing tests updated: X files
174
213
  Status: RED (tests fail as expected — ready for implementation)
175
214
  ```