diff --git a/.agents/prompts/phase-06/step-1.md b/.agents/prompts/phase-06/step-1.md new file mode 100644 index 0000000..7d7ad0b --- /dev/null +++ b/.agents/prompts/phase-06/step-1.md @@ -0,0 +1,329 @@ +# Phase 6 Step 1: Create Website Wizard Shell + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 5 is complete and Phase 6 is ready. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 2. +Do not implement the individual wizard step forms yet. +Do not implement the preview step yet. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Create the website wizard shell for the LaunchKit MVP. + +This step should establish the main builder interface, wizard navigation, shared page layout, and client-side selection state shape. The individual step contents will be implemented in later Phase 6 steps. + +The first screen should be the product builder experience, not a marketing-only landing page. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Create or update the app home page and supporting components for the wizard shell. + +Recommended structure: + +```txt +apps/web/ + app/ + page.tsx + components/ + builder/ + builder-shell.tsx + wizard-progress.tsx + wizard-navigation.tsx + wizard-step-panel.tsx + lib/ + builder/ + steps.ts + builder-state.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Wizard Flow + +The wizard should define these steps: + +```txt +1. Project +2. Framework +3. Styling and UI +4. Database +5. ORM +6. Auth +7. Extras +8. Preview +9. Download +``` + +For this step, each step can render placeholder content only. + +Do not implement real option controls yet. + +## Requirements + +### 1. Builder Shell + +Create a main builder shell that includes: + +- LaunchKit title or compact product identity. +- Step progress indicator. +- Current step panel. +- Back and Next navigation. +- Disabled Back button on the first step. +- Disabled Next button on the last step. +- Responsive layout for desktop and mobile. + +Keep the UI practical and developer-tool focused. + +Avoid a marketing hero. +Avoid oversized decorative sections. +Avoid decorative gradient blobs/orbs. + +### 2. Step State + +Create a client-side state shape for the selected LaunchKit config. + +Use the schema/default config from: + +```txt +@launchkit/schema +``` + +If the web app cannot yet import the package due to workspace config issues, fix the workspace/package wiring narrowly. + +Do not duplicate the schema manually inside `apps/web`. + +Recommended state: + +```ts +LaunchKitConfig; +``` + +Initialize from: + +```ts +defaultLaunchKitConfig; +``` + +### 3. Step Definitions + +Create a shared step definition list. + +Example: + +```ts +export type BuilderStepId = + | "project" + | "framework" + | "styling-ui" + | "database" + | "orm" + | "auth" + | "extras" + | "preview" + | "download"; +``` + +Each step should have: + +```txt +id +label +short label if useful +``` + +### 4. Placeholder Step Panels + +For now, render clear placeholder panels for each step. + +Examples: + +```txt +Project step coming next. +Framework step coming next. +Preview step coming later. +``` + +Do not add visible instructional text about keyboard shortcuts or internal implementation details. + +### 5. Visual Direction + +Use the established LaunchKit UI direction: + +- Minimal +- Fast to scan +- Practical +- Trustworthy +- Technical without clutter +- Green accent theme inspired by Supabase and Neon +- Token-based styling + +Prefer token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated one-off hardcoded color classes: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +### 6. shadcn/ui + +Use existing shadcn/ui components if they already exist in `apps/web`. + +Good candidates: + +```txt +Button +Card only for actual framed tool panels if already used +Separator +Badge +Progress +``` + +Do not add new shadcn components unless needed and consistent with the existing app setup. + +Do not nest cards inside cards. + +### 7. Generator Boundary + +Do not import generator logic into UI components yet unless the current architecture already requires it for type-only preview data. + +This step should not call: + +```txt +@launchkit/generator +``` + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Wizard renders all step labels. +- Back button is disabled on the first step. +- Next button moves to the next step. +- Back button returns to the previous step. +- Last step disables Next. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 1 completed: Create website wizard shell + +Changes made: +- Created or updated the LaunchKit builder home page. +- Added wizard shell layout. +- Added wizard step definitions. +- Added current-step navigation state. +- Initialized builder config from @launchkit/schema default config. +- Added placeholder panels for all wizard steps. + +Files changed: +- apps/web/app/page.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/components/builder/wizard-progress.tsx +- apps/web/components/builder/wizard-navigation.tsx +- apps/web/components/builder/wizard-step-panel.tsx +- apps/web/lib/builder/steps.ts +- apps/web/lib/builder/builder-state.ts +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 2: Create project step +``` + +## Completion Criteria + +This step is complete when: + +- The website home page shows the LaunchKit builder shell. +- The wizard has all 9 planned steps. +- Step progress is visible. +- Back/Next navigation works. +- Placeholder content renders for each step. +- Builder config state is initialized from `@launchkit/schema`. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-10.md b/.agents/prompts/phase-06/step-10.md new file mode 100644 index 0000000..83e4bc3 --- /dev/null +++ b/.agents/prompts/phase-06/step-10.md @@ -0,0 +1,349 @@ +# Phase 6 Step 10: Create API Generate Route + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 9 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 11. +Do not implement final browser download UI yet. +Do not implement CLI functionality. +Do not put generator logic in UI components. +Do not run generated project code on the LaunchKit server. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Create the server-side API route that validates a LaunchKit config and generates project output using the shared generator. + +This route should be the server boundary between the website and: + +```txt +@launchkit/schema +@launchkit/generator +``` + +It should prepare the website for the download flow in Phase 6 Step 11. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/app/api/generate/route.ts +apps/web/lib/api/generate.ts +apps/web/lib/api/errors.ts +apps/web/lib/api/response.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. API Route + +Create a Next.js App Router API route: + +```txt +apps/web/app/api/generate/route.ts +``` + +It should handle: + +```txt +POST /api/generate +``` + +The request body should be JSON containing a LaunchKit config. + +Do not support GET for generation. + +### 2. Request Validation + +Validate the request body using: + +```txt +@launchkit/schema +``` + +Use: + +```txt +LaunchKitConfigSchema +``` + +and compatibility validation helpers such as: + +```txt +validateCompatibility +``` + +or the equivalent exports from the schema package. + +Do not duplicate schema rules inside the API route. + +### 3. Generator Integration + +Call the shared generator package: + +```txt +@launchkit/generator +``` + +Use the existing generator function, likely: + +```ts +generateProject(config); +``` + +or the equivalent exported API. + +Do not place generator logic in `apps/web`. +Do not duplicate template logic in the route. +Do not import from `packages/templates` directly unless the generator API already requires that integration. + +### 4. Response Shape + +Return generated project data in a shape the website can use in Step 11. + +Recommended JSON response: + +```ts +type GenerateProjectResponse = { + project: { + name: string; + packageManager: "npm" | "pnpm"; + files: Array<{ + path: string; + contents: string; + encoding: "utf8" | "base64"; + }>; + }; +}; +``` + +If the generator can return `Uint8Array` contents, encode binary files as base64. + +For MVP template files, most contents should be UTF-8 strings. + +Do not return Node `Buffer` objects directly in JSON. + +### 5. Error Responses + +Return structured JSON errors. + +Recommended shape: + +```ts +type ApiErrorResponse = { + error: { + code: string; + message: string; + issues?: unknown[]; + }; +}; +``` + +Use appropriate HTTP statuses: + +```txt +400 invalid JSON or invalid config +422 compatibility failure +405 unsupported method, if needed +500 unexpected generation failure +``` + +Do not leak stack traces or internal filesystem paths to the client. + +### 6. Request Size And Safety + +Add basic request safety. + +Requirements: + +- Reject non-JSON content where practical. +- Reject malformed JSON. +- Reject request bodies that are too large. +- Do not write generated files to disk. +- Do not execute generated project code. +- Do not install generated project dependencies. +- Do not run shell commands from API input. + +Recommended maximum request body size: + +```txt +64 KB +``` + +Use the simplest safe approach that works in Next.js App Router. + +### 7. Path Safety + +The generator should already enforce safe generated paths from Phase 4. + +Still, before returning files from the API, verify generated file paths are safe: + +- relative paths only +- no leading `/` +- no `..` +- no empty path segments +- no `src/` directory + +If the generator exports a path normalization/helper, use it. + +Do not duplicate complex path logic if the generator already has a helper. + +### 8. No Zip Yet + +Do not create the zip archive in this step unless the earlier architecture explicitly decided the API route returns a zip directly. + +Preferred Phase 6 flow: + +```txt +Step 10: API returns generated project data safely. +Step 11: Download flow turns generated project data into a downloadable zip. +``` + +If the project architecture already expects the route to return a zip, document that decision in `progress-tracker.md` and keep the implementation minimal and server-side. + +### 9. Website Integration + +Do not wire the final Download button yet. + +It is acceptable to add a small typed client helper if useful: + +```txt +apps/web/lib/api/client.ts +``` + +But the visible download flow belongs to Phase 6 Step 11. + +## Tests + +Use Vitest only. + +Add route/helper tests if the repo already has a pattern for testing Next.js route handlers. + +Recommended tests: + +- Valid config returns generated project data. +- Invalid config returns `400`. +- Incompatible config returns `422`. +- Malformed JSON returns `400`. +- Oversized body returns an error. +- Response does not include unsafe file paths. +- Response does not include `src/` paths. +- Unexpected generator failure returns structured `500` without leaking stack traces. + +If route-handler testing is not already configured, add focused tests for extracted pure helpers instead of introducing a large new test stack. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +If generator/schema package tests may be affected, run: + +```bash +npm test -w @launchkit/schema +npm test -w @launchkit/generator +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 10 completed: Create API generate route + +Changes made: +- Added POST /api/generate route. +- Added request validation using @launchkit/schema. +- Added compatibility validation. +- Connected route to @launchkit/generator. +- Added structured success and error responses. +- Added basic request size and JSON safety checks. +- Added generated path safety checks before response. +- Confirmed no generated project code is executed. +- Confirmed no generated project dependencies are installed. +- Confirmed zip download UI remains for the next step. + +Files changed: +- apps/web/app/api/generate/route.ts +- apps/web/lib/api/generate.ts, if added +- apps/web/lib/api/errors.ts, if added +- apps/web/lib/api/response.ts, if added +- apps/web/lib/api/client.ts, if added +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 11: Create download flow +``` + +## Completion Criteria + +This step is complete when: + +- `POST /api/generate` exists. +- The route validates request JSON using `@launchkit/schema`. +- The route validates compatibility using shared schema helpers. +- The route calls `@launchkit/generator`. +- Valid configs return generated project data. +- Invalid configs return structured errors. +- Compatibility failures return structured errors. +- Malformed or oversized requests are handled safely. +- The route does not write generated files to disk. +- The route does not execute generated project code. +- The route does not install generated project dependencies. +- Generated paths are checked before response. +- No generated `src/` paths are returned. +- Final zip download UI is not implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-11.md b/.agents/prompts/phase-06/step-11.md new file mode 100644 index 0000000..0e1edb4 --- /dev/null +++ b/.agents/prompts/phase-06/step-11.md @@ -0,0 +1,380 @@ +# Phase 6 Step 11: Create Download Flow + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 10 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 12. +Do not add CLI functionality. +Do not put generator logic in UI components. +Do not run generated project code on the LaunchKit server. +Do not install generated project dependencies. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the website download flow for generated LaunchKit projects. + +Users should be able to: + +1. Review their selected config. +2. Click a generate/download button. +3. Receive a zip file containing the generated project. + +The flow should use the API route from Phase 6 Step 10: + +```txt +POST /api/generate +``` + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/download-step.tsx +apps/web/components/builder/download/download-button.tsx +apps/web/components/builder/download/download-status.tsx +apps/web/lib/download/create-project-zip.ts +apps/web/lib/api/client.ts +apps/web/components/builder/builder-shell.tsx +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Download Step UI + +Implement the Download step in the wizard. + +It should show: + +- Project name. +- Selected package manager. +- Short selected stack summary. +- Generate/download button. +- Loading state. +- Success state. +- Error state. + +Keep the UI compact and practical. + +Do not make this a marketing confirmation page. + +### 2. API Client + +Create or update a typed client helper for: + +```txt +POST /api/generate +``` + +It should: + +- Send the current `LaunchKitConfig`. +- Handle non-2xx responses. +- Parse structured API errors. +- Return generated project data. + +Do not duplicate generator logic in the client. + +### 3. Zip Creation + +Create a zip file from the generated project response. + +Preferred browser-side approach: + +- Use a small zip library if already installed. +- If no zip library exists, add one only if appropriate for the repo. + +Good option: + +```txt +jszip +``` + +If adding a dependency, add it to the correct workspace package: + +```txt +apps/web +``` + +Do not add zip logic to `@launchkit/generator` unless the existing architecture already planned a zip adapter there. + +### 4. Zip Contents + +The zip should contain generated files under a top-level project folder: + +```txt +{{projectName}}/ + package.json + app/page.tsx + ... +``` + +Example zip file name: + +```txt +{{projectName}}.zip +``` + +File contents should come from the API response. + +For each file: + +- Use `utf8` contents as text. +- Use `base64` contents for binary files if present. + +Do not include unsafe paths. +Do not include absolute paths. +Do not include paths containing `..`. +Do not include empty path segments. +Do not include `src/`. + +### 5. Browser Download + +Trigger the browser download after zip creation. + +Use a safe browser approach: + +- Create a Blob. +- Create an object URL. +- Create/click a temporary anchor. +- Revoke the object URL after use. + +Do not write files to the server filesystem. + +### 6. State Handling + +The download flow should handle: + +```txt +idle +generating +success +error +``` + +Disable the download button while generating. + +Show a concise error message if: + +- API validation fails. +- Compatibility validation fails. +- Generation fails. +- Zip creation fails. + +Do not leak stack traces. + +### 7. Validation Before Download + +Before calling the API, validate the current config using: + +```txt +@launchkit/schema +``` + +If invalid: + +- Show concise errors. +- Do not call the API. + +The API should still validate again server-side. + +### 8. No Generated Code Execution + +The download flow must not: + +- Run generated project code. +- Install generated dependencies. +- Execute shell commands. +- Upload generated code anywhere. + +It should only request generated file data and package it as a zip. + +### 9. Visual Direction + +Use token-based styling: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +Button +Alert +Badge +Separator +Progress +``` + +Do not nest cards inside cards. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests where practical. + +Possible tests: + +- Download step renders project summary. +- Download button is disabled while generating. +- Invalid config prevents API call. +- API errors render concise messages. +- Zip helper rejects unsafe paths. +- Zip helper rejects `src/` paths. +- Zip helper includes files under the top-level project folder. +- Zip helper handles UTF-8 file contents. +- Zip helper handles base64 file contents if supported. + +If browser download behavior is hard to test in the current setup, test the pure zip/path helper and document UI manual verification in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +If adding a dependency, run: + +```bash +npm install +``` + +or the appropriate workspace install command for the existing repo setup. + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Manual Verification + +If the app can be run locally, manually verify: + +1. Start the web app. +2. Complete the wizard with the default config. +3. Go to Download. +4. Click download. +5. Confirm a zip downloads. +6. Confirm the zip contains a top-level project folder. +7. Confirm the zip contains expected generated files. +8. Confirm no `src/` folder exists. + +If local app startup is not possible, document why in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 11 completed: Create download flow + +Changes made: +- Added Download step UI. +- Added API client for POST /api/generate. +- Added zip creation helper. +- Added browser download trigger. +- Added loading, success, and error states. +- Added client-side validation before generation. +- Added path safety checks for zip contents. +- Confirmed generated code is not executed or installed. + +Files changed: +- apps/web/components/builder/steps/download-step.tsx +- apps/web/components/builder/download/download-button.tsx +- apps/web/components/builder/download/download-status.tsx +- apps/web/lib/download/create-project-zip.ts +- apps/web/lib/api/client.ts +- apps/web/components/builder/builder-shell.tsx +- apps/web/package.json, if a dependency was added +- package-lock.json, if dependencies changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Manual verification: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 12: Responsive UI polish and Phase 6 verification +``` + +## Completion Criteria + +This step is complete when: + +- Download step renders in the wizard. +- Download button calls `POST /api/generate`. +- Generated project data is turned into a zip. +- Zip downloads in the browser. +- Zip contains a top-level project folder. +- Zip contains only safe relative paths. +- Zip does not include `src/`. +- Invalid config prevents API calls. +- API errors are shown clearly. +- Loading, success, and error states work. +- No generator logic is duplicated in UI components. +- Generated project code is not executed. +- Generated project dependencies are not installed. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-12.md b/.agents/prompts/phase-06/step-12.md new file mode 100644 index 0000000..55a2810 --- /dev/null +++ b/.agents/prompts/phase-06/step-12.md @@ -0,0 +1,410 @@ +# Phase 6 Step 12: Responsive UI Polish and Phase 6 Verification + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Steps 1-11 are complete. +4. Read this step prompt. +5. Implement only this verification and polish step. + +Do not start Phase 7. +Do not add CLI functionality. +Do not add new product options. +Do not add unsupported frameworks, databases, ORMs, auth providers, UI libraries, or package managers. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Polish and verify the complete LaunchKit website MVP wizard. + +This step should make sure the website flow is usable, responsive, visually consistent, and correctly connected to schema, generator, preview, API generation, and download behavior. + +Phase 6 should only be marked complete if the website MVP genuinely works. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Make small, focused fixes across the Phase 6 website files as needed. + +Do not perform unrelated refactors. + +## Required User Flow + +Verify the full website flow: + +```txt +1. Project +2. Framework +3. Styling and UI +4. Database +5. ORM +6. Auth +7. Extras +8. Preview +9. Download +``` + +The user should be able to: + +1. Open the website. +2. Enter a valid project name. +3. Choose package manager. +4. Review fixed framework choices. +5. Choose UI option. +6. Choose database option. +7. Choose ORM option. +8. Choose auth option. +9. Choose Docker option. +10. Preview generated output. +11. Download a zip. + +## Verification Checklist + +### 1. Wizard Navigation + +Confirm: + +- All 9 steps render. +- Back and Next work. +- Back is disabled on the first step. +- Next is disabled or replaced appropriately on the last step. +- Step progress updates correctly. +- Invalid current-step config prevents advancing. +- Users can return to previous steps without losing selections. + +### 2. Project Step + +Confirm: + +- Project name input works. +- Project name validation matches `@launchkit/schema`. +- Invalid names show concise errors. +- Invalid names prevent advancing. +- Package manager selector supports `npm` and `pnpm`. + +### 3. Framework Step + +Confirm: + +- Next.js is shown. +- TypeScript is shown. +- App Router is shown. +- No `src/` structure is shown. +- Unsupported framework choices are not exposed. + +### 4. Styling and UI Step + +Confirm: + +- Tailwind CSS is shown as fixed. +- UI selector supports `none` and `shadcn`. +- Unsupported styling systems and UI libraries are not exposed. +- shadcn selection remains compatible with Tailwind. + +### 5. Database Step + +Confirm: + +- Database selector supports `none` and `postgres`. +- Selecting `database: "none"` resets incompatible Prisma and Docker selections. +- Selecting `database: "none"` does not reset Auth.js credentials. +- Unsupported databases are not exposed. + +### 6. ORM Step + +Confirm: + +- ORM selector supports `none` and `prisma`. +- Prisma is disabled or unavailable unless PostgreSQL is selected. +- Prisma can be selected when PostgreSQL is selected. +- Unsupported ORMs are not exposed. + +### 7. Auth Step + +Confirm: + +- Auth selector supports `none` and `authjs-credentials`. +- Auth.js credentials can be selected without PostgreSQL. +- Auth.js credentials does not force database or ORM changes. +- Auth.js credentials is clearly described as a scaffold. +- Unsupported auth providers are not exposed. + +### 8. Extras Step + +Confirm: + +- Docker selector supports `none` and `postgres`. +- Docker PostgreSQL is disabled or unavailable unless PostgreSQL is selected. +- Docker PostgreSQL can be selected when PostgreSQL is selected. +- Unsupported extras are not exposed. + +### 9. Preview Step + +Confirm preview shows: + +```txt +Selected stack summary +Dependencies +Dev dependencies +Scripts +Environment variables +Generated file tree +``` + +Confirm: + +- Preview matches selected options. +- Preview excludes unselected optional feature files. +- Preview does not show `src/`. +- Invalid config shows concise errors. + +### 10. API Generate Route + +Confirm: + +- `POST /api/generate` validates config with `@launchkit/schema`. +- Compatibility validation runs server-side. +- The route calls `@launchkit/generator`. +- Invalid config returns structured errors. +- Incompatible config returns structured errors. +- The route does not execute generated code. +- The route does not install generated dependencies. +- The route does not write generated project files to disk. +- Generated paths are safe before response. + +### 11. Download Flow + +Confirm: + +- Download button calls the API route. +- Loading state appears while generating. +- Errors are displayed clearly. +- A zip downloads on success. +- Zip file is named from the project name. +- Zip contains a top-level project folder. +- Zip contains expected generated files. +- Zip does not include `src/`. +- Zip does not include unsafe paths. + +## Responsive UI Polish + +Verify and polish the UI at common viewport sizes: + +```txt +mobile: 375px wide +tablet: 768px wide +desktop: 1280px wide +wide desktop: 1440px+ wide +``` + +Fix issues such as: + +- Text overflow. +- Button labels wrapping badly. +- Step navigation crowding. +- Panels becoming too narrow. +- File tree overflowing without scroll handling. +- Preview lists becoming unreadable. +- Download state layout breaking. +- Touch targets being too small on mobile. + +Use stable dimensions and responsive constraints where needed. + +Do not use viewport-width-based font sizing. +Do not use negative letter spacing. +Do not add decorative gradient blobs/orbs. +Do not nest cards inside cards. + +## Visual Consistency + +Confirm the website still feels like a focused developer tool: + +- Minimal +- Fast to scan +- Practical +- Trustworthy +- Technical without clutter + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Remove repeated one-off hardcoded color utilities where practical: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Do not redesign the whole app. Make focused polish fixes only. + +## Tests + +Use Vitest only. + +Add or update focused tests where the current test setup supports it. + +Recommended coverage: + +- Wizard navigation. +- Project name validation. +- Dependent option behavior. +- Preview excludes unselected features. +- API route validation. +- Download zip path safety helper. + +Do not introduce a large new frontend test stack in this final verification step unless the repo already has the foundation for it. + +## Verification Commands + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +If schema/generator packages may be affected, run: + +```bash +npm test -w @launchkit/schema +npm test -w @launchkit/generator +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Manual Verification + +If the web app can run locally, manually verify: + +1. Start the web app. +2. Complete the wizard with default options. +3. Preview the generated output. +4. Download the zip. +5. Inspect the zip contents. +6. Repeat with all compatible MVP features selected: + - shadcn/ui + - PostgreSQL + - Prisma + - Auth.js credentials + - Docker PostgreSQL +7. Confirm invalid combinations are prevented or explained. +8. Check mobile, tablet, and desktop widths. + +If local app startup is not possible, document why in `progress-tracker.md`. + +## Progress Tracker Update + +After verification, update `progress-tracker.md`. + +If Phase 6 is complete, mark: + +```txt +Phase 6: Complete +Phase 7: Ready +``` + +Add an entry like: + +```txt +Phase 6 Step 12 completed: Responsive UI polish and Phase 6 verification + +Changes made: +- Verified full website wizard flow. +- Verified all Phase 6 steps. +- Polished responsive layout issues. +- Verified API generation route. +- Verified download flow. +- Verified generated zip contents. +- Fixed any small in-scope issues found during verification. + +Files changed: +- apps/web/..., if polish fixes were needed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Manual verification: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 7 Step 1: Begin testing, validation, and hardening +``` + +If Phase 6 is not complete, do not mark it complete. Instead record: + +```txt +Phase 6: In progress +Blocked/missing: +- ... + +Next suggested step: +- Fix the listed Phase 6 blocker before starting Phase 7. +``` + +## Completion Criteria + +This step is complete when: + +- All 9 wizard steps work. +- Builder state persists while navigating between steps. +- Schema validation works in the UI. +- Compatibility behavior works in the UI. +- Preview accurately reflects selected options. +- API generation route works. +- Download flow produces a zip. +- Zip contents are correct and safe. +- No generated project contains `src/`. +- The website is usable on mobile, tablet, and desktop. +- No generator logic is duplicated in UI components. +- No generated code is executed or installed by the website. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass, or unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. +- Phase 6 is marked complete only if the website MVP genuinely works. + +Then stop. diff --git a/.agents/prompts/phase-06/step-2.md b/.agents/prompts/phase-06/step-2.md new file mode 100644 index 0000000..d2fa120 --- /dev/null +++ b/.agents/prompts/phase-06/step-2.md @@ -0,0 +1,319 @@ +# Phase 6 Step 2: Create Project Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 1 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 3. +Do not implement framework, styling/UI, database, ORM, auth, extras, preview, or download steps yet. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Project step in the LaunchKit website wizard. + +This step should let users configure the generated project identity and package manager: + +```txt +name +packageManager +``` + +Use the shared schema package for validation and metadata. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/project-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/builder-state.ts +apps/web/lib/builder/validation.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Project Name Input + +Add a project name input for: + +```ts +config.name; +``` + +Project name rules must match `@launchkit/schema`: + +Allowed: + +```txt +lowercase letters +numbers +hyphens +``` + +Examples: + +```txt +my-app +launchkit-demo +app123 +``` + +Disallowed: + +```txt +empty string +spaces +uppercase letters +path separators +special characters +``` + +Do not duplicate validation rules manually if the schema exports a parser or validator. + +Use: + +```txt +LaunchKitConfigSchema +``` + +or another exported schema helper from: + +```txt +@launchkit/schema +``` + +### 2. Validation UX + +Show validation feedback near the input. + +Requirements: + +- Invalid project names should show a concise error. +- Valid project names should not show an error. +- The user should not be able to advance from the Project step with an invalid project name. +- Do not use blocking browser alerts. +- Do not show noisy validation before the field has been edited unless the initial default is invalid. + +### 3. Package Manager Control + +Add a package manager selector for: + +```ts +config.packageManager; +``` + +Supported values: + +```txt +npm +pnpm +``` + +Use metadata from `@launchkit/schema` if available. + +Recommended UI: + +- Segmented control +- Radio group +- Two-option toggle + +Do not use a plain text input. + +Remember: + +- The LaunchKit repo itself uses npm. +- Generated projects may support npm and pnpm instructions. + +### 4. State Updates + +Update the shared builder config state created in Phase 6 Step 1. + +When the user edits the project name or package manager: + +- Update the current `LaunchKitConfig`. +- Preserve all other selected config values. +- Keep state local to the builder for now. + +Do not write to local storage unless the existing UX plan already requires it. + +### 5. Navigation Behavior + +The Project step should gate the Next button. + +If the Project step has validation errors: + +- Disable Next, or +- Keep Next enabled but prevent navigation and show the error. + +Prefer disabling Next when invalid. + +Do not add validation gates for future steps yet. + +### 6. Visual Direction + +Keep the UI practical and compact. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +Input +Label +Button +RadioGroup +Alert or small inline message +``` + +Do not nest cards inside cards. + +### 7. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only updates website state and validation. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Project step renders the default project name. +- Editing the project name updates state. +- Invalid names show validation feedback. +- Invalid names prevent moving to the next step. +- Package manager selector updates `packageManager`. +- Supported package manager options come from schema metadata if available. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 2 completed: Create project step + +Changes made: +- Added Project step UI. +- Added project name input. +- Added project name validation using @launchkit/schema. +- Added package manager selector. +- Connected Project step to shared builder config state. +- Gated Next navigation when the Project step is invalid. + +Files changed: +- apps/web/components/builder/steps/project-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/builder-state.ts +- apps/web/lib/builder/validation.ts, if added +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 3: Create framework step +``` + +## Completion Criteria + +This step is complete when: + +- Project step renders in the wizard. +- Project name input is connected to builder config state. +- Project name validation uses `@launchkit/schema`. +- Invalid project names show useful feedback. +- Invalid project names prevent advancing from the Project step. +- Package manager selector supports `npm` and `pnpm`. +- Package manager selection updates builder config state. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-3.md b/.agents/prompts/phase-06/step-3.md new file mode 100644 index 0000000..a2842f3 --- /dev/null +++ b/.agents/prompts/phase-06/step-3.md @@ -0,0 +1,311 @@ +# Phase 6 Step 3: Create Framework Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 2 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 4. +Do not implement styling/UI, database, ORM, auth, extras, preview, or download steps yet. +Do not add unsupported frameworks, languages, routers, or project structures. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Framework step in the LaunchKit website wizard. + +For the MVP, this step should communicate and confirm the fixed technical foundation for generated projects: + +```txt +framework: "next" +language: "typescript" +router: "app" +projectStructure: "no-src" +``` + +This step should not introduce unsupported choices. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/framework-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/steps.ts +apps/web/lib/builder/builder-state.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Display MVP Framework Choices + +Show the current generated-project foundation: + +```txt +Next.js +TypeScript +App Router +No src/ directory +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +frameworkMetadata +languageMetadata +routerMetadata +projectStructureMetadata +``` + +Do not duplicate labels/descriptions manually if metadata is exported. + +### 2. Keep Options Fixed + +The MVP supports only: + +```txt +frameworkOptions: ["next"] +languageOptions: ["typescript"] +routerOptions: ["app"] +projectStructureOptions: ["no-src"] +``` + +Do not add UI choices for: + +```txt +React Router +Remix +Astro +Vue +Svelte +JavaScript +Pages Router +src/ folder +``` + +Because there is only one supported option in each category, this step may render them as selected fixed options rather than interactive controls. + +### 3. State Consistency + +Ensure the shared builder config state contains: + +```ts +{ + framework: "next", + language: "typescript", + router: "app", + projectStructure: "no-src" +} +``` + +Do not let this step change those values to unsupported values. + +If the state was initialized from `defaultLaunchKitConfig`, this should already be true. + +### 4. Validation + +Use: + +```txt +LaunchKitConfigSchema +``` + +or exported schema helpers from: + +```txt +@launchkit/schema +``` + +to ensure the current config remains valid. + +This step should not introduce additional validation gates beyond confirming the current fixed framework values are valid. + +### 5. Navigation Behavior + +The user should be able to move Back and Next through this step if the config is valid. + +Do not block navigation because the framework choices are fixed. + +If the config somehow contains invalid framework values, show a concise error and prevent moving forward. + +### 6. Visual Direction + +Keep the UI compact and useful. + +Recommended presentation: + +- A concise stack summary. +- Four fixed selected rows or tiles. +- Small descriptions from schema metadata. +- A subtle note that more frameworks may come later, if appropriate. + +Do not make this a marketing section. +Do not use a large hero. +Do not use decorative gradient blobs/orbs. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available, if not add them and use: + +```txt +Badge +Separator +Button +``` + +Do not nest cards inside cards. + +### 7. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only renders and validates builder state. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Framework step renders Next.js. +- Framework step renders TypeScript. +- Framework step renders App Router. +- Framework step renders no `src/` structure. +- Framework step does not render unsupported framework options. +- User can continue when the default config is valid. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 3 completed: Create framework step + +Changes made: +- Added Framework step UI. +- Displayed fixed MVP framework foundation. +- Used @launchkit/schema metadata where available. +- Confirmed framework config remains Next.js, TypeScript, App Router, no-src. +- Confirmed unsupported framework choices are not exposed. + +Files changed: +- apps/web/components/builder/steps/framework-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/steps.ts, if changed +- apps/web/lib/builder/builder-state.ts, if changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 4: Create styling and UI step +``` + +## Completion Criteria + +This step is complete when: + +- Framework step renders in the wizard. +- The step shows Next.js. +- The step shows TypeScript. +- The step shows App Router. +- The step shows no `src/` project structure. +- Unsupported framework/language/router/structure choices are not exposed. +- Current config remains valid according to `@launchkit/schema`. +- User can continue with the default config. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-4.md b/.agents/prompts/phase-06/step-4.md new file mode 100644 index 0000000..c93256f --- /dev/null +++ b/.agents/prompts/phase-06/step-4.md @@ -0,0 +1,325 @@ +# Phase 6 Step 4: Create Styling and UI Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 3 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 5. +Do not implement database, ORM, auth, extras, preview, or download steps yet. +Do not add unsupported styling systems or UI libraries. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Styling and UI step in the LaunchKit website wizard. + +For the MVP, generated projects always use: + +```txt +styling: "tailwind" +``` + +Users can choose: + +```txt +ui: "none" +ui: "shadcn" +``` + +This step should let users decide whether to include shadcn/ui in the generated project. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/styling-ui-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/steps.ts +apps/web/lib/builder/builder-state.ts +apps/web/lib/builder/validation.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Show Tailwind as Fixed Styling + +Display Tailwind CSS as the fixed MVP styling choice: + +```txt +styling: "tailwind" +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +stylingMetadata +``` + +Do not expose unsupported styling options such as: + +```txt +CSS Modules +Sass +Styled Components +Panda CSS +UnoCSS +``` + +### 2. Add UI Library Selector + +Add a selector for: + +```ts +config.ui; +``` + +Supported values: + +```txt +none +shadcn +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +uiMetadata +``` + +Recommended UI: + +- Segmented control +- Radio group +- Selectable option rows + +Do not use a plain text input. + +### 3. State Updates + +When the user changes the UI option: + +- Update `config.ui`. +- Preserve all other builder config values. +- Keep `config.styling` as `"tailwind"`. + +Do not write to local storage unless the existing UX plan already requires it. + +### 4. Compatibility + +The schema should already have this compatibility rule: + +```txt +shadcn/ui requires Tailwind CSS. +``` + +Since Tailwind is fixed in the MVP, selecting shadcn should normally be valid. + +Use schema helpers from: + +```txt +@launchkit/schema +``` + +to validate the current config if the builder shell already supports validation. + +Do not duplicate compatibility rules manually in UI code unless the existing architecture has a small UI helper for displaying schema issues. + +### 5. Navigation Behavior + +The user should be able to move Back and Next when: + +```txt +styling: "tailwind" +ui: "none" or "shadcn" +``` + +If the current config somehow violates schema compatibility, show a concise error and prevent moving forward. + +Do not add validation gates for future steps yet. + +### 6. Visual Direction + +Keep the UI compact and scannable. + +Recommended presentation: + +- A fixed Tailwind summary row. +- Two selectable UI rows: "No component library" and "shadcn/ui". +- Short descriptions from schema metadata. +- A recommended indicator for shadcn/ui if metadata marks it recommended. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +RadioGroup +Badge +Button +Separator +``` + +Do not nest cards inside cards. + +### 7. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only renders and updates builder state. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Styling/UI step renders Tailwind as fixed. +- Styling/UI step renders `none` and `shadcn` UI options. +- Selecting shadcn updates `config.ui`. +- Selecting none updates `config.ui`. +- Unsupported styling systems are not rendered. +- Unsupported UI libraries are not rendered. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 4 completed: Create styling and UI step + +Changes made: +- Added Styling and UI step UI. +- Displayed Tailwind CSS as the fixed MVP styling choice. +- Added UI selector for none and shadcn/ui. +- Used @launchkit/schema metadata where available. +- Connected UI selection to shared builder config state. +- Confirmed unsupported styling systems and UI libraries are not exposed. + +Files changed: +- apps/web/components/builder/steps/styling-ui-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/steps.ts, if changed +- apps/web/lib/builder/builder-state.ts, if changed +- apps/web/lib/builder/validation.ts, if changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 5: Create database step +``` + +## Completion Criteria + +This step is complete when: + +- Styling and UI step renders in the wizard. +- Tailwind CSS is shown as the fixed styling option. +- UI selector supports `none` and `shadcn`. +- Selecting a UI option updates builder config state. +- Unsupported styling systems are not exposed. +- Unsupported UI libraries are not exposed. +- Current config remains valid according to `@launchkit/schema`. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-5.md b/.agents/prompts/phase-06/step-5.md new file mode 100644 index 0000000..ca8b5c5 --- /dev/null +++ b/.agents/prompts/phase-06/step-5.md @@ -0,0 +1,313 @@ +# Phase 6 Step 5: Create Database Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 4 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 6. +Do not implement ORM, auth, extras, preview, or download steps yet. +Do not add unsupported databases. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Database step in the LaunchKit website wizard. + +Users should be able to choose: + +```txt +database: "none" +database: "postgres" +``` + +This step should only select the database layer. Prisma, Auth.js, and Docker options are handled in later steps. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/database-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/steps.ts +apps/web/lib/builder/builder-state.ts +apps/web/lib/builder/validation.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Add Database Selector + +Add a selector for: + +```ts +config.database; +``` + +Supported values: + +```txt +none +postgres +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +databaseMetadata +``` + +Recommended UI: + +- Segmented control +- Radio group +- Selectable option rows + +Do not use a plain text input. + +### 2. State Updates + +When the user changes the database option: + +- Update `config.database`. +- Preserve all other builder config values unless a dependent value would become invalid. + +If changing from PostgreSQL to none would make existing selections invalid, apply the smallest clear reset needed. + +Recommended behavior: + +```txt +If database becomes "none": + - set orm to "none" if it was "prisma" + - set docker to "none" if it was "postgres" + - keep auth unchanged, because authjs-credentials may work without a database +``` + +This preserves compatibility while avoiding hidden invalid config. + +### 3. Compatibility Awareness + +The schema should already have these compatibility rules: + +```txt +Prisma requires PostgreSQL. +PostgreSQL Docker Compose is only available when PostgreSQL is selected. +Auth.js credentials scaffold may work without a database. +``` + +Use schema helpers from: + +```txt +@launchkit/schema +``` + +to validate compatibility if the builder shell already supports compatibility issue display. + +Do not duplicate compatibility rules manually in UI code unless needed for dependent-field reset behavior. + +### 4. Do Not Implement Later Steps + +Do not add controls for: + +```txt +orm +auth +docker +``` + +Those belong to later Phase 6 steps. + +This step may reset invalid dependent values when database changes, but it should not render the dependent controls. + +### 5. Navigation Behavior + +The user should be able to move Back and Next when: + +```txt +database: "none" or "postgres" +``` + +If the current config somehow violates schema compatibility, show a concise error and prevent moving forward. + +Do not add validation gates for future steps beyond compatibility issues caused by database selection. + +### 6. Visual Direction + +Keep the UI practical and compact. + +Recommended presentation: + +- Two selectable rows: "No database" and "PostgreSQL". +- Short descriptions from schema metadata. +- Recommended indicator for PostgreSQL if metadata marks it recommended. +- Optional small note that Prisma and Docker are configured later. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +RadioGroup +Badge +Button +Separator +``` + +Do not nest cards inside cards. + +### 7. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only renders and updates builder state. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Database step renders `none` and `postgres` options. +- Selecting PostgreSQL updates `config.database`. +- Selecting no database updates `config.database`. +- Selecting no database resets Prisma ORM to none if previously selected. +- Selecting no database resets Docker PostgreSQL to none if previously selected. +- Selecting no database does not reset Auth.js credentials. +- Unsupported databases are not rendered. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 5 completed: Create database step + +Changes made: +- Added Database step UI. +- Added database selector for none and PostgreSQL. +- Used @launchkit/schema metadata where available. +- Connected database selection to shared builder config state. +- Added dependent reset behavior for Prisma and Docker when PostgreSQL is disabled. +- Confirmed unsupported databases are not exposed. + +Files changed: +- apps/web/components/builder/steps/database-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/steps.ts, if changed +- apps/web/lib/builder/builder-state.ts, if changed +- apps/web/lib/builder/validation.ts, if changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 6: Create ORM step +``` + +## Completion Criteria + +This step is complete when: + +- Database step renders in the wizard. +- Database selector supports `none` and `postgres`. +- Selecting a database option updates builder config state. +- Selecting `database: "none"` resets incompatible Prisma and Docker selections. +- Selecting `database: "none"` does not reset Auth.js credentials. +- Unsupported databases are not exposed. +- Current config remains valid according to `@launchkit/schema`. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-6.md b/.agents/prompts/phase-06/step-6.md new file mode 100644 index 0000000..13c2ccf --- /dev/null +++ b/.agents/prompts/phase-06/step-6.md @@ -0,0 +1,343 @@ +# Phase 6 Step 6: Create ORM Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 5 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 7. +Do not implement auth, extras, preview, or download steps yet. +Do not add unsupported ORMs. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the ORM step in the LaunchKit website wizard. + +Users should be able to choose: + +```txt +orm: "none" +orm: "prisma" +``` + +Prisma should only be available when: + +```txt +database: "postgres" +``` + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/orm-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/steps.ts +apps/web/lib/builder/builder-state.ts +apps/web/lib/builder/validation.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Add ORM Selector + +Add a selector for: + +```ts +config.orm; +``` + +Supported values: + +```txt +none +prisma +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +ormMetadata +``` + +Recommended UI: + +- Segmented control +- Radio group +- Selectable option rows + +Do not use a plain text input. + +### 2. PostgreSQL Dependency + +Prisma requires PostgreSQL. + +If: + +```txt +config.database !== "postgres" +``` + +then the Prisma option should be disabled or unavailable. + +Recommended behavior: + +- Show `No ORM` as selected. +- Show Prisma as disabled with a concise reason: `Requires PostgreSQL`. +- Keep `config.orm` as `"none"`. + +Do not silently enable PostgreSQL when Prisma is selected. + +### 3. State Updates + +When the user changes the ORM option: + +- Update `config.orm`. +- Preserve all other builder config values. + +If selecting Prisma: + +- Only allow it when `config.database === "postgres"`. +- Keep `database` unchanged. + +If selecting none: + +- Set `config.orm` to `"none"`. +- Preserve database, auth, and docker selections unless the existing validation helper requires a narrow compatibility fix. + +### 4. Compatibility + +The schema should already have this compatibility rule: + +```txt +Prisma requires PostgreSQL. +``` + +Use schema helpers from: + +```txt +@launchkit/schema +``` + +to validate compatibility if the builder shell already supports compatibility issue display. + +Do not duplicate compatibility rules manually except for UI affordances such as disabling the Prisma option when PostgreSQL is not selected. + +### 5. Do Not Implement Later Steps + +Do not add controls for: + +```txt +auth +docker +preview +download +``` + +Those belong to later Phase 6 steps. + +### 6. Navigation Behavior + +The user should be able to move Back and Next when: + +```txt +orm: "none" +``` + +or: + +```txt +database: "postgres" +orm: "prisma" +``` + +If the current config somehow has: + +```txt +database: "none" +orm: "prisma" +``` + +show a concise error and prevent moving forward. + +### 7. Visual Direction + +Keep the UI practical and compact. + +Recommended presentation: + +- Two selectable rows: "No ORM" and "Prisma". +- Short descriptions from schema metadata. +- Recommended indicator for Prisma if metadata marks it recommended. +- Disabled state for Prisma when PostgreSQL is not selected. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +RadioGroup +Badge +Button +Separator +``` + +Do not nest cards inside cards. + +### 8. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only renders and updates builder state. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- ORM step renders `none` and `prisma` options. +- Prisma option is disabled when database is `none`. +- Prisma option is enabled when database is `postgres`. +- Selecting Prisma updates `config.orm` only when PostgreSQL is selected. +- Selecting no ORM updates `config.orm`. +- Unsupported ORMs are not rendered. +- Invalid config with Prisma and no PostgreSQL prevents moving forward. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 6 completed: Create ORM step + +Changes made: +- Added ORM step UI. +- Added ORM selector for none and Prisma. +- Used @launchkit/schema metadata where available. +- Connected ORM selection to shared builder config state. +- Disabled Prisma unless PostgreSQL is selected. +- Confirmed unsupported ORMs are not exposed. + +Files changed: +- apps/web/components/builder/steps/orm-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/steps.ts, if changed +- apps/web/lib/builder/builder-state.ts, if changed +- apps/web/lib/builder/validation.ts, if changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 7: Create auth step +``` + +## Completion Criteria + +This step is complete when: + +- ORM step renders in the wizard. +- ORM selector supports `none` and `prisma`. +- Prisma is disabled or unavailable when PostgreSQL is not selected. +- Prisma can be selected when PostgreSQL is selected. +- Selecting an ORM option updates builder config state. +- Unsupported ORMs are not exposed. +- Current config remains valid according to `@launchkit/schema`. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-7.md b/.agents/prompts/phase-06/step-7.md new file mode 100644 index 0000000..9ccb934 --- /dev/null +++ b/.agents/prompts/phase-06/step-7.md @@ -0,0 +1,339 @@ +# Phase 6 Step 7: Create Auth Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 6 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 8. +Do not implement extras, preview, or download steps yet. +Do not add unsupported auth providers. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Auth step in the LaunchKit website wizard. + +Users should be able to choose: + +```txt +auth: "none" +auth: "authjs-credentials" +``` + +The Auth.js credentials option is a scaffold only. The UI should not imply production-ready authentication. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/auth-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/steps.ts +apps/web/lib/builder/builder-state.ts +apps/web/lib/builder/validation.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Add Auth Selector + +Add a selector for: + +```ts +config.auth; +``` + +Supported values: + +```txt +none +authjs-credentials +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +authMetadata +``` + +Recommended UI: + +- Segmented control +- Radio group +- Selectable option rows + +Do not use a plain text input. + +### 2. Auth.js Credentials Messaging + +The Auth.js credentials option should clearly communicate that it is a scaffold. + +It should not imply: + +- Production-ready auth. +- Complete user management. +- Secure password verification out of the box. +- A complete sign-in UI. + +Use concise wording such as: + +```txt +Adds Auth.js credentials structure that you connect to your user model and password verification. +``` + +Do not over-explain implementation details in the UI. + +### 3. State Updates + +When the user changes the auth option: + +- Update `config.auth`. +- Preserve all other builder config values. + +Auth.js credentials may work without a database, so do not force PostgreSQL on when auth is selected. + +Do not reset database or ORM when auth changes unless the current schema validation requires a narrow compatibility fix. + +### 4. Compatibility + +The schema should already represent these rules: + +```txt +Auth.js credentials scaffold may work without a database. +Auth.js credentials scaffold with Prisma requires PostgreSQL and Prisma. +Prisma requires PostgreSQL. +``` + +Verify the UI allows: + +```txt +auth: "authjs-credentials", database: "none", orm: "none" +auth: "authjs-credentials", database: "postgres", orm: "none" +auth: "authjs-credentials", database: "postgres", orm: "prisma" +``` + +Use schema helpers from: + +```txt +@launchkit/schema +``` + +to validate compatibility if the builder shell already supports compatibility issue display. + +Do not duplicate compatibility rules manually in UI code unless needed for a small UI affordance. + +### 5. Do Not Implement Later Steps + +Do not add controls for: + +```txt +docker +preview +download +``` + +Those belong to later Phase 6 steps. + +### 6. Navigation Behavior + +The user should be able to move Back and Next when: + +```txt +auth: "none" +``` + +or: + +```txt +auth: "authjs-credentials" +``` + +as long as the full config is schema-compatible. + +If the current config somehow violates compatibility, show a concise error and prevent moving forward. + +### 7. Visual Direction + +Keep the UI practical and compact. + +Recommended presentation: + +- Two selectable rows: "No auth" and "Auth.js credentials scaffold". +- Short descriptions from schema metadata. +- A small scaffold warning for Auth.js credentials. +- No marketing-style auth provider grid. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +RadioGroup +Badge +Button +Separator +Alert +``` + +Do not nest cards inside cards. + +### 8. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only renders and updates builder state. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Auth step renders `none` and `authjs-credentials` options. +- Selecting Auth.js credentials updates `config.auth`. +- Selecting no auth updates `config.auth`. +- Auth.js credentials can be selected without PostgreSQL. +- Auth.js credentials can be selected with PostgreSQL. +- Auth.js credentials can be selected with PostgreSQL and Prisma. +- Unsupported auth providers are not rendered. +- Auth.js option includes scaffold messaging. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 7 completed: Create auth step + +Changes made: +- Added Auth step UI. +- Added auth selector for none and Auth.js credentials scaffold. +- Used @launchkit/schema metadata where available. +- Connected auth selection to shared builder config state. +- Confirmed Auth.js credentials can be selected without forcing a database. +- Added concise scaffold messaging. +- Confirmed unsupported auth providers are not exposed. + +Files changed: +- apps/web/components/builder/steps/auth-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/steps.ts, if changed +- apps/web/lib/builder/builder-state.ts, if changed +- apps/web/lib/builder/validation.ts, if changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 8: Create extras step +``` + +## Completion Criteria + +This step is complete when: + +- Auth step renders in the wizard. +- Auth selector supports `none` and `authjs-credentials`. +- Selecting an auth option updates builder config state. +- Auth.js credentials can be selected without PostgreSQL. +- Auth.js credentials does not force database or ORM changes. +- Auth.js credentials option has clear scaffold messaging. +- Unsupported auth providers are not exposed. +- Current config remains valid according to `@launchkit/schema`. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-8.md b/.agents/prompts/phase-06/step-8.md new file mode 100644 index 0000000..4bf4023 --- /dev/null +++ b/.agents/prompts/phase-06/step-8.md @@ -0,0 +1,345 @@ +# Phase 6 Step 8: Create Extras Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 7 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 9. +Do not implement preview or download steps yet. +Do not implement the API generate route yet. +Do not implement zip download behavior yet. +Do not add unsupported extras. +Do not add CLI functionality. +Do not put generator logic in `apps/web`. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Extras step in the LaunchKit website wizard. + +For the MVP, Extras controls the optional Docker Compose setup for local PostgreSQL development: + +```txt +docker: "none" +docker: "postgres" +``` + +Docker PostgreSQL should only be available when: + +```txt +database: "postgres" +``` + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/extras-step.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/steps.ts +apps/web/lib/builder/builder-state.ts +apps/web/lib/builder/validation.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Add Docker Selector + +Add a selector for: + +```ts +config.docker; +``` + +Supported values: + +```txt +none +postgres +``` + +Use metadata from: + +```txt +@launchkit/schema +``` + +where available: + +```txt +dockerMetadata +``` + +Recommended UI: + +- Segmented control +- Radio group +- Selectable option rows +- Toggle with clear label + +Do not use a plain text input. + +### 2. PostgreSQL Dependency + +Docker PostgreSQL requires PostgreSQL. + +If: + +```txt +config.database !== "postgres" +``` + +then the Docker PostgreSQL option should be disabled or unavailable. + +Recommended behavior: + +- Show `No Docker setup` as selected. +- Show Docker PostgreSQL as disabled with a concise reason: `Requires PostgreSQL`. +- Keep `config.docker` as `"none"`. + +Do not silently enable PostgreSQL when Docker PostgreSQL is selected. + +### 3. State Updates + +When the user changes the Docker option: + +- Update `config.docker`. +- Preserve all other builder config values. + +If selecting Docker PostgreSQL: + +- Only allow it when `config.database === "postgres"`. +- Keep `database` unchanged. + +If selecting none: + +- Set `config.docker` to `"none"`. +- Preserve database, ORM, auth, and UI selections. + +### 4. Compatibility + +The schema should already have this compatibility rule: + +```txt +PostgreSQL Docker Compose is only available when PostgreSQL is selected. +``` + +Use schema helpers from: + +```txt +@launchkit/schema +``` + +to validate compatibility if the builder shell already supports compatibility issue display. + +Do not duplicate compatibility rules manually except for UI affordances such as disabling Docker PostgreSQL when PostgreSQL is not selected. + +### 5. Do Not Implement Later Steps + +Do not add: + +```txt +preview generation +download generation +API route +zip creation +``` + +Those belong to later Phase 6 steps. + +### 6. Navigation Behavior + +The user should be able to move Back and Next when: + +```txt +docker: "none" +``` + +or: + +```txt +database: "postgres" +docker: "postgres" +``` + +If the current config somehow has: + +```txt +database: "none" +docker: "postgres" +``` + +show a concise error and prevent moving forward. + +### 7. Visual Direction + +Keep the UI practical and compact. + +Recommended presentation: + +- Two selectable rows: "No Docker setup" and "PostgreSQL Docker Compose". +- Short descriptions from schema metadata. +- Disabled state for Docker PostgreSQL when PostgreSQL is not selected. +- Small note that Docker Compose is for local development. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +RadioGroup +Badge +Button +Separator +Switch +``` + +Do not nest cards inside cards. + +### 8. Generator Boundary + +Do not call: + +```txt +@launchkit/generator +``` + +This step only renders and updates builder state. + +The API route and generator integration belong to later Phase 6 steps. + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Extras step renders `none` and Docker PostgreSQL options. +- Docker PostgreSQL option is disabled when database is `none`. +- Docker PostgreSQL option is enabled when database is `postgres`. +- Selecting Docker PostgreSQL updates `config.docker` only when PostgreSQL is selected. +- Selecting no Docker updates `config.docker`. +- Unsupported extras are not rendered. +- Invalid config with Docker PostgreSQL and no PostgreSQL prevents moving forward. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 8 completed: Create extras step + +Changes made: +- Added Extras step UI. +- Added Docker selector for none and PostgreSQL Docker Compose. +- Used @launchkit/schema metadata where available. +- Connected Docker selection to shared builder config state. +- Disabled Docker PostgreSQL unless PostgreSQL is selected. +- Confirmed unsupported extras are not exposed. + +Files changed: +- apps/web/components/builder/steps/extras-step.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/steps.ts, if changed +- apps/web/lib/builder/builder-state.ts, if changed +- apps/web/lib/builder/validation.ts, if changed +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 9: Create preview step +``` + +## Completion Criteria + +This step is complete when: + +- Extras step renders in the wizard. +- Docker selector supports `none` and `postgres`. +- Docker PostgreSQL is disabled or unavailable when PostgreSQL is not selected. +- Docker PostgreSQL can be selected when PostgreSQL is selected. +- Selecting a Docker option updates builder config state. +- Unsupported extras are not exposed. +- Current config remains valid according to `@launchkit/schema`. +- No generator logic is placed in `apps/web`. +- No API route or download flow is implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/.agents/prompts/phase-06/step-9.md b/.agents/prompts/phase-06/step-9.md new file mode 100644 index 0000000..276276e --- /dev/null +++ b/.agents/prompts/phase-06/step-9.md @@ -0,0 +1,359 @@ +# Phase 6 Step 9: Create Preview Step + +You are working on LaunchKit. + +Before making changes: + +1. Read all files in `context/` in order. +2. Read `progress-tracker.md`. +3. Confirm that Phase 6 Step 8 is complete. +4. Read this step prompt. +5. Implement only this step. + +Do not move to Phase 6 Step 10. +Do not implement the API generate route yet unless the existing architecture already exposes a safe server-side preview helper. +Do not implement zip download behavior yet. +Do not add CLI functionality. +Do not put generator logic directly in UI components. +Use npm workspaces. +Use Vitest for tests. +Do not use Node's built-in test runner. + +## Goal + +Implement the Preview step in the LaunchKit website wizard. + +The Preview step should help users inspect what will be generated before download. + +It should show: + +```txt +Selected stack summary +Dependencies +Dev dependencies +Scripts +Environment variables +Generated file tree +``` + +Full file content preview is optional later and should not be implemented in this step unless the existing generator already makes it trivial. + +## Scope + +Work inside: + +```txt +apps/web/ +``` + +Recommended files: + +```txt +apps/web/components/builder/steps/preview-step.tsx +apps/web/components/builder/preview/stack-summary.tsx +apps/web/components/builder/preview/dependency-list.tsx +apps/web/components/builder/preview/script-list.tsx +apps/web/components/builder/preview/env-var-list.tsx +apps/web/components/builder/preview/file-tree-preview.tsx +apps/web/components/builder/builder-shell.tsx +apps/web/lib/builder/preview.ts +``` + +Adjust paths to match the existing app structure and conventions. + +## Requirements + +### 1. Selected Stack Summary + +Show a compact summary of the current builder config: + +```txt +Project name +Framework +Language +Router +Project structure +Styling +UI +Database +ORM +Auth +Docker +Package manager +``` + +Use labels from `@launchkit/schema` metadata where available. + +Do not expose raw enum values as the primary display text if metadata labels exist. + +### 2. Dependencies and Dev Dependencies + +Show dependencies that will be included in the generated project. + +Preferred source: + +```txt +@launchkit/generator +``` + +If the generator exports a safe planning helper such as: + +```ts +createGenerationPlan(config); +``` + +or similar, use it to derive dependencies and dev dependencies. + +Do not duplicate package dependency logic manually in `apps/web` if generator plan data exists. + +If the current generator only exposes async generation, do not call it directly from a client component if it depends on server-only APIs. In that case, create a small server-safe preview helper or defer full generator integration to Step 10 and show a schema-based selected stack summary only, documenting the limitation in `progress-tracker.md`. + +### 3. Scripts + +Show generated `package.json` scripts. + +Examples: + +```txt +dev +build +start +typecheck +db:generate +db:push +db:studio +``` + +Only show scripts that are actually contributed by the selected config. + +Use generator plan data if available. + +### 4. Environment Variables + +Show generated environment variables. + +Examples: + +```txt +DATABASE_URL +AUTH_SECRET +``` + +Do not display real secrets. + +Do not imply generated secrets are production-ready. + +Use generator plan data if available. + +### 5. Generated File Tree + +Show a generated file tree preview. + +Examples: + +```txt +app/layout.tsx +app/page.tsx +app/globals.css +components/ui/button.tsx +lib/utils.ts +lib/db.ts +prisma/schema.prisma +docker-compose.yml +package.json +.env.example +README.md +``` + +Only show files that match the selected config. + +Do not show files for unselected optional features. + +No generated file tree should include: + +```txt +src/ +``` + +### 6. Compatibility and Errors + +Before showing the preview, validate the current config with: + +```txt +@launchkit/schema +``` + +If there are schema or compatibility errors: + +- Show concise errors. +- Disable moving to the Download step. +- Do not attempt generation. + +### 7. Client/Server Boundary + +Do not place Node-only generator logic in client components. + +Acceptable approaches: + +- Use pure exported generator planning helpers if they are browser-safe. +- Use a server component wrapper if the route architecture supports it. +- Use a server action or API route only if it already exists and is in scope. + +Do not create the final generate/download API route in this step. That belongs to Step 10. + +### 8. Visual Direction + +Keep the preview dense, structured, and scannable. + +Recommended presentation: + +- Stack summary section. +- Dependencies and dev dependencies in compact lists. +- Scripts in a compact list. +- Environment variables in a compact list. +- File tree in a monospace block or structured tree. + +Do not use a marketing layout. +Do not use a large hero. +Do not use decorative gradient blobs/orbs. +Do not nest cards inside cards. + +Use token-based classes: + +```txt +bg-background +text-foreground +bg-primary +text-primary-foreground +border-border +text-muted-foreground +ring-ring +bg-accent +``` + +Avoid repeated hardcoded color utilities: + +```txt +bg-green-500 +text-emerald-600 +border-lime-400 +``` + +Use existing shadcn/ui components if available: + +```txt +Tabs +Badge +Separator +ScrollArea +Alert +``` + +## Tests + +Use the test setup already present in the repo. + +Add or update focused tests only if the app already has a frontend test pattern. + +Possible tests: + +- Preview step renders selected stack summary. +- Preview step uses schema metadata labels where available. +- Preview step shows dependencies for selected features. +- Preview step shows env vars for PostgreSQL/Auth.js selections. +- Preview step shows Prisma files only when Prisma is selected. +- Preview step shows Docker files only when Docker PostgreSQL is selected. +- Preview step does not show `src/`. +- Invalid config prevents moving to Download. + +If the repo does not yet have frontend component testing configured, do not add a large new test stack in this step. Document that in `progress-tracker.md`. + +## Verification + +Run the relevant checks available in the repo. + +Recommended: + +```bash +npm run typecheck +npm test +npm run lint +npm run build +``` + +If app-specific commands exist, also run: + +```bash +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +``` + +Use the actual workspace command names from the repo. + +If a command does not exist, record that clearly in `progress-tracker.md`. + +## Progress Tracker Update + +After implementation, update `progress-tracker.md` with: + +```txt +Phase 6 Step 9 completed: Create preview step + +Changes made: +- Added Preview step UI. +- Added selected stack summary. +- Added dependencies/dev dependencies preview. +- Added scripts preview. +- Added environment variables preview. +- Added generated file tree preview. +- Added compatibility/error display for preview. +- Confirmed preview does not implement download behavior. + +Files changed: +- apps/web/components/builder/steps/preview-step.tsx +- apps/web/components/builder/preview/stack-summary.tsx +- apps/web/components/builder/preview/dependency-list.tsx +- apps/web/components/builder/preview/script-list.tsx +- apps/web/components/builder/preview/env-var-list.tsx +- apps/web/components/builder/preview/file-tree-preview.tsx +- apps/web/components/builder/builder-shell.tsx +- apps/web/lib/builder/preview.ts +- relevant test files, if added or changed +- progress-tracker.md + +Commands run: +- ... + +Verification result: +- ... + +Notes/blockers: +- ... + +Next suggested step: +- Phase 6 Step 10: Create API generate route +``` + +## Completion Criteria + +This step is complete when: + +- Preview step renders in the wizard. +- Preview shows selected stack summary. +- Preview shows dependencies and dev dependencies when available. +- Preview shows scripts when available. +- Preview shows environment variables when available. +- Preview shows generated file tree. +- Preview excludes unselected optional feature files. +- Preview does not show any `src/` path. +- Invalid config shows concise errors and prevents moving to Download. +- Generator logic is not placed directly in client UI components. +- Final API generate route is not implemented yet. +- Zip download behavior is not implemented yet. +- TypeScript checks pass, or unrelated failures are documented. +- Tests pass if configured, or missing/unrelated failures are documented. +- Build/lint pass if configured, or missing/unrelated failures are documented. +- `progress-tracker.md` is updated. + +Then stop. diff --git a/apps/web/app/api/generate/route.ts b/apps/web/app/api/generate/route.ts new file mode 100644 index 0000000..b453a4e --- /dev/null +++ b/apps/web/app/api/generate/route.ts @@ -0,0 +1,12 @@ +import { + handleGenerateProjectRequest, + methodNotAllowedResponse, +} from "../../../lib/api/generate"; + +export async function POST(request: Request): Promise { + return handleGenerateProjectRequest(request); +} + +export function GET(): Response { + return methodNotAllowedResponse(); +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 32700d5..7a71755 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -2,8 +2,8 @@ import type { Metadata } from "next"; import "./globals.css"; export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "LaunchKit", + description: "Build and download TypeScript project starters.", }; export default function RootLayout({ diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 2abb688..451692d 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,7 +1,5 @@ +import { BuilderShell } from "@/components/builder/builder-shell"; + export default function Home() { - return ( -
- -
- ); + return ; } diff --git a/apps/web/components/builder/builder-shell.tsx b/apps/web/components/builder/builder-shell.tsx new file mode 100644 index 0000000..e26222a --- /dev/null +++ b/apps/web/components/builder/builder-shell.tsx @@ -0,0 +1,226 @@ +"use client"; + +import { useMemo, useState } from "react"; + +import { AuthStep } from "@/components/builder/steps/auth-step"; +import { DatabaseStep } from "@/components/builder/steps/database-step"; +import { DownloadStep } from "@/components/builder/steps/download-step"; +import { ExtrasStep } from "@/components/builder/steps/extras-step"; +import { FrameworkStep } from "@/components/builder/steps/framework-step"; +import { OrmStep } from "@/components/builder/steps/orm-step"; +import { PreviewStep } from "@/components/builder/steps/preview-step"; +import { ProjectStep } from "@/components/builder/steps/project-step"; +import { StylingUiStep } from "@/components/builder/steps/styling-ui-step"; +import { + createInitialBuilderState, + type BuilderConfigPatch, + updateBuilderConfig, +} from "@/lib/builder/builder-state"; +import { builderSteps } from "@/lib/builder/steps"; +import { + validateAuthStep, + validateDatabaseStep, + validateExtrasStep, + validateFrameworkStep, + validateOrmStep, + validatePreviewStep, + validateProjectStep, + validateStylingUiStep, +} from "@/lib/builder/validation"; +import { WizardNavigation } from "./wizard-navigation"; +import { WizardProgress } from "./wizard-progress"; +import { WizardStepPanel } from "./wizard-step-panel"; + +export function BuilderShell() { + const [builderState, setBuilderState] = useState(createInitialBuilderState); + const [currentStepIndex, setCurrentStepIndex] = useState(0); + + const currentStep = builderSteps[currentStepIndex]; + const isFirstStep = currentStepIndex === 0; + const isLastStep = currentStepIndex === builderSteps.length - 1; + const projectStepValidation = validateProjectStep(builderState.config); + const frameworkStepValidation = validateFrameworkStep(builderState.config); + const stylingUiStepValidation = validateStylingUiStep(builderState.config); + const databaseStepValidation = validateDatabaseStep(builderState.config); + const ormStepValidation = validateOrmStep(builderState.config); + const authStepValidation = validateAuthStep(builderState.config); + const extrasStepValidation = validateExtrasStep(builderState.config); + const previewStepValidation = validatePreviewStep(builderState.config); + const isProjectStep = currentStep.id === "project"; + const isFrameworkStep = currentStep.id === "framework"; + const isStylingUiStep = currentStep.id === "styling-ui"; + const isDatabaseStep = currentStep.id === "database"; + const isOrmStep = currentStep.id === "orm"; + const isAuthStep = currentStep.id === "auth"; + const isExtrasStep = currentStep.id === "extras"; + const isPreviewStep = currentStep.id === "preview"; + const isDownloadStep = currentStep.id === "download"; + const isNextDisabled = + (isProjectStep && !projectStepValidation.isValid) || + (isFrameworkStep && !frameworkStepValidation.isValid) || + (isStylingUiStep && !stylingUiStepValidation.isValid) || + (isDatabaseStep && !databaseStepValidation.isValid) || + (isOrmStep && !ormStepValidation.isValid) || + (isAuthStep && !authStepValidation.isValid) || + (isExtrasStep && !extrasStepValidation.isValid) || + (isPreviewStep && !previewStepValidation.isValid); + + const selectedStack = useMemo( + () => [ + ["Framework", builderState.config.framework], + ["Language", builderState.config.language], + ["Styling", builderState.config.styling], + ["UI", builderState.config.ui], + ["Database", builderState.config.database], + ["ORM", builderState.config.orm], + ["Auth", builderState.config.auth], + ["Docker", builderState.config.docker], + ["Package manager", builderState.config.packageManager], + ], + [builderState.config], + ); + + function goBack() { + setCurrentStepIndex((stepIndex) => Math.max(stepIndex - 1, 0)); + } + + function goNext() { + if (isNextDisabled) { + return; + } + + setCurrentStepIndex((stepIndex) => + Math.min(stepIndex + 1, builderSteps.length - 1), + ); + } + + function updateConfig(patch: BuilderConfigPatch) { + setBuilderState((state) => updateBuilderConfig(state, patch)); + } + + return ( +
+
+
+
+

+ LaunchKit +

+

+ Project builder +

+
+
+ {builderState.config.name} +
+
+ +
+ + +
+
+ + {isProjectStep ? ( + + ) : null} + {isFrameworkStep ? ( + + ) : null} + {isStylingUiStep ? ( + + ) : null} + {isDatabaseStep ? ( + + ) : null} + {isOrmStep ? ( + + ) : null} + {isAuthStep ? ( + + ) : null} + {isExtrasStep ? ( + + ) : null} + {isPreviewStep ? ( + + ) : null} + {isDownloadStep ? ( + + ) : null} + + +
+ + +
+
+
+
+ ); +} diff --git a/apps/web/components/builder/download/download-button.tsx b/apps/web/components/builder/download/download-button.tsx new file mode 100644 index 0000000..07c159a --- /dev/null +++ b/apps/web/components/builder/download/download-button.tsx @@ -0,0 +1,20 @@ +type DownloadButtonProps = { + isGenerating: boolean; + onClick: () => void; +}; + +export function DownloadButton({ + isGenerating, + onClick, +}: DownloadButtonProps) { + return ( + + ); +} diff --git a/apps/web/components/builder/download/download-status.tsx b/apps/web/components/builder/download/download-status.tsx new file mode 100644 index 0000000..9805399 --- /dev/null +++ b/apps/web/components/builder/download/download-status.tsx @@ -0,0 +1,38 @@ +type DownloadStatusProps = { + status: "idle" | "generating" | "success" | "error"; + message?: string; +}; + +export function DownloadStatus({ status, message }: DownloadStatusProps) { + if (status === "idle") { + return null; + } + + const isError = status === "error"; + + return ( +
+ {message ?? getDefaultMessage(status)} +
+ ); +} + +function getDefaultMessage(status: DownloadStatusProps["status"]): string { + if (status === "generating") { + return "Generating project..."; + } + + if (status === "success") { + return "Download started."; + } + + return ""; +} diff --git a/apps/web/components/builder/preview/dependency-list.tsx b/apps/web/components/builder/preview/dependency-list.tsx new file mode 100644 index 0000000..5fb6e02 --- /dev/null +++ b/apps/web/components/builder/preview/dependency-list.tsx @@ -0,0 +1,41 @@ +type DependencyEntry = { + name: string; + version: string; +}; + +type DependencyListProps = { + title: string; + dependencies: DependencyEntry[]; +}; + +export function DependencyList({ title, dependencies }: DependencyListProps) { + return ( +
+
+

{title}

+ + {dependencies.length} + +
+ {dependencies.length > 0 ? ( +
    + {dependencies.map((dependency) => ( +
  • + + {dependency.name} + + + {dependency.version} + +
  • + ))} +
+ ) : ( +

None

+ )} +
+ ); +} diff --git a/apps/web/components/builder/preview/env-var-list.tsx b/apps/web/components/builder/preview/env-var-list.tsx new file mode 100644 index 0000000..2082932 --- /dev/null +++ b/apps/web/components/builder/preview/env-var-list.tsx @@ -0,0 +1,43 @@ +import type { EnvVarPreviewItem } from "@/lib/builder/preview"; + +type EnvVarListProps = { + envVars: EnvVarPreviewItem[]; +}; + +export function EnvVarList({ envVars }: EnvVarListProps) { + return ( +
+

+ Environment variables +

+ {envVars.length > 0 ? ( +
    + {envVars.map((envVar) => ( +
  • +
    + + {envVar.name} + + {envVar.required ? ( + + Required + + ) : null} +
    + {envVar.description ? ( +

    + {envVar.description} +

    + ) : null} +
  • + ))} +
+ ) : ( +

None

+ )} +

+ Values are placeholders in `.env.example`; set real secrets yourself. +

+
+ ); +} diff --git a/apps/web/components/builder/preview/file-tree-preview.tsx b/apps/web/components/builder/preview/file-tree-preview.tsx new file mode 100644 index 0000000..03beb8f --- /dev/null +++ b/apps/web/components/builder/preview/file-tree-preview.tsx @@ -0,0 +1,51 @@ +type FileTreePreviewProps = { + projectName: string; + filePaths: string[]; +}; + +export function FileTreePreview({ + projectName, + filePaths, +}: FileTreePreviewProps) { + return ( +
+
+

+ Generated file tree +

+ + {filePaths.length} + +
+
+        {formatFileTree(projectName, filePaths)}
+      
+
+ ); +} + +function formatFileTree(projectName: string, filePaths: string[]): string { + const lines = [`${projectName}/`]; + const seenDirectories = new Set(); + + for (const filePath of filePaths) { + const segments = filePath.split("/"); + + segments.forEach((segment, index) => { + const isFile = index === segments.length - 1; + const directoryKey = segments.slice(0, index + 1).join("/"); + + if (!isFile) { + if (seenDirectories.has(directoryKey)) { + return; + } + + seenDirectories.add(directoryKey); + } + + lines.push(`${" ".repeat(index + 1)}${segment}${isFile ? "" : "/"}`); + }); + } + + return lines.join("\n"); +} diff --git a/apps/web/components/builder/preview/script-list.tsx b/apps/web/components/builder/preview/script-list.tsx new file mode 100644 index 0000000..68dd124 --- /dev/null +++ b/apps/web/components/builder/preview/script-list.tsx @@ -0,0 +1,29 @@ +import type { ScriptPreviewItem } from "@/lib/builder/preview"; + +type ScriptListProps = { + scripts: ScriptPreviewItem[]; +}; + +export function ScriptList({ scripts }: ScriptListProps) { + return ( +
+

Scripts

+ {scripts.length > 0 ? ( +
+ {scripts.map((script) => ( +
+
+ {script.name} +
+
+ {script.command} +
+
+ ))} +
+ ) : ( +

None

+ )} +
+ ); +} diff --git a/apps/web/components/builder/preview/stack-summary.tsx b/apps/web/components/builder/preview/stack-summary.tsx new file mode 100644 index 0000000..773d0b3 --- /dev/null +++ b/apps/web/components/builder/preview/stack-summary.tsx @@ -0,0 +1,29 @@ +import type { StackSummaryItem } from "@/lib/builder/preview"; + +type StackSummaryProps = { + items: StackSummaryItem[]; +}; + +export function StackSummary({ items }: StackSummaryProps) { + return ( +
+
+

+ Selected stack summary +

+
+
+ {items.map((item) => ( +
+
+ {item.label} +
+
+ {item.value} +
+
+ ))} +
+
+ ); +} diff --git a/apps/web/components/builder/steps/auth-step.tsx b/apps/web/components/builder/steps/auth-step.tsx new file mode 100644 index 0000000..204574d --- /dev/null +++ b/apps/web/components/builder/steps/auth-step.tsx @@ -0,0 +1,117 @@ +"use client"; + +import { + authMetadata, + type AuthOption, + type LaunchKitConfig, + type OptionMetadata, +} from "@launchkit/schema"; + +import type { BuilderConfigPatch } from "@/lib/builder/builder-state"; +import type { AuthStepValidation } from "@/lib/builder/validation"; + +type AuthStepProps = { + config: LaunchKitConfig; + validation: AuthStepValidation; + onConfigChange: (patch: BuilderConfigPatch) => void; +}; + +function getAuthOptionLabel(option: OptionMetadata): string { + if (option.value === "none") { + return "No auth"; + } + + return option.label; +} + +export function AuthStep({ + config, + validation, + onConfigChange, +}: AuthStepProps) { + const errorMessages = Object.values(validation.errors).filter(Boolean); + + function updateAuth(auth: AuthOption) { + onConfigChange({ auth }); + } + + return ( +
+ {errorMessages.length > 0 ? ( +
+ {errorMessages[0]} +
+ ) : null} + +
+ Auth +
+ {authMetadata.map((option) => { + const isSelected = config.auth === option.value; + const isAuthJsCredentials = option.value === "authjs-credentials"; + const isRecommended = + "recommended" in option && option.recommended; + + return ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/web/components/builder/steps/database-step.tsx b/apps/web/components/builder/steps/database-step.tsx new file mode 100644 index 0000000..b4f56f8 --- /dev/null +++ b/apps/web/components/builder/steps/database-step.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { + databaseMetadata, + type DatabaseOption, + type LaunchKitConfig, + type OptionMetadata, +} from "@launchkit/schema"; + +import type { BuilderConfigPatch } from "@/lib/builder/builder-state"; +import type { DatabaseStepValidation } from "@/lib/builder/validation"; + +type DatabaseStepProps = { + config: LaunchKitConfig; + validation: DatabaseStepValidation; + onConfigChange: (patch: BuilderConfigPatch) => void; +}; + +function getDatabaseOptionLabel(option: OptionMetadata): string { + if (option.value === "none") { + return "No database"; + } + + return option.label; +} + +export function DatabaseStep({ + config, + validation, + onConfigChange, +}: DatabaseStepProps) { + const errorMessages = Object.values(validation.errors).filter(Boolean); + + function updateDatabase(database: DatabaseOption) { + const patch: BuilderConfigPatch = { database }; + + if (database === "none") { + if (config.orm === "prisma") { + patch.orm = "none"; + } + + if (config.docker === "postgres") { + patch.docker = "none"; + } + } + + onConfigChange(patch); + } + + return ( +
+ {errorMessages.length > 0 ? ( +
+ {errorMessages[0]} +
+ ) : null} + +
+ + Database + +
+ {databaseMetadata.map((option) => { + const isSelected = config.database === option.value; + + return ( + + ); + })} +
+
+ +

+ Prisma and Docker are configured in later steps. +

+
+ ); +} diff --git a/apps/web/components/builder/steps/download-step.tsx b/apps/web/components/builder/steps/download-step.tsx new file mode 100644 index 0000000..28319e4 --- /dev/null +++ b/apps/web/components/builder/steps/download-step.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { useMemo, useState } from "react"; + +import type { LaunchKitConfig } from "@launchkit/schema"; + +import { DownloadButton } from "@/components/builder/download/download-button"; +import { DownloadStatus } from "@/components/builder/download/download-status"; +import { + GenerateProjectApiError, + generateProjectRequest, +} from "@/lib/api/client"; +import { createBuilderPreview } from "@/lib/builder/preview"; +import type { PreviewStepValidation } from "@/lib/builder/validation"; +import { createProjectZip } from "@/lib/download/create-project-zip"; + +type DownloadStatusState = "idle" | "generating" | "success" | "error"; + +type DownloadStepProps = { + config: LaunchKitConfig; + validation: PreviewStepValidation; +}; + +export function DownloadStep({ config, validation }: DownloadStepProps) { + const [status, setStatus] = useState("idle"); + const [message, setMessage] = useState(); + const preview = useMemo(() => createBuilderPreview(config), [config]); + const summaryItems = preview.stackSummary.filter((item) => + ["Framework", "UI", "Database", "ORM", "Auth", "Docker"].includes( + item.label, + ), + ); + const isGenerating = status === "generating"; + + async function downloadProject() { + if (!validation.isValid) { + setStatus("error"); + setMessage(getValidationMessage(validation.errors)); + return; + } + + setStatus("generating"); + setMessage("Generating project..."); + + try { + const response = await generateProjectRequest(config); + const zipBlob = await createProjectZip(response.project); + + triggerBrowserDownload(zipBlob, `${response.project.name}.zip`); + setStatus("success"); + setMessage("Download started."); + } catch (error) { + setStatus("error"); + setMessage(getErrorMessage(error)); + } + } + + return ( +
+
+

+ Project summary +

+
+
+
+ Project name +
+
+ {config.name} +
+
+
+
+ Package manager +
+
+ {config.packageManager} +
+
+
+
+ +
+

Selected stack

+
+ {summaryItems.map((item) => ( +
+
+ {item.label} +
+
+ {item.value} +
+
+ ))} +
+
+ +
+

+ Creates `{config.name}.zip` in your browser. Generated code is not run + or installed. +

+ void downloadProject()} + /> +
+ + +
+ ); +} + +function getValidationMessage( + errors: PreviewStepValidation["errors"], +): string { + return ( + Object.values(errors).find((error): error is string => Boolean(error)) ?? + "Fix the selected stack before generating." + ); +} + +function getErrorMessage(error: unknown): string { + if (error instanceof GenerateProjectApiError) { + return error.message; + } + + if (error instanceof Error && error.name === "UnsafeZipPathError") { + return "Generated project contained unsafe file paths."; + } + + return "Could not create the ZIP file."; +} + +function triggerBrowserDownload(blob: Blob, fileName: string): void { + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + + anchor.href = url; + anchor.download = fileName; + anchor.rel = "noopener"; + document.body.append(anchor); + anchor.click(); + anchor.remove(); + URL.revokeObjectURL(url); +} diff --git a/apps/web/components/builder/steps/extras-step.tsx b/apps/web/components/builder/steps/extras-step.tsx new file mode 100644 index 0000000..3e1dc7a --- /dev/null +++ b/apps/web/components/builder/steps/extras-step.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { + dockerMetadata, + type DockerOption, + type LaunchKitConfig, + type OptionMetadata, +} from "@launchkit/schema"; + +import type { BuilderConfigPatch } from "@/lib/builder/builder-state"; +import type { ExtrasStepValidation } from "@/lib/builder/validation"; + +type ExtrasStepProps = { + config: LaunchKitConfig; + validation: ExtrasStepValidation; + onConfigChange: (patch: BuilderConfigPatch) => void; +}; + +function getDockerOptionLabel(option: OptionMetadata): string { + if (option.value === "none") { + return "No Docker setup"; + } + + return option.label; +} + +export function ExtrasStep({ + config, + validation, + onConfigChange, +}: ExtrasStepProps) { + const errorMessages = Object.values(validation.errors).filter(Boolean); + const hasPostgres = config.database === "postgres"; + const selectedDocker: DockerOption = hasPostgres ? config.docker : "none"; + + function updateDocker(docker: DockerOption) { + if (docker === "postgres" && !hasPostgres) { + return; + } + + onConfigChange({ docker }); + } + + return ( +
+ {errorMessages.length > 0 ? ( +
+ {errorMessages[0]} +
+ ) : null} + +
+ Docker +
+ {dockerMetadata.map((option) => { + const isPostgresDocker = option.value === "postgres"; + const isDisabled = isPostgresDocker && !hasPostgres; + const isSelected = selectedDocker === option.value; + + return ( + + ); + })} +
+
+ +

+ Docker Compose is for local PostgreSQL development. README and + `.env.example` are included by default. +

+
+ ); +} diff --git a/apps/web/components/builder/steps/framework-step.tsx b/apps/web/components/builder/steps/framework-step.tsx new file mode 100644 index 0000000..36144fe --- /dev/null +++ b/apps/web/components/builder/steps/framework-step.tsx @@ -0,0 +1,107 @@ +import { + frameworkMetadata, + languageMetadata, + projectStructureMetadata, + routerMetadata, + type LaunchKitConfig, + type OptionMetadata, +} from "@launchkit/schema"; + +import type { FrameworkStepValidation } from "@/lib/builder/validation"; + +type FrameworkStepProps = { + config: LaunchKitConfig; + validation: FrameworkStepValidation; +}; + +type FrameworkSummaryItem = { + label: string; + value: string; + metadata: OptionMetadata; +}; + +function findMetadata( + metadata: readonly OptionMetadata[], + value: TValue, +): OptionMetadata { + return metadata.find((item) => item.value === value) ?? metadata[0]; +} + +export function FrameworkStep({ config, validation }: FrameworkStepProps) { + const summaryItems: FrameworkSummaryItem[] = [ + { + label: "Framework", + value: config.framework, + metadata: findMetadata(frameworkMetadata, config.framework), + }, + { + label: "Language", + value: config.language, + metadata: findMetadata(languageMetadata, config.language), + }, + { + label: "Router", + value: config.router, + metadata: findMetadata(routerMetadata, config.router), + }, + { + label: "Project structure", + value: config.projectStructure, + metadata: findMetadata(projectStructureMetadata, config.projectStructure), + }, + ]; + + const errorMessages = Object.values(validation.errors).filter(Boolean); + + return ( +
+
+

+ Fixed MVP foundation +

+

+ Generated projects start from one supported Next.js TypeScript setup. +

+
+ + {errorMessages.length > 0 ? ( +
+ {errorMessages[0]} +
+ ) : null} + +
+ {summaryItems.map((item) => ( +
+
+
+

+ {item.label} +

+

+ {item.metadata.label} +

+
+ + Selected + +
+

+ {item.metadata.description} +

+
+ ))} +
+ +

+ Additional foundations can be added after the MVP. +

+
+ ); +} diff --git a/apps/web/components/builder/steps/orm-step.tsx b/apps/web/components/builder/steps/orm-step.tsx new file mode 100644 index 0000000..ef8f3b0 --- /dev/null +++ b/apps/web/components/builder/steps/orm-step.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { + ormMetadata, + type LaunchKitConfig, + type OptionMetadata, + type OrmOption, +} from "@launchkit/schema"; + +import type { BuilderConfigPatch } from "@/lib/builder/builder-state"; +import type { OrmStepValidation } from "@/lib/builder/validation"; + +type OrmStepProps = { + config: LaunchKitConfig; + validation: OrmStepValidation; + onConfigChange: (patch: BuilderConfigPatch) => void; +}; + +function getOrmOptionLabel(option: OptionMetadata): string { + if (option.value === "none") { + return "No ORM"; + } + + return option.label; +} + +export function OrmStep({ + config, + validation, + onConfigChange, +}: OrmStepProps) { + const errorMessages = Object.values(validation.errors).filter(Boolean); + const hasPostgres = config.database === "postgres"; + const selectedOrm: OrmOption = hasPostgres ? config.orm : "none"; + + function updateOrm(orm: OrmOption) { + if (orm === "prisma" && !hasPostgres) { + return; + } + + onConfigChange({ orm }); + } + + return ( +
+ {errorMessages.length > 0 ? ( +
+ {errorMessages[0]} +
+ ) : null} + +
+ ORM +
+ {ormMetadata.map((option) => { + const isPrisma = option.value === "prisma"; + const isDisabled = isPrisma && !hasPostgres; + const isSelected = selectedOrm === option.value; + + return ( + + ); + })} +
+
+ +

+ Prisma is available after PostgreSQL is selected in the Database step. +

+
+ ); +} diff --git a/apps/web/components/builder/steps/preview-step.tsx b/apps/web/components/builder/steps/preview-step.tsx new file mode 100644 index 0000000..eaceb9b --- /dev/null +++ b/apps/web/components/builder/steps/preview-step.tsx @@ -0,0 +1,57 @@ +"use client"; + +import type { LaunchKitConfig } from "@launchkit/schema"; + +import { DependencyList } from "@/components/builder/preview/dependency-list"; +import { EnvVarList } from "@/components/builder/preview/env-var-list"; +import { FileTreePreview } from "@/components/builder/preview/file-tree-preview"; +import { ScriptList } from "@/components/builder/preview/script-list"; +import { StackSummary } from "@/components/builder/preview/stack-summary"; +import { createBuilderPreview } from "@/lib/builder/preview"; +import type { PreviewStepValidation } from "@/lib/builder/validation"; + +type PreviewStepProps = { + config: LaunchKitConfig; + validation: PreviewStepValidation; +}; + +export function PreviewStep({ config, validation }: PreviewStepProps) { + const errorMessages = Object.values(validation.errors).filter(Boolean); + + if (!validation.isValid) { + return ( +
+ {errorMessages[0] ?? "Fix the selected stack before previewing."} +
+ ); + } + + const preview = createBuilderPreview(config); + + return ( +
+ +
+ + +
+
+ + +
+ +
+ ); +} diff --git a/apps/web/components/builder/steps/project-step.tsx b/apps/web/components/builder/steps/project-step.tsx new file mode 100644 index 0000000..0f28ea5 --- /dev/null +++ b/apps/web/components/builder/steps/project-step.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useState } from "react"; + +import { + packageManagerMetadata, + type LaunchKitConfig, + type PackageManagerOption, +} from "@launchkit/schema"; + +import type { BuilderConfigPatch } from "@/lib/builder/builder-state"; +import type { ProjectStepValidation } from "@/lib/builder/validation"; + +type ProjectStepProps = { + config: LaunchKitConfig; + validation: ProjectStepValidation; + onConfigChange: (patch: BuilderConfigPatch) => void; +}; + +export function ProjectStep({ + config, + validation, + onConfigChange, +}: ProjectStepProps) { + const [nameTouched, setNameTouched] = useState(false); + const showNameError = nameTouched && validation.errors.name; + + function updateProjectName(name: string) { + setNameTouched(true); + onConfigChange({ name }); + } + + function updatePackageManager(packageManager: PackageManagerOption) { + onConfigChange({ packageManager }); + } + + return ( +
+
+ + setNameTouched(true)} + onChange={(event) => updateProjectName(event.target.value)} + aria-invalid={showNameError ? "true" : "false"} + aria-describedby="project-name-help project-name-error" + className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" + placeholder="my-app" + /> +

+ Use lowercase letters, numbers, and hyphen-separated words. +

+

+ {showNameError ? validation.errors.name : ""} +

+
+ +
+ Package manager +
+ {packageManagerMetadata.map((option) => { + const isSelected = config.packageManager === option.value; + + return ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/web/components/builder/steps/styling-ui-step.tsx b/apps/web/components/builder/steps/styling-ui-step.tsx new file mode 100644 index 0000000..532d891 --- /dev/null +++ b/apps/web/components/builder/steps/styling-ui-step.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { + stylingMetadata, + uiMetadata, + type LaunchKitConfig, + type OptionMetadata, + type UiOption, +} from "@launchkit/schema"; + +import type { BuilderConfigPatch } from "@/lib/builder/builder-state"; +import type { StylingUiStepValidation } from "@/lib/builder/validation"; + +type StylingUiStepProps = { + config: LaunchKitConfig; + validation: StylingUiStepValidation; + onConfigChange: (patch: BuilderConfigPatch) => void; +}; + +function findMetadata( + metadata: readonly OptionMetadata[], + value: TValue, +): OptionMetadata { + return metadata.find((item) => item.value === value) ?? metadata[0]; +} + +function getUiOptionLabel(option: OptionMetadata): string { + if (option.value === "none") { + return "No component library"; + } + + return option.label; +} + +export function StylingUiStep({ + config, + validation, + onConfigChange, +}: StylingUiStepProps) { + const styling = findMetadata(stylingMetadata, config.styling); + const errorMessages = Object.values(validation.errors).filter(Boolean); + + function updateUi(ui: UiOption) { + onConfigChange({ styling: "tailwind", ui }); + } + + return ( +
+
+
+
+

+ Fixed styling +

+

{styling.label}

+
+ + Selected + +
+

+ {styling.description} +

+
+ + {errorMessages.length > 0 ? ( +
+ {errorMessages[0]} +
+ ) : null} + +
+ + UI library + +
+ {uiMetadata.map((option) => { + const isSelected = config.ui === option.value; + + return ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/web/components/builder/wizard-navigation.tsx b/apps/web/components/builder/wizard-navigation.tsx new file mode 100644 index 0000000..3aca3b1 --- /dev/null +++ b/apps/web/components/builder/wizard-navigation.tsx @@ -0,0 +1,36 @@ +type WizardNavigationProps = { + isFirstStep: boolean; + isLastStep: boolean; + isNextDisabled?: boolean; + onBack: () => void; + onNext: () => void; +}; + +export function WizardNavigation({ + isFirstStep, + isLastStep, + isNextDisabled = false, + onBack, + onNext, +}: WizardNavigationProps) { + return ( +
+ + +
+ ); +} diff --git a/apps/web/components/builder/wizard-progress.tsx b/apps/web/components/builder/wizard-progress.tsx new file mode 100644 index 0000000..6bb98e0 --- /dev/null +++ b/apps/web/components/builder/wizard-progress.tsx @@ -0,0 +1,48 @@ +import { builderSteps, type BuilderStepId } from "@/lib/builder/steps"; + +type WizardProgressProps = { + currentStepId: BuilderStepId; +}; + +export function WizardProgress({ currentStepId }: WizardProgressProps) { + const currentIndex = builderSteps.findIndex( + (step) => step.id === currentStepId, + ); + + return ( + + ); +} diff --git a/apps/web/components/builder/wizard-step-panel.tsx b/apps/web/components/builder/wizard-step-panel.tsx new file mode 100644 index 0000000..8c14c28 --- /dev/null +++ b/apps/web/components/builder/wizard-step-panel.tsx @@ -0,0 +1,43 @@ +import type { BuilderStep } from "@/lib/builder/steps"; +import type { ReactNode } from "react"; + +type WizardStepPanelProps = { + step: BuilderStep; + stepNumber: number; + totalSteps: number; + children?: ReactNode; +}; + +export function WizardStepPanel({ + step, + stepNumber, + totalSteps, + children, +}: WizardStepPanelProps) { + return ( +
+
+
+

+ Step {stepNumber} of {totalSteps} +

+

+ {step.label} +

+
+ + {step.shortLabel} + +
+ {children ?? ( +

{step.placeholder}

+ )} +
+ ); +} diff --git a/apps/web/lib/api/client.test.ts b/apps/web/lib/api/client.test.ts new file mode 100644 index 0000000..416f5f0 --- /dev/null +++ b/apps/web/lib/api/client.test.ts @@ -0,0 +1,69 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { defaultLaunchKitConfig } from "@launchkit/schema"; + +import { + GenerateProjectApiError, + generateProjectRequest, +} from "./client"; + +describe("generateProjectRequest", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("returns generated project data from a successful response", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue( + Response.json({ + project: { + name: "my-app", + packageManager: "npm", + files: [], + }, + }), + ), + ); + + await expect(generateProjectRequest(defaultLaunchKitConfig)).resolves.toEqual({ + project: { + name: "my-app", + packageManager: "npm", + files: [], + }, + }); + expect(fetch).toHaveBeenCalledWith( + "/api/generate", + expect.objectContaining({ + method: "POST", + body: JSON.stringify(defaultLaunchKitConfig), + }), + ); + }); + + it("throws structured API errors for non-2xx responses", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue( + Response.json( + { + error: { + code: "invalid_config", + message: "Request body must be a valid LaunchKit config.", + issues: [{ path: ["name"] }], + }, + }, + { status: 400 }, + ), + ), + ); + + await expect(generateProjectRequest(defaultLaunchKitConfig)).rejects.toMatchObject({ + code: "invalid_config", + status: 400, + message: "Request body must be a valid LaunchKit config.", + issues: [{ path: ["name"] }], + } satisfies Partial); + }); +}); diff --git a/apps/web/lib/api/client.ts b/apps/web/lib/api/client.ts new file mode 100644 index 0000000..15b3477 --- /dev/null +++ b/apps/web/lib/api/client.ts @@ -0,0 +1,51 @@ +import type { LaunchKitConfig } from "@launchkit/schema"; + +import type { ApiErrorResponse, GenerateProjectResponse } from "./types"; + +export class GenerateProjectApiError extends Error { + readonly code: string; + readonly status: number; + readonly issues?: unknown[]; + + constructor(input: { + code: string; + message: string; + status: number; + issues?: unknown[]; + }) { + super(input.message); + this.name = "GenerateProjectApiError"; + this.code = input.code; + this.status = input.status; + this.issues = input.issues; + } +} + +export async function generateProjectRequest( + config: LaunchKitConfig, +): Promise { + const response = await fetch("/api/generate", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(config), + }); + + const body = (await response.json()) as + | GenerateProjectResponse + | ApiErrorResponse; + + if (!response.ok) { + const error = "error" in body ? body.error : undefined; + + throw new GenerateProjectApiError({ + status: response.status, + code: error?.code ?? "request_failed", + message: error?.message ?? "Project generation failed.", + issues: error?.issues, + }); + } + + return body as GenerateProjectResponse; +} diff --git a/apps/web/lib/api/generate.test.ts b/apps/web/lib/api/generate.test.ts new file mode 100644 index 0000000..90f83bf --- /dev/null +++ b/apps/web/lib/api/generate.test.ts @@ -0,0 +1,226 @@ +import { describe, expect, it } from "vitest"; + +import { defaultLaunchKitConfig, type LaunchKitConfig } from "@launchkit/schema"; + +import { + MAX_GENERATE_REQUEST_BYTES, + handleGenerateProjectRequest, + serializeGeneratedProject, +} from "./generate"; + +describe("generate API helpers", () => { + it("returns generated project data for a valid config", async () => { + const response = await handleGenerateProjectRequest(jsonRequest(defaultLaunchKitConfig)); + const body = await readJson(response); + + expect(response.status).toBe(200); + expect(body.project).toMatchObject({ + name: defaultLaunchKitConfig.name, + packageManager: defaultLaunchKitConfig.packageManager, + }); + expect(body.project!.files).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + path: "app/layout.tsx", + encoding: "utf8", + }), + expect.objectContaining({ + path: "package.json", + encoding: "utf8", + }), + ]), + ); + }); + + it("returns 400 for invalid configs", async () => { + const response = await handleGenerateProjectRequest( + jsonRequest({ + ...defaultLaunchKitConfig, + name: "Invalid Name", + }), + ); + const body = await readJson(response); + + expect(response.status).toBe(400); + expect(body.error).toMatchObject({ + code: "invalid_config", + }); + expect(body.error!.issues).toEqual(expect.any(Array)); + }); + + it("returns 422 for incompatible configs", async () => { + const response = await handleGenerateProjectRequest( + jsonRequest({ + ...defaultLaunchKitConfig, + database: "none", + orm: "prisma", + }), + ); + const body = await readJson(response); + + expect(response.status).toBe(422); + expect(body.error).toMatchObject({ + code: "incompatible_config", + }); + expect(body.error!.issues).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: "prisma_requires_postgresql", + }), + ]), + ); + }); + + it("returns 400 for malformed JSON", async () => { + const response = await handleGenerateProjectRequest( + new Request("http://localhost/api/generate", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: "{", + }), + ); + const body = await readJson(response); + + expect(response.status).toBe(400); + expect(body.error).toMatchObject({ + code: "invalid_json", + }); + }); + + it("returns 400 for oversized request bodies", async () => { + const response = await handleGenerateProjectRequest( + new Request("http://localhost/api/generate", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + ...defaultLaunchKitConfig, + extra: "x".repeat(MAX_GENERATE_REQUEST_BYTES), + }), + }), + ); + const body = await readJson(response); + + expect(response.status).toBe(400); + expect(body.error).toMatchObject({ + code: "request_too_large", + }); + }); + + it("returns 400 for non-JSON requests", async () => { + const response = await handleGenerateProjectRequest( + new Request("http://localhost/api/generate", { + method: "POST", + headers: { + "content-type": "text/plain", + }, + body: JSON.stringify(defaultLaunchKitConfig), + }), + ); + const body = await readJson(response); + + expect(response.status).toBe(400); + expect(body.error).toMatchObject({ + code: "unsupported_content_type", + }); + }); + + it("rejects unsafe generated paths before response serialization", () => { + expect(() => + serializeGeneratedProject({ + name: "unsafe", + packageManager: "npm", + files: [ + { + path: "../outside.txt", + contents: "bad", + }, + ], + }), + ).toThrow("Unsafe generated path"); + }); + + it("rejects generated src directory paths before response serialization", () => { + expect(() => + serializeGeneratedProject({ + name: "unsafe", + packageManager: "npm", + files: [ + { + path: "src/app/page.tsx", + contents: "bad", + }, + ], + }), + ).toThrow("Unsafe generated path"); + }); + + it("returns structured 500 errors without stack traces", async () => { + const response = await handleGenerateProjectRequest( + jsonRequest(defaultLaunchKitConfig), + { + generate: async () => { + throw new Error("/internal/path/secret.ts exploded"); + }, + }, + ); + const body = await readJson(response); + + expect(response.status).toBe(500); + expect(body.error).toEqual({ + code: "generation_failed", + message: "Project generation failed.", + }); + }); + + it("serializes binary file contents as base64", () => { + const project = serializeGeneratedProject({ + name: "binary-demo", + packageManager: "pnpm", + files: [ + { + path: "public/icon.bin", + contents: new Uint8Array([1, 2, 3]), + }, + ], + }); + + expect(project.files).toEqual([ + { + path: "public/icon.bin", + contents: "AQID", + encoding: "base64", + }, + ]); + }); +}); + +function jsonRequest(config: LaunchKitConfig | Record): Request { + return new Request("http://localhost/api/generate", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(config), + }); +} + +type TestJsonBody = { + project?: { + name?: string; + packageManager?: string; + files?: unknown[]; + }; + error?: { + code?: string; + message?: string; + issues?: unknown[]; + }; +}; + +async function readJson(response: Response): Promise { + return response.json(); +} diff --git a/apps/web/lib/api/generate.ts b/apps/web/lib/api/generate.ts new file mode 100644 index 0000000..2afc7de --- /dev/null +++ b/apps/web/lib/api/generate.ts @@ -0,0 +1,244 @@ +import { + createGenerationPlan, + generateProject, + normalizeGeneratedPath, + type GeneratedProject, +} from "@launchkit/generator"; +import { + LaunchKitCompatibilityError, + LaunchKitConfigSchema, + validateCompatibility, + type LaunchKitConfig, +} from "@launchkit/schema"; + +import { jsonErrorResponse, jsonResponse } from "./response"; +import { createWebTemplateLoader } from "./template-loader"; +import type { GenerateProjectResponse } from "./types"; + +export const MAX_GENERATE_REQUEST_BYTES = 64 * 1024; + +export type GenerateProjectHandlerOptions = { + generate?: (config: LaunchKitConfig) => Promise; +}; + +export async function handleGenerateProjectRequest( + request: Request, + options: GenerateProjectHandlerOptions = {}, +): Promise { + const parsedBody = await readJsonRequestBody(request); + + if (!parsedBody.ok) { + return parsedBody.response; + } + + const parsedConfig = LaunchKitConfigSchema.safeParse(parsedBody.value); + + if (!parsedConfig.success) { + return jsonErrorResponse({ + status: 400, + code: "invalid_config", + message: "Request body must be a valid LaunchKit config.", + issues: parsedConfig.error.issues, + }); + } + + const compatibilityIssues = validateCompatibility(parsedConfig.data); + + if (compatibilityIssues.length > 0) { + return jsonErrorResponse({ + status: 422, + code: "incompatible_config", + message: "Selected stack options are not compatible.", + issues: compatibilityIssues, + }); + } + + try { + const project = await (options.generate ?? generateProjectFromConfig)( + parsedConfig.data, + ); + + return jsonResponse( + { + project: serializeGeneratedProject(project), + }, + 200, + ); + } catch (error) { + if (error instanceof LaunchKitCompatibilityError) { + return jsonErrorResponse({ + status: 422, + code: "incompatible_config", + message: "Selected stack options are not compatible.", + issues: error.issues, + }); + } + + if (isZodError(error)) { + return jsonErrorResponse({ + status: 400, + code: "invalid_config", + message: "Request body must be a valid LaunchKit config.", + issues: error.issues, + }); + } + + if (error instanceof UnsafeGeneratedPathError) { + return jsonErrorResponse({ + status: 500, + code: "unsafe_generated_path", + message: "Generated project contained unsafe file paths.", + }); + } + + return jsonErrorResponse({ + status: 500, + code: "generation_failed", + message: "Project generation failed.", + }); + } +} + +export function methodNotAllowedResponse(): Response { + return jsonErrorResponse({ + status: 405, + code: "method_not_allowed", + message: "Use POST to generate a project.", + }); +} + +export async function generateProjectFromConfig( + config: LaunchKitConfig, +): Promise { + const plan = createGenerationPlan(config); + + return generateProject(config, { + templateLoader: createWebTemplateLoader(plan), + }); +} + +export function serializeGeneratedProject( + project: GeneratedProject, +): GenerateProjectResponse["project"] { + return { + name: project.name, + packageManager: project.packageManager, + files: project.files.map((file) => ({ + path: assertSafeGeneratedResponsePath(file.path), + contents: + typeof file.contents === "string" + ? file.contents + : Buffer.from(file.contents).toString("base64"), + encoding: typeof file.contents === "string" ? "utf8" : "base64", + })), + }; +} + +function assertSafeGeneratedResponsePath(path: string): string { + let normalizedPath: string; + + try { + normalizedPath = normalizeGeneratedPath(path); + } catch { + throw new UnsafeGeneratedPathError(path); + } + + const segments = normalizedPath.split("/"); + + if (segments.includes("src")) { + throw new UnsafeGeneratedPathError(path); + } + + return normalizedPath; +} + +async function readJsonRequestBody( + request: Request, +): Promise<{ ok: true; value: unknown } | { ok: false; response: Response }> { + const contentType = request.headers.get("content-type"); + + if (!isJsonContentType(contentType)) { + return { + ok: false, + response: jsonErrorResponse({ + status: 400, + code: "unsupported_content_type", + message: "Request body must use application/json.", + }), + }; + } + + const contentLength = request.headers.get("content-length"); + + if ( + contentLength && + Number.isFinite(Number(contentLength)) && + Number(contentLength) > MAX_GENERATE_REQUEST_BYTES + ) { + return { + ok: false, + response: jsonErrorResponse({ + status: 400, + code: "request_too_large", + message: "Request body must be 64 KB or smaller.", + }), + }; + } + + const text = await request.text(); + + if (new TextEncoder().encode(text).byteLength > MAX_GENERATE_REQUEST_BYTES) { + return { + ok: false, + response: jsonErrorResponse({ + status: 400, + code: "request_too_large", + message: "Request body must be 64 KB or smaller.", + }), + }; + } + + try { + return { + ok: true, + value: JSON.parse(text) as unknown, + }; + } catch { + return { + ok: false, + response: jsonErrorResponse({ + status: 400, + code: "invalid_json", + message: "Request body must be valid JSON.", + }), + }; + } +} + +function isJsonContentType(contentType: string | null): boolean { + if (!contentType) { + return false; + } + + const mediaType = contentType.split(";")[0]?.trim().toLowerCase(); + + return mediaType === "application/json" || mediaType.endsWith("+json"); +} + +class UnsafeGeneratedPathError extends Error { + constructor(path: string) { + super(`Unsafe generated path: ${path}`); + this.name = "UnsafeGeneratedPathError"; + } +} + +function isZodError(error: unknown): error is { issues: unknown[] } { + return ( + typeof error === "object" && + error !== null && + "name" in error && + error.name === "ZodError" && + "issues" in error && + Array.isArray(error.issues) + ); +} diff --git a/apps/web/lib/api/response.ts b/apps/web/lib/api/response.ts new file mode 100644 index 0000000..5d1084e --- /dev/null +++ b/apps/web/lib/api/response.ts @@ -0,0 +1,23 @@ +import type { ApiErrorResponse } from "./types"; + +export function jsonResponse(body: TBody, status: number): Response { + return Response.json(body, { status }); +} + +export function jsonErrorResponse(input: { + status: number; + code: string; + message: string; + issues?: unknown[]; +}): Response { + return jsonResponse( + { + error: { + code: input.code, + message: input.message, + ...(input.issues ? { issues: input.issues } : {}), + }, + }, + input.status, + ); +} diff --git a/apps/web/lib/api/template-loader.ts b/apps/web/lib/api/template-loader.ts new file mode 100644 index 0000000..b702b61 --- /dev/null +++ b/apps/web/lib/api/template-loader.ts @@ -0,0 +1,98 @@ +import { readdir, readFile } from "node:fs/promises"; +import { join, relative } from "node:path"; + +import { + applyTemplatePlaceholders, + normalizeGeneratedPath, + type GenerationPlan, + type TemplateContext, + type TemplateFile, + type TemplateLoader, +} from "@launchkit/generator"; + +const templatesRoot = join(process.cwd(), "..", "..", "packages", "templates"); + +export function createWebTemplateLoader(plan: GenerationPlan): TemplateLoader { + const targetPathBySourcePath = new Map( + plan.templateFiles.map((file) => [file.sourcePath, file.targetPath]), + ); + + return { + async loadTemplateFiles(input) { + const templateId = normalizeGeneratedPath(input.templateId); + + if (templateId === `base/${plan.baseTemplate}`) { + return loadTemplateDirectory(templateId, input.context); + } + + const targetPath = targetPathBySourcePath.get(templateId); + + if (!targetPath) { + throw new Error("Generator requested an unexpected template file."); + } + + return [ + await loadTemplateFile({ + sourcePath: templateId, + targetPath, + context: input.context, + }), + ]; + }, + }; +} + +async function loadTemplateDirectory( + templateId: string, + context: TemplateContext, +): Promise { + const root = join(templatesRoot, templateId); + const filePaths = await listFiles(root); + + return Promise.all( + filePaths.map((filePath) => { + const targetPath = relative(root, filePath).replaceAll("\\", "/"); + + return loadTemplateFile({ + sourcePath: `${templateId}/${targetPath}`, + targetPath, + context, + }); + }), + ); +} + +async function loadTemplateFile(input: { + sourcePath: string; + targetPath: string; + context: TemplateContext; +}): Promise { + const safeSourcePath = normalizeGeneratedPath(input.sourcePath); + const safeTargetPath = normalizeGeneratedPath(input.targetPath); + const contents = await readFile(join(templatesRoot, safeSourcePath), "utf8"); + + return { + sourcePath: safeSourcePath, + targetPath: normalizeGeneratedPath( + applyTemplatePlaceholders(safeTargetPath, input.context), + ), + contents: applyTemplatePlaceholders(contents, input.context), + }; +} + +async function listFiles(root: string): Promise { + const entries = await readdir(root, { withFileTypes: true }); + const files: string[] = []; + + for (const entry of entries) { + const entryPath = join(root, entry.name); + + if (entry.isDirectory()) { + files.push(...(await listFiles(entryPath))); + } else { + files.push(entryPath); + } + } + + return files; +} diff --git a/apps/web/lib/api/types.ts b/apps/web/lib/api/types.ts new file mode 100644 index 0000000..39cea7a --- /dev/null +++ b/apps/web/lib/api/types.ts @@ -0,0 +1,21 @@ +export type ApiErrorResponse = { + error: { + code: string; + message: string; + issues?: unknown[]; + }; +}; + +export type GeneratedProjectFileResponse = { + path: string; + contents: string; + encoding: "utf8" | "base64"; +}; + +export type GenerateProjectResponse = { + project: { + name: string; + packageManager: "npm" | "pnpm"; + files: GeneratedProjectFileResponse[]; + }; +}; diff --git a/apps/web/lib/builder/builder-state.ts b/apps/web/lib/builder/builder-state.ts new file mode 100644 index 0000000..ecf2a03 --- /dev/null +++ b/apps/web/lib/builder/builder-state.ts @@ -0,0 +1,29 @@ +import { + defaultLaunchKitConfig, + type LaunchKitConfig, +} from "@launchkit/schema"; + +export type BuilderState = { + config: LaunchKitConfig; +}; + +export type BuilderConfigPatch = Partial; + +export function createInitialBuilderState(): BuilderState { + return { + config: { ...defaultLaunchKitConfig }, + }; +} + +export function updateBuilderConfig( + state: BuilderState, + patch: BuilderConfigPatch, +): BuilderState { + return { + ...state, + config: { + ...state.config, + ...patch, + }, + }; +} diff --git a/apps/web/lib/builder/phase-6-verification.test.ts b/apps/web/lib/builder/phase-6-verification.test.ts new file mode 100644 index 0000000..078b5ee --- /dev/null +++ b/apps/web/lib/builder/phase-6-verification.test.ts @@ -0,0 +1,162 @@ +import { describe, expect, it } from "vitest"; + +import { + authOptions, + databaseOptions, + defaultLaunchKitConfig, + dockerOptions, + frameworkOptions, + languageOptions, + ormOptions, + packageManagerOptions, + projectStructureOptions, + routerOptions, + stylingOptions, + uiOptions, + type LaunchKitConfig, +} from "@launchkit/schema"; + +import { createBuilderPreview } from "./preview"; +import { builderSteps } from "./steps"; +import { + validateAuthStep, + validateBuilderConfig, + validateProjectStep, +} from "./validation"; + +describe("Phase 6 website wizard contract", () => { + it("renders the required MVP step order", () => { + expect(builderSteps.map((step) => step.id)).toEqual([ + "project", + "framework", + "styling-ui", + "database", + "orm", + "auth", + "extras", + "preview", + "download", + ]); + }); + + it("exposes only the supported MVP option values", () => { + expect(frameworkOptions).toEqual(["next"]); + expect(languageOptions).toEqual(["typescript"]); + expect(routerOptions).toEqual(["app"]); + expect(projectStructureOptions).toEqual(["no-src"]); + expect(stylingOptions).toEqual(["tailwind"]); + expect(uiOptions).toEqual(["none", "shadcn"]); + expect(databaseOptions).toEqual(["none", "postgres"]); + expect(ormOptions).toEqual(["none", "prisma"]); + expect(authOptions).toEqual(["none", "authjs-credentials"]); + expect(dockerOptions).toEqual(["none", "postgres"]); + expect(packageManagerOptions).toEqual(["npm", "pnpm"]); + }); + + it("uses schema validation for project names and supported package managers", () => { + expect( + validateProjectStep({ + ...defaultLaunchKitConfig, + name: "Invalid Name", + }).errors.name, + ).toBeTruthy(); + + expect( + validateProjectStep({ + ...defaultLaunchKitConfig, + packageManager: "yarn", + } as unknown as LaunchKitConfig).errors.packageManager, + ).toBeTruthy(); + }); + + it("keeps Auth.js credentials independent from database and ORM choices", () => { + expect( + validateAuthStep({ + ...defaultLaunchKitConfig, + auth: "authjs-credentials", + database: "none", + orm: "none", + }).isValid, + ).toBe(true); + }); + + it("rejects Prisma and PostgreSQL Docker without PostgreSQL", () => { + expect( + validateBuilderConfig({ + ...defaultLaunchKitConfig, + database: "none", + orm: "prisma", + }).errors.orm, + ).toBeTruthy(); + + expect( + validateBuilderConfig({ + ...defaultLaunchKitConfig, + database: "none", + docker: "postgres", + }).errors.docker, + ).toBeTruthy(); + }); + + it("previews the selected stack without unselected optional files or src paths", () => { + const preview = createBuilderPreview(defaultLaunchKitConfig); + + expect(preview.stackSummary).toEqual( + expect.arrayContaining([ + { label: "Framework", value: "Next.js" }, + { label: "Project structure", value: "No src folder" }, + { label: "UI", value: "None" }, + { label: "Database", value: "None" }, + ]), + ); + expect(preview.filePaths).toEqual( + expect.arrayContaining(["app/layout.tsx", "app/page.tsx", "package.json"]), + ); + expect(preview.filePaths).not.toEqual( + expect.arrayContaining([ + "components.json", + "prisma/schema.prisma", + "docker-compose.yml", + ]), + ); + expect(preview.filePaths.every((path) => !path.startsWith("src/"))).toBe( + true, + ); + }); + + it("previews full selected stack additions", () => { + const preview = createBuilderPreview({ + ...defaultLaunchKitConfig, + ui: "shadcn", + database: "postgres", + orm: "prisma", + auth: "authjs-credentials", + docker: "postgres", + packageManager: "pnpm", + }); + + expect(preview.dependencies.map((dependency) => dependency.name)).toEqual( + expect.arrayContaining(["@prisma/client", "next-auth"]), + ); + expect(preview.devDependencies.map((dependency) => dependency.name)).toEqual( + expect.arrayContaining(["prisma"]), + ); + expect(preview.scripts.map((script) => script.name)).toEqual( + expect.arrayContaining(["dev", "db:generate", "db:push"]), + ); + expect(preview.envVars.map((envVar) => envVar.name)).toEqual( + expect.arrayContaining(["DATABASE_URL", "AUTH_SECRET"]), + ); + expect(preview.filePaths).toEqual( + expect.arrayContaining([ + "components.json", + "prisma/schema.prisma", + "app/api/auth/[...nextauth]/route.ts", + "docker-compose.yml", + ]), + ); + expect(preview.filePaths.every((path) => !path.startsWith("src/"))).toBe( + true, + ); + }); +}); diff --git a/apps/web/lib/builder/preview.ts b/apps/web/lib/builder/preview.ts new file mode 100644 index 0000000..0e352b0 --- /dev/null +++ b/apps/web/lib/builder/preview.ts @@ -0,0 +1,159 @@ +import { createGenerationPlan } from "@launchkit/generator"; +import { + authMetadata, + databaseMetadata, + dockerMetadata, + frameworkMetadata, + languageMetadata, + ormMetadata, + packageManagerMetadata, + projectStructureMetadata, + routerMetadata, + stylingMetadata, + uiMetadata, + type LaunchKitConfig, + type OptionMetadata, +} from "@launchkit/schema"; + +type PackageEntry = { + name: string; + version: string; +}; + +export type StackSummaryItem = { + label: string; + value: string; +}; + +export type ScriptPreviewItem = { + name: string; + command: string; +}; + +export type EnvVarPreviewItem = { + name: string; + description?: string; + required: boolean; +}; + +export type BuilderPreview = { + stackSummary: StackSummaryItem[]; + dependencies: PackageEntry[]; + devDependencies: PackageEntry[]; + scripts: ScriptPreviewItem[]; + envVars: EnvVarPreviewItem[]; + filePaths: string[]; +}; + +const baseNextPreviewFiles = [ + ".gitignore", + "app/layout.tsx", + "app/page.tsx", + "next.config.ts", + "tsconfig.json", +] as const; + +const generatedSupportFiles = ["package.json", ".env.example", "README.md"] as const; + +export function createBuilderPreview(config: LaunchKitConfig): BuilderPreview { + const plan = createGenerationPlan(config); + const packageJson = plan.packageJson; + + return { + stackSummary: createStackSummary(config), + dependencies: mapPackageEntries(packageJson.dependencies), + devDependencies: mapPackageEntries(packageJson.devDependencies), + scripts: mapScriptEntries(packageJson.scripts), + envVars: plan.env.map((envVar) => ({ + name: envVar.name, + description: envVar.description, + required: envVar.required ?? false, + })), + filePaths: createPreviewFilePaths([ + ...baseNextPreviewFiles, + ...plan.templateFiles.map((file) => file.targetPath), + ...plan.generatedFiles.map((file) => file.path), + ...generatedSupportFiles, + ]), + }; +} + +function createStackSummary(config: LaunchKitConfig): StackSummaryItem[] { + return [ + { + label: "Project name", + value: config.name, + }, + { + label: "Framework", + value: getMetadataLabel(frameworkMetadata, config.framework), + }, + { + label: "Language", + value: getMetadataLabel(languageMetadata, config.language), + }, + { + label: "Router", + value: getMetadataLabel(routerMetadata, config.router), + }, + { + label: "Project structure", + value: getMetadataLabel(projectStructureMetadata, config.projectStructure), + }, + { + label: "Styling", + value: getMetadataLabel(stylingMetadata, config.styling), + }, + { + label: "UI", + value: getMetadataLabel(uiMetadata, config.ui), + }, + { + label: "Database", + value: getMetadataLabel(databaseMetadata, config.database), + }, + { + label: "ORM", + value: getMetadataLabel(ormMetadata, config.orm), + }, + { + label: "Auth", + value: getMetadataLabel(authMetadata, config.auth), + }, + { + label: "Docker", + value: getMetadataLabel(dockerMetadata, config.docker), + }, + { + label: "Package manager", + value: getMetadataLabel(packageManagerMetadata, config.packageManager), + }, + ]; +} + +function getMetadataLabel( + metadata: readonly OptionMetadata[], + value: TValue, +): string { + return metadata.find((option) => option.value === value)?.label ?? value; +} + +function mapPackageEntries(packages: Record = {}): PackageEntry[] { + return Object.entries(packages).map(([name, version]) => ({ + name, + version, + })); +} + +function mapScriptEntries(scripts: Record = {}): ScriptPreviewItem[] { + return Object.entries(scripts).map(([name, command]) => ({ + name, + command, + })); +} + +function createPreviewFilePaths(paths: readonly string[]): string[] { + return [...new Set(paths)].sort((firstPath, secondPath) => + firstPath.localeCompare(secondPath), + ); +} diff --git a/apps/web/lib/builder/steps.ts b/apps/web/lib/builder/steps.ts new file mode 100644 index 0000000..c2f6e92 --- /dev/null +++ b/apps/web/lib/builder/steps.ts @@ -0,0 +1,74 @@ +export type BuilderStepId = + | "project" + | "framework" + | "styling-ui" + | "database" + | "orm" + | "auth" + | "extras" + | "preview" + | "download"; + +export type BuilderStep = { + id: BuilderStepId; + label: string; + shortLabel: string; + placeholder: string; +}; + +export const builderSteps = [ + { + id: "project", + label: "Project", + shortLabel: "Project", + placeholder: "Project step coming next.", + }, + { + id: "framework", + label: "Framework", + shortLabel: "Stack", + placeholder: "Framework step coming next.", + }, + { + id: "styling-ui", + label: "Styling and UI", + shortLabel: "UI", + placeholder: "Styling and UI step coming next.", + }, + { + id: "database", + label: "Database", + shortLabel: "Data", + placeholder: "Database step coming next.", + }, + { + id: "orm", + label: "ORM", + shortLabel: "ORM", + placeholder: "Choose ORM setup.", + }, + { + id: "auth", + label: "Auth", + shortLabel: "Auth", + placeholder: "Choose auth scaffold.", + }, + { + id: "extras", + label: "Extras", + shortLabel: "Extras", + placeholder: "Choose optional extras.", + }, + { + id: "preview", + label: "Preview", + shortLabel: "Preview", + placeholder: "Inspect generated project details.", + }, + { + id: "download", + label: "Download", + shortLabel: "ZIP", + placeholder: "Generate and download the project ZIP.", + }, +] as const satisfies readonly BuilderStep[]; diff --git a/apps/web/lib/builder/validation.ts b/apps/web/lib/builder/validation.ts new file mode 100644 index 0000000..ddd2eee --- /dev/null +++ b/apps/web/lib/builder/validation.ts @@ -0,0 +1,196 @@ +import { + LaunchKitConfigSchema, + validateCompatibility, + type LaunchKitConfig, +} from "@launchkit/schema"; + +type ValidationErrors = Partial>; + +export type ProjectStepValidation = { + isValid: boolean; + errors: Pick; +}; + +export type FrameworkStepValidation = { + isValid: boolean; + errors: Pick< + ValidationErrors, + "framework" | "language" | "router" | "projectStructure" + >; +}; + +export type StylingUiStepValidation = { + isValid: boolean; + errors: Pick; +}; + +export type DatabaseStepValidation = { + isValid: boolean; + errors: Pick; +}; + +export type OrmStepValidation = { + isValid: boolean; + errors: Pick; +}; + +export type AuthStepValidation = { + isValid: boolean; + errors: Partial>; +}; + +export type ExtrasStepValidation = { + isValid: boolean; + errors: Pick; +}; + +export type PreviewStepValidation = { + isValid: boolean; + errors: ValidationErrors; +}; + +export function validateBuilderConfig(config: LaunchKitConfig): { + isValid: boolean; + errors: ValidationErrors; +} { + const result = LaunchKitConfigSchema.safeParse(config); + + if (!result.success) { + const errors: ValidationErrors = {}; + + for (const issue of result.error.issues) { + const field = issue.path[0]; + + if (typeof field === "string" && !(field in errors)) { + errors[field as keyof LaunchKitConfig] = issue.message; + } + } + + return { + isValid: false, + errors, + }; + } + + const errors: ValidationErrors = {}; + + for (const issue of validateCompatibility(result.data)) { + const fields = issue.path ?? []; + + for (const field of fields) { + if (typeof field === "string" && !(field in errors)) { + errors[field as keyof LaunchKitConfig] = issue.message; + } + } + } + + return { + isValid: Object.keys(errors).length === 0, + errors, + }; +} + +export function validateProjectStep( + config: LaunchKitConfig, +): ProjectStepValidation { + const validation = validateBuilderConfig(config); + const errors = { + name: validation.errors.name, + packageManager: validation.errors.packageManager, + }; + + return { + isValid: !errors.name && !errors.packageManager, + errors, + }; +} + +export function validateFrameworkStep( + config: LaunchKitConfig, +): FrameworkStepValidation { + const validation = validateBuilderConfig(config); + const errors = { + framework: validation.errors.framework, + language: validation.errors.language, + router: validation.errors.router, + projectStructure: validation.errors.projectStructure, + }; + + return { + isValid: + !errors.framework && + !errors.language && + !errors.router && + !errors.projectStructure, + errors, + }; +} + +export function validateStylingUiStep( + config: LaunchKitConfig, +): StylingUiStepValidation { + const validation = validateBuilderConfig(config); + const errors = { + styling: validation.errors.styling, + ui: validation.errors.ui, + }; + + return { + isValid: !errors.styling && !errors.ui, + errors, + }; +} + +export function validateDatabaseStep( + config: LaunchKitConfig, +): DatabaseStepValidation { + const validation = validateBuilderConfig(config); + const errors = { + database: validation.errors.database, + orm: validation.errors.orm, + docker: validation.errors.docker, + }; + + return { + isValid: !errors.database && !errors.orm && !errors.docker, + errors, + }; +} + +export function validateOrmStep(config: LaunchKitConfig): OrmStepValidation { + const validation = validateBuilderConfig(config); + const errors = { + orm: validation.errors.orm, + database: validation.errors.database, + }; + + return { + isValid: !errors.orm && !errors.database, + errors, + }; +} + +export function validateAuthStep(config: LaunchKitConfig): AuthStepValidation { + return validateBuilderConfig(config); +} + +export function validateExtrasStep( + config: LaunchKitConfig, +): ExtrasStepValidation { + const validation = validateBuilderConfig(config); + const errors = { + docker: validation.errors.docker, + database: validation.errors.database, + }; + + return { + isValid: !errors.docker && !errors.database, + errors, + }; +} + +export function validatePreviewStep( + config: LaunchKitConfig, +): PreviewStepValidation { + return validateBuilderConfig(config); +} diff --git a/apps/web/lib/download/create-project-zip.test.ts b/apps/web/lib/download/create-project-zip.test.ts new file mode 100644 index 0000000..8c5c5e2 --- /dev/null +++ b/apps/web/lib/download/create-project-zip.test.ts @@ -0,0 +1,93 @@ +import JSZip from "jszip"; +import { describe, expect, it } from "vitest"; + +import { createProjectZip } from "./create-project-zip"; + +describe("createProjectZip", () => { + it("includes files under the top-level project folder", async () => { + const zip = await loadZip( + await createProjectZip({ + name: "my-app", + packageManager: "npm", + files: [ + { + path: "package.json", + contents: "{}\n", + encoding: "utf8", + }, + { + path: "app/page.tsx", + contents: "export default function Page() {}\n", + encoding: "utf8", + }, + ], + }), + ); + + expect(Object.keys(zip.files)).toEqual( + expect.arrayContaining([ + "my-app/package.json", + "my-app/app/page.tsx", + ]), + ); + await expect(zip.file("my-app/package.json")?.async("text")).resolves.toBe( + "{}\n", + ); + }); + + it("handles base64 file contents", async () => { + const zip = await loadZip( + await createProjectZip({ + name: "binary-demo", + packageManager: "pnpm", + files: [ + { + path: "public/icon.bin", + contents: "AQID", + encoding: "base64", + }, + ], + }), + ); + + const bytes = await zip.file("binary-demo/public/icon.bin")?.async("uint8array"); + + expect(Array.from(bytes ?? [])).toEqual([1, 2, 3]); + }); + + it("rejects unsafe paths", async () => { + await expect( + createProjectZip({ + name: "unsafe", + packageManager: "npm", + files: [ + { + path: "../outside.txt", + contents: "bad", + encoding: "utf8", + }, + ], + }), + ).rejects.toThrow("Unsafe zip path"); + }); + + it("rejects generated src directory paths", async () => { + await expect( + createProjectZip({ + name: "unsafe", + packageManager: "npm", + files: [ + { + path: "src/app/page.tsx", + contents: "bad", + encoding: "utf8", + }, + ], + }), + ).rejects.toThrow("Unsafe zip path"); + }); +}); + +async function loadZip(blob: Blob): Promise { + return JSZip.loadAsync(await blob.arrayBuffer()); +} diff --git a/apps/web/lib/download/create-project-zip.ts b/apps/web/lib/download/create-project-zip.ts new file mode 100644 index 0000000..96fddde --- /dev/null +++ b/apps/web/lib/download/create-project-zip.ts @@ -0,0 +1,76 @@ +import JSZip from "jszip"; + +import type { GenerateProjectResponse } from "@/lib/api/types"; + +export class UnsafeZipPathError extends Error { + constructor(path: string) { + super(`Unsafe zip path: ${path}`); + this.name = "UnsafeZipPathError"; + } +} + +export async function createProjectZip( + project: GenerateProjectResponse["project"], +): Promise { + const zip = new JSZip(); + const projectFolder = assertSafeProjectFolder(project.name); + + for (const file of project.files) { + const safePath = assertSafeProjectFilePath(file.path); + const zipPath = `${projectFolder}/${safePath}`; + + zip.file( + zipPath, + file.contents, + file.encoding === "base64" ? { base64: true } : undefined, + ); + } + + return zip.generateAsync({ + type: "blob", + compression: "DEFLATE", + }); +} + +function assertSafeProjectFolder(name: string): string { + if ( + name.length === 0 || + name.trim().length === 0 || + name.includes("/") || + name.includes("\\") || + name === "." || + name === ".." + ) { + throw new UnsafeZipPathError(name); + } + + return name; +} + +function assertSafeProjectFilePath(path: string): string { + const normalizedPath = path.replaceAll("\\", "/"); + + if ( + normalizedPath.length === 0 || + normalizedPath.trim().length === 0 || + normalizedPath === "." || + normalizedPath === ".." || + normalizedPath.startsWith("/") || + /^[A-Za-z]:($|\/)/.test(normalizedPath) + ) { + throw new UnsafeZipPathError(path); + } + + const segments = normalizedPath.split("/"); + + if ( + segments.some( + (segment) => segment.length === 0 || segment === "." || segment === "..", + ) || + segments.includes("src") + ) { + throw new UnsafeZipPathError(path); + } + + return normalizedPath; +} diff --git a/apps/web/package.json b/apps/web/package.json index f3d7146..ff7c090 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,11 +7,15 @@ "build": "next build", "start": "next start", "lint": "eslint", + "test": "vitest run", "typecheck": "tsc --noEmit" }, "dependencies": { + "@launchkit/generator": "0.0.0", + "@launchkit/schema": "0.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jszip": "^3.10.1", "lucide-react": "^1.21.0", "next": "16.2.9", "radix-ui": "^1.6.0", diff --git a/context/progress-tracker.md b/context/progress-tracker.md index 378d6e4..809c194 100644 --- a/context/progress-tracker.md +++ b/context/progress-tracker.md @@ -7,8 +7,8 @@ Use this file to track development progress, changes made, decisions, notes, blo ```txt Project: LaunchKit Stage: Foundation setup -Current phase: Phase 5 complete -Primary focus: Phase 6 website wizard shell is ready to begin +Current phase: Phase 6 in progress +Primary focus: Phase 6 Step 12 responsive polish and automated verification are complete; user-run browser/download QA remains before marking Phase 6 complete ``` ## Phase Progress @@ -20,7 +20,7 @@ Primary focus: Phase 6 website wizard shell is ready to begin | Phase 3 | Shared Schema and Compatibility Rules | Complete | Step 8 checkpoint verified schema package completeness, exports, Vitest coverage, and workspace checks. | | Phase 4 | Generator Core | Complete | Step 10 checkpoint verified generator exports, source organization, tests, builds, and Node-loadable ESM package output. | | Phase 5 | Template Implementation | Complete | Step 9 verified all MVP template layers, real-template generator output, path safety, and compatibility behavior. | -| Phase 6 | Website MVP | Ready | Ready to create the website wizard shell, preview, and download flow without duplicating generator logic. | +| Phase 6 | Website MVP | In Progress | Step 12 polished responsive wizard layout and added Phase 6 contract tests; manual browser/download QA remains before marking Phase 6 complete. | | Phase 7 | Testing, Validation, and Hardening | Not Started | Will add tests, smoke checks, and API safety. | | Phase 8 | Launch Preparation | Not Started | Will prepare docs, deployment, and final MVP review. | | Phase 9 | Future CLI | Not Started | Deferred until website MVP is stable. | @@ -29,8 +29,1552 @@ Primary focus: Phase 6 website wizard shell is ready to begin Add entries in reverse chronological order. +### 2026-07-03 + +Phase 6 Step 12 completed: Responsive UI polish and Phase 6 verification + +Changes made: + +- Removed the extra dashed inner frame from wizard step content to avoid a card-within-card feel. +- Tightened wizard step header spacing on small screens. +- Updated progress cards to use short labels on mobile/tablet and full labels on large screens. +- Improved project-name wrapping in the header and Download step. +- Improved current-selection value wrapping and width constraints. +- Updated option-card label rows to wrap badges and radio indicators cleanly on narrow viewports. +- Updated preview stack and download stack values to wrap instead of truncating important labels. +- Updated dependency names and environment variable names to wrap safely. +- Updated script command rows to scroll horizontally when exact command text is too long. +- Added focused Phase 6 wizard contract tests for step order, supported options, validation, compatibility, preview contents, optional file inclusion/exclusion, and `src/` path exclusion. +- Confirmed no CLI functionality was added. +- Confirmed no new product options were added. +- Confirmed generator logic was not moved into UI components. + +Files changed: + +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/components/builder/preview/dependency-list.tsx` +- `apps/web/components/builder/preview/env-var-list.tsx` +- `apps/web/components/builder/preview/script-list.tsx` +- `apps/web/components/builder/preview/stack-summary.tsx` +- `apps/web/components/builder/steps/auth-step.tsx` +- `apps/web/components/builder/steps/database-step.tsx` +- `apps/web/components/builder/steps/download-step.tsx` +- `apps/web/components/builder/steps/extras-step.tsx` +- `apps/web/components/builder/steps/framework-step.tsx` +- `apps/web/components/builder/steps/orm-step.tsx` +- `apps/web/components/builder/steps/project-step.tsx` +- `apps/web/components/builder/steps/styling-ui-step.tsx` +- `apps/web/components/builder/wizard-progress.tsx` +- `apps/web/components/builder/wizard-step-panel.tsx` +- `apps/web/lib/builder/phase-6-verification.test.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,240p' context/progress-tracker.md +sed -n '1,260p' .agents/prompts/phase-06/step-12.md +git status --short +rg --files context +find apps/web -maxdepth 3 -type f | sort +find packages -maxdepth 3 -type f | sort +sed -n '1,260p' context/project-overview.md +sed -n '261,620p' context/project-overview.md +sed -n '1,320p' context/architecture.md +sed -n '321,760p' context/architecture.md +sed -n '1,320p' context/build-plan.md +sed -n '321,820p' context/build-plan.md +sed -n '821,1240p' context/build-plan.md +sed -n '1,320p' context/ui-rules.md +sed -n '321,700p' context/ui-rules.md +npm run test -w apps/web +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run dev -w apps/web +npm run dev -w apps/web +npm ls playwright +npm ls @playwright/test +curl -I http://localhost:3000 +curl -I http://localhost:3000 +npm run typecheck +npm run test +npm run lint +git diff --check +git status --short +git diff --stat +``` + +Verification: + +- [x] All 9 wizard steps are defined in the required order. +- [x] Supported MVP option values are constrained to the documented choices. +- [x] Project-name validation uses shared schema validation. +- [x] Unsupported package managers are rejected. +- [x] Auth.js credentials remains valid without PostgreSQL. +- [x] Prisma without PostgreSQL is rejected. +- [x] Docker PostgreSQL without PostgreSQL is rejected. +- [x] Preview includes selected stack summary data. +- [x] Preview includes dependencies and dev dependencies. +- [x] Preview includes scripts. +- [x] Preview includes environment variables. +- [x] Preview includes generated file tree paths. +- [x] Preview excludes unselected optional feature files. +- [x] Preview excludes `src/` paths. +- [x] Full-stack preview includes Prisma, Auth.js, shadcn, Docker, env, and script additions. +- [x] Download flow code still uses the API client and browser ZIP helper from Step 11. +- [x] Responsive polish addressed mobile progress labels, wrapping, and scroll handling. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app tests passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] `git diff --check` passed. + +Verification result: + +- Initial `npm run test -w apps/web` failed because the new verification test expected `db:migrate`, while the current generator exposes `db:push`. The test was corrected to match the generator contract. +- Initial `npm run typecheck -w apps/web` failed because an intentionally invalid package manager literal needed an `unknown` cast before casting to `LaunchKitConfig`. The test was corrected. +- `npm run test -w apps/web` passed after fixes: 4 test files, 23 tests. +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: web app ran 23 tests, generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `git diff --check` passed. + +Manual verification: + +- Browser QA was not completed in this session. +- The in-app browser connector failed before exposing a usable tab because the Node REPL tool returned an internal `sandbox-state-meta` error. +- The workspace does not currently include Playwright or `@playwright/test`, so a local Playwright fallback was not available without adding dependencies. +- A sandboxed `curl -I http://localhost:3000` could not connect. +- An elevated localhost `curl` check was not allowed. +- An elevated `npm run dev -w apps/web` attempt found another Next dev server lock/process and exited; the user said they will do manual browser/download verification themselves. + +Notes/blockers: + +- Phase 6 is intentionally still marked `In Progress` until user-run browser/download QA confirms the website MVP genuinely works end to end. +- No CLI work was started. +- No new stack options were added. +- Pre-existing unrelated worktree changes remain: `memory.md` is modified and `.agents/prompts/phase-06/step-12.md` is untracked. + +Next suggested step: + +- User-run manual QA for the wizard at 375px, 768px, 1280px, and 1440px+ widths, including a real `Generate ZIP` download. + +Phase 6 Step 11 completed: Create download flow + +Changes made: + +- Added Download step UI to the website wizard. +- Added compact project name and package manager summary. +- Added short selected stack summary using existing schema/generator-derived preview labels. +- Added `Generate ZIP` button. +- Added loading, success, and error states. +- Added client-side validation before calling the API. +- Invalid config states show concise errors and do not call the API. +- Added typed API client for `POST /api/generate`. +- API client sends the current `LaunchKitConfig`. +- API client parses structured API errors and throws concise client errors. +- Added browser-side ZIP creation helper using `jszip`. +- Added `jszip` to `apps/web` dependencies and updated `package-lock.json`. +- ZIP helper puts generated files under a top-level `{{projectName}}/` folder. +- ZIP helper supports UTF-8 file contents. +- ZIP helper supports base64 file contents. +- ZIP helper rejects unsafe paths: + - absolute paths; + - `..`; + - empty path segments; + - generated `src/` directory paths; + - unsafe top-level project folder names. +- Added browser download trigger using `Blob`, object URL, temporary anchor click, and URL revocation. +- Download flow only requests generated file data and packages it as a zip in the browser. +- Confirmed no generated project code is executed. +- Confirmed no generated project dependencies are installed. +- Confirmed no generated files are written to the server filesystem. +- Added focused Vitest coverage for the API client and ZIP helper. +- Kept generator logic out of UI components. +- Confirmed no CLI functionality was added. + +Files changed: + +- `apps/web/components/builder/download/download-button.tsx` +- `apps/web/components/builder/download/download-status.tsx` +- `apps/web/components/builder/steps/download-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/api/client.ts` +- `apps/web/lib/api/client.test.ts` +- `apps/web/lib/api/types.ts` +- `apps/web/lib/api/generate.ts` +- `apps/web/lib/api/response.ts` +- `apps/web/lib/download/create-project-zip.ts` +- `apps/web/lib/download/create-project-zip.test.ts` +- `apps/web/lib/builder/steps.ts` +- `apps/web/package.json` +- `package-lock.json` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,280p' context/progress-tracker.md +sed -n '1,340p' .agents/prompts/phase-06/step-11.md +git status --short +sed -n '341,680p' .agents/prompts/phase-06/step-11.md +sed -n '1,260p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '1,280p' context/project-overview.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1160p' context/build-plan.md +sed -n '281,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '281,620p' context/ui-rules.md +rg 'jszip|fflate|zip' package.json package-lock.json apps/web/package.json packages -g '!dist/**' +sed -n '1,260p' apps/web/components/builder/builder-shell.tsx +sed -n '1,260p' apps/web/lib/api/generate.ts +sed -n '1,220p' apps/web/lib/builder/preview.ts +npm install jszip -w apps/web +npm install jszip -w apps/web +sed -n '1,260p' apps/web/lib/builder/validation.ts +sed -n '1,260p' apps/web/lib/builder/steps.ts +sed -n '1,220p' apps/web/components/builder/wizard-navigation.tsx +cat apps/web/package.json +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run test -w apps/web +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run test -w apps/web +git diff --check +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +rg 'child_process|exec\(|spawn\(|npm install|pnpm install|packages/cli|node:test|node --test|writeFile|mkdir|fs/promises|createProjectZip' apps/web packages -g '!dist/**' +git diff -- apps/web/components/builder/steps/download-step.tsx apps/web/lib/download/create-project-zip.ts apps/web/lib/api/client.ts apps/web/lib/api/types.ts apps/web/components/builder/builder-shell.tsx apps/web/package.json package-lock.json +git diff --check +npm run build +git status --short +git diff --stat +git diff -- apps/web/components/builder/steps/download-step.tsx apps/web/lib/download/create-project-zip.ts apps/web/lib/api/client.ts apps/web/lib/api/types.ts apps/web/components/builder/builder-shell.tsx apps/web/package.json package-lock.json +``` + +Verification: + +- [x] Download step renders in the wizard. +- [x] Download step shows project name. +- [x] Download step shows selected package manager. +- [x] Download step shows a short selected stack summary. +- [x] Download button calls the typed `POST /api/generate` client helper. +- [x] Invalid config prevents API calls in the Download step. +- [x] API client handles non-2xx structured API errors. +- [x] Generated project data is turned into a ZIP. +- [x] ZIP contains files under the top-level project folder. +- [x] ZIP helper handles UTF-8 file contents. +- [x] ZIP helper handles base64 file contents. +- [x] ZIP helper rejects unsafe paths. +- [x] ZIP helper rejects generated `src/` paths. +- [x] Browser download trigger uses a `Blob`, object URL, temporary anchor, and URL revocation. +- [x] Download button is disabled while generating. +- [x] Loading, success, and error states are implemented. +- [x] API errors render concise messages. +- [x] No generated project code is executed. +- [x] No generated project dependencies are installed. +- [x] No generated files are written to the server filesystem by the download flow. +- [x] No generator logic is duplicated in UI components. +- [x] No CLI functionality was added. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app tests passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- Initial `npm install jszip -w apps/web` failed in the sandbox because the npm registry could not be resolved. Rerunning with elevated permissions succeeded, added 11 packages, and updated `package-lock.json`. +- `npm install jszip -w apps/web` reported 2 moderate vulnerabilities in npm audit output. No `npm audit fix --force` was run because it would be an unrelated broad dependency change. +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run test -w apps/web` passed: 3 test files, 16 tests. +- `git diff --check` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: web app ran 16 tests, generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` passed across all workspaces when run with elevated permissions for the known Turbopack process/port restriction. +- Source search found no generated-code execution, generated dependency install, CLI work, or Node test runner usage in the Step 11 implementation. It found expected template/test install text, existing server-side template file reads from Step 10, and the browser-side ZIP helper. + +Manual verification: + +- Local browser download QA was not run in this session because the user said they will run the dev server locally. +- Automated ZIP tests verified the top-level project folder, UTF-8 contents, base64 contents, unsafe path rejection, and `src/` path rejection. + +Notes/blockers: + +- The browser-side ZIP flow depends on the Phase 6 Step 10 API returning generated project JSON. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- `npm install` audit output currently reports 2 moderate vulnerabilities; this step did not attempt broad dependency remediation. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 12: Responsive UI polish and Phase 6 verification. + +Phase 6 Step 10 completed: Create API generate route + +Changes made: + +- Added `POST /api/generate` App Router API route. +- Added a structured `GET /api/generate` method-not-allowed response. +- Added request parsing for JSON bodies. +- Rejects non-JSON request content. +- Rejects malformed JSON. +- Rejects request bodies over 64 KB. +- Added request validation using `@launchkit/schema` `LaunchKitConfigSchema`. +- Added compatibility validation using `@launchkit/schema` `validateCompatibility`. +- Connected the route to `@launchkit/generator` `generateProject`. +- Added a server-side web template loader that reads template files and passes them through the generator `TemplateLoader` API. +- Kept template composition and feature decisions inside `@launchkit/generator`. +- Added JSON success response shape with project name, package manager, generated file paths, contents, and `utf8`/`base64` encoding metadata. +- Encodes `Uint8Array` file contents as base64 instead of returning Node `Buffer` objects. +- Added structured error responses for invalid JSON, invalid config, incompatible config, oversized requests, unsupported content type, unsafe generated paths, and unexpected generation failures. +- Added generated path safety checks before responding: + - relative paths only; + - no leading `/`; + - no `..`; + - no empty path segments; + - no generated `src/` directory paths. +- Added focused Vitest coverage for API helper behavior. +- Added `test` script to `apps/web` so root `npm run test` includes web API tests. +- Confirmed no generated project files are written to disk. +- Confirmed no generated project code is executed. +- Confirmed no generated project dependencies are installed. +- Confirmed no zip archive or final browser download UI was added. +- Followed the Step 10 prompt's JSON-response handoff for Step 11, despite older architecture notes describing the eventual API as zip-returning. + +Files changed: + +- `apps/web/app/api/generate/route.ts` +- `apps/web/lib/api/generate.ts` +- `apps/web/lib/api/generate.test.ts` +- `apps/web/lib/api/response.ts` +- `apps/web/lib/api/template-loader.ts` +- `apps/web/package.json` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,260p' context/progress-tracker.md +sed -n '1,300p' .agents/prompts/phase-06/step-10.md +git status --short +sed -n '301,620p' .agents/prompts/phase-06/step-10.md +sed -n '1,260p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '1,280p' context/project-overview.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1160p' context/build-plan.md +sed -n '281,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '281,620p' context/ui-rules.md +rg --files apps/web packages/generator/src packages/templates/src packages/schema/src | sort +sed -n '1,360p' packages/generator/src/generate-project.ts +sed -n '1,260p' packages/generator/src/template-loader.ts +cat apps/web/package.json +sed -n '1,260p' packages/generator/src/__tests__/generate-project.test.ts +sed -n '1,260p' packages/generator/src/__tests__/phase-5-completion.test.ts +sed -n '1,220p' packages/schema/src/compatibility.ts +sed -n '1,180p' packages/schema/src/index.ts +cat apps/web/tsconfig.json +cat package.json +cat packages/generator/tsconfig.json +sed -n '1,220p' packages/generator/src/file-tree.ts +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run test -w apps/web +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run test -w apps/web +git diff --check +cat packages/schema/package.json +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run test -w apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +rg 'createProjectZip|Generate ZIP|download\(|app/api/generate|child_process|exec\(|spawn\(|npm install|pnpm install|packages/cli|node:test|node --test' apps/web packages -g '!dist/**' +git diff -- apps/web/app/api/generate/route.ts apps/web/lib/api/generate.ts apps/web/lib/api/template-loader.ts apps/web/lib/api/response.ts apps/web/lib/api/generate.test.ts apps/web/package.json +git diff --check +npm run build +``` + +Verification: + +- [x] `POST /api/generate` exists. +- [x] Route validates request JSON using `@launchkit/schema`. +- [x] Route validates compatibility using shared schema helpers. +- [x] Route calls `@launchkit/generator`. +- [x] Valid config returns generated project data. +- [x] Invalid config returns structured `400`. +- [x] Incompatible config returns structured `422`. +- [x] Malformed JSON returns structured `400`. +- [x] Non-JSON content returns structured `400`. +- [x] Oversized body returns structured `400`. +- [x] Unexpected generator failure returns structured `500` without stack traces or internal paths. +- [x] Binary generated file contents serialize as base64. +- [x] Generated file paths are checked before response. +- [x] Unsafe generated paths are rejected before response serialization. +- [x] Generated `src/` directory paths are rejected before response serialization. +- [x] No generated `src/` paths are returned for the valid generated project. +- [x] No generated project code is executed. +- [x] No generated project dependencies are installed. +- [x] No generated project files are written to disk. +- [x] No zip archive was created. +- [x] No final browser download UI was implemented. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app tests passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run test -w apps/web` passed: 1 test file, 10 tests. +- `git diff --check` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed and listed `/api/generate` as a dynamic route. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: web app ran 10 tests, generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` passed across all workspaces when run with elevated permissions for the known Turbopack process/port restriction. +- Source search found no API shell execution, CLI work, zip creation, or browser download implementation. It only found generated README install-command text and related generator tests. + +Notes/blockers: + +- Step 10 intentionally returns generated project JSON instead of a zip. Step 11 should consume this API response and implement the final browser download flow. +- The web API template loader reads template files from the monorepo `packages/templates` directory through the generator `TemplateLoader` API because the generator package does not yet expose a production filesystem template loader. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 11: Create download flow. + ### 2026-07-02 +Phase 6 Step 9 completed: Create Preview step + +Changes made: + +- Added the Preview step UI to the website wizard. +- Added selected stack summary with metadata labels from `@launchkit/schema`. +- Added dependency and dev dependency preview sections from `@launchkit/generator` `createGenerationPlan(config)`. +- Added generated package script preview from generator plan data. +- Added environment variable preview from generator plan data. +- Environment variables show names, descriptions, and required state only; no real secret values are displayed. +- Added generated file tree preview with compact monospace formatting. +- File tree preview includes selected optional feature files from generator plan data. +- File tree preview excludes unselected optional feature files. +- File tree preview includes no `src/` paths. +- Added Preview-step validation using the existing schema parsing and compatibility helper path. +- Gated Next navigation on the Preview step when the full config is invalid. +- Avoided full file content preview. +- Added `@launchkit/generator` as an explicit `apps/web` dependency for the planning helper import. +- Kept generator planning logic isolated in `apps/web/lib/builder/preview.ts`; UI components only render preview data. +- Confirmed no generate/download API route was added. +- Confirmed no zip download behavior was added. +- Confirmed no CLI functionality was added. + +Files changed: + +- `apps/web/components/builder/preview/dependency-list.tsx` +- `apps/web/components/builder/preview/env-var-list.tsx` +- `apps/web/components/builder/preview/file-tree-preview.tsx` +- `apps/web/components/builder/preview/script-list.tsx` +- `apps/web/components/builder/preview/stack-summary.tsx` +- `apps/web/components/builder/steps/preview-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/preview.ts` +- `apps/web/lib/builder/steps.ts` +- `apps/web/lib/builder/validation.ts` +- `apps/web/package.json` +- `package-lock.json` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,240p' context/progress-tracker.md +sed -n '1,240p' .agents/prompts/phase-06/step-9.md +git status --short +rg --files context | sort +sed -n '1,260p' context/architecture.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1160p' context/build-plan.md +sed -n '1,260p' context/progress-tracker.md +sed -n '261,700p' context/progress-tracker.md +sed -n '1,280p' context/project-overview.md +sed -n '281,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '281,620p' context/ui-rules.md +rg --files apps/web/components/builder apps/web/lib/builder apps/web/app packages/generator/src packages/schema/src +sed -n '1,420p' apps/web/components/builder/builder-shell.tsx +sed -n '1,260p' apps/web/lib/builder/steps.ts +sed -n '1,320p' apps/web/lib/builder/validation.ts +sed -n '1,260p' packages/generator/src/index.ts +sed -n '1,360p' packages/generator/src/generation-plan.ts +sed -n '1,380p' packages/generator/src/features/registry.ts +sed -n '1,320p' packages/generator/src/generate-project.ts +sed -n '1,460p' packages/generator/src/features/definitions.ts +cat apps/web/package.json +cat packages/generator/package.json +sed -n '1,320p' packages/generator/src/template-loader.ts +sed -n '1,320p' packages/generator/src/file-tree.ts +sed -n '1,420p' packages/schema/src/metadata.ts +sed -n '1,320p' packages/schema/src/config.ts +sed -n '1,220p' apps/web/lib/builder/builder-state.ts +sed -n '1,260p' apps/web/components/builder/wizard-step-panel.tsx +sed -n '1,320p' apps/web/components/builder/steps/extras-step.tsx +sed -n '1,320p' apps/web/components/builder/steps/auth-step.tsx +sed -n '1,320p' apps/web/components/builder/steps/project-step.tsx +sed -n '1,220p' apps/web/components/builder/wizard-navigation.tsx +rg --files packages/templates | sort +cat packages/templates/package.json +sed -n '1,260p' packages/templates/src/index.ts +ls package-lock.json +sed -n '1,220p' package-lock.json +find node_modules/@launchkit -maxdepth 1 -type l -o -type d -print +ls -la node_modules/@launchkit +rg 'node_modules\/\@launchkit\/(generator|schema|templates|shared)' package-lock.json +sed -n '/node_modules\/\@launchkit\/generator/,+18p' package-lock.json +find packages/templates/base/next -maxdepth 2 -type f | sort +npm run typecheck -w apps/web +npm run lint -w apps/web +git diff --check +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +rg '@launchkit/generator|app/api|createProjectZip|download|node:test|node --test|src/' apps/web +npm run build +git status --short +git diff --stat +git diff -- apps/web/lib/builder/preview.ts apps/web/components/builder/steps/preview-step.tsx apps/web/components/builder/builder-shell.tsx apps/web/lib/builder/validation.ts apps/web/package.json package-lock.json +sed -n '1,120p' context/progress-tracker.md +git diff --check +git status --short +git diff --stat +sed -n '1,220p' context/progress-tracker.md +lsof -nP -iTCP:3000 -sTCP:LISTEN +npm run dev -w apps/web -- --hostname 127.0.0.1 --port 3001 +npm run dev -w apps/web -- --hostname 127.0.0.1 --port 3001 +``` + +Verification: + +- [x] Preview step renders in the wizard. +- [x] Selected stack summary includes project name, framework, language, router, project structure, styling, UI, database, ORM, auth, Docker, and package manager. +- [x] Stack summary uses schema metadata labels when metadata exists. +- [x] Dependencies come from `@launchkit/generator` plan data. +- [x] Dev dependencies come from `@launchkit/generator` plan data. +- [x] Scripts come from `@launchkit/generator` plan data. +- [x] Environment variables come from `@launchkit/generator` plan data. +- [x] Environment variable preview does not display real secrets. +- [x] File tree preview shows selected optional feature files only. +- [x] File tree preview includes no `src/` paths. +- [x] Invalid schema or compatibility state shows a concise Preview-step error. +- [x] Invalid schema or compatibility state prevents moving from Preview to Download. +- [x] No full file content preview was added. +- [x] No generate/download API route was added. +- [x] No zip download behavior was added. +- [x] No CLI functionality was added. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `git diff --check` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` passed across all workspaces when run with elevated permissions for the known Turbopack process/port restriction. +- `lsof -nP -iTCP:3000 -sTCP:LISTEN` showed port 3000 was already in use by a local Node process. +- `npm run dev -w apps/web -- --hostname 127.0.0.1 --port 3001` failed in the sandbox because binding to `127.0.0.1:3001` was not permitted. The elevated rerun was rejected, so no dev server is running from this step. + +Notes/blockers: + +- The generator plan currently exposes selected feature file references, dependencies, dev dependencies, scripts, and environment variables, but it does not expose a base template file manifest. The Preview helper therefore keeps a small local list of MVP base Next.js file paths until the generator exports base template file references. +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; port 3000 was already occupied, the sandbox blocked port 3001, and the elevated rerun was rejected. + +Next suggested step: + +- Phase 6 Step 10: Build generate API route. + +Phase 6 Step 8 completed: Create extras step + +Changes made: + +- Added Extras step UI to the website wizard. +- Added a Docker selector for `docker: "none"` and `docker: "postgres"`. +- Used `@launchkit/schema` `dockerMetadata` for Docker option labels and descriptions. +- Displayed `No Docker setup` and `PostgreSQL Docker Compose` as the two supported Docker choices. +- Disabled PostgreSQL Docker Compose unless `database: "postgres"` is selected. +- Shows the disabled reason `Requires PostgreSQL` when Docker PostgreSQL is unavailable. +- Shows `No Docker setup` as the effective selection when PostgreSQL is not selected. +- Connected Docker selection to shared builder config state. +- Guarded Docker state updates so PostgreSQL Docker Compose cannot be selected without PostgreSQL. +- Docker changes preserve database, ORM, auth, UI, and all other builder config values. +- Added Extras-step validation using the existing schema parsing and compatibility helper path. +- Gated Next navigation on the Extras step when an invalid `database: "none"` plus `docker: "postgres"` config is present. +- Added a concise note that Docker Compose is for local PostgreSQL development and README plus `.env.example` are included by default. +- Confirmed unsupported extras are not exposed. +- Confirmed no preview, download, API route, generator logic, or CLI functionality was added. + +Files changed: + +- `apps/web/components/builder/steps/extras-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/steps.ts` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,260p' context/progress-tracker.md +sed -n '1,300p' .agents/prompts/phase-06/step-8.md +rg --files context apps/web packages/schema/src .agents/prompts/phase-06 +sed -n '301,620p' .agents/prompts/phase-06/step-8.md +sed -n '1,260p' context/architecture.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1160p' context/build-plan.md +sed -n '1,280p' context/project-overview.md +sed -n '281,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '281,620p' context/ui-rules.md +sed -n '1,380p' apps/web/components/builder/builder-shell.tsx +sed -n '1,280p' apps/web/lib/builder/validation.ts +sed -n '1,260p' apps/web/components/builder/steps/database-step.tsx +sed -n '1,260p' apps/web/components/builder/steps/orm-step.tsx +sed -n '1,240p' apps/web/lib/builder/steps.ts +sed -n '1,340p' packages/schema/src/metadata.ts +sed -n '1,220p' packages/schema/src/options.ts +sed -n '1,240p' packages/schema/src/compatibility.ts +npm run typecheck -w apps/web +npm run lint -w apps/web +rg '@launchkit/generator|app/api|createProjectZip|download|preview|Redis|Sentry|analytics|Stripe|Clerk|node:test|node --test' apps/web +git diff --check +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build -w apps/web +npm run build +git diff -- apps/web/components/builder/builder-shell.tsx apps/web/components/builder/steps/extras-step.tsx apps/web/lib/builder/steps.ts apps/web/lib/builder/validation.ts +git status --short +git diff --stat +``` + +Verification: + +- [x] Extras step renders in the wizard. +- [x] Docker selector supports `none` and `postgres`. +- [x] Docker options come from schema metadata. +- [x] Docker PostgreSQL is disabled when `database !== "postgres"`. +- [x] Docker PostgreSQL disabled state shows `Requires PostgreSQL`. +- [x] No Docker setup is shown as selected when PostgreSQL is not selected. +- [x] Selecting Docker PostgreSQL updates `config.docker` only when PostgreSQL is selected. +- [x] Selecting Docker PostgreSQL does not modify `config.database`. +- [x] Selecting no Docker sets `config.docker` to `"none"`. +- [x] Docker updates preserve database, ORM, auth, UI, and other config values. +- [x] Invalid Docker PostgreSQL without PostgreSQL uses schema compatibility validation and prevents Next on the Extras step. +- [x] Unsupported extras are not rendered. +- [x] No preview or download flow was implemented. +- [x] No API route was added. +- [x] No generator logic was added to `apps/web`. +- [x] No CLI functionality was added. +- [x] Later steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` passed across all workspaces when run with elevated permissions for the known Turbopack process/port restriction. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 9: Create Preview step. + +Phase 6 Step 7 completed: Create Auth step + +Changes made: + +- Added Auth step UI to the website wizard. +- Added an auth selector for `auth: "none"` and `auth: "authjs-credentials"`. +- Used `@launchkit/schema` `authMetadata` for auth option labels and descriptions. +- Displayed `No auth` and `Auth.js credentials scaffold` as the two supported auth choices. +- Added concise scaffold messaging for Auth.js credentials: + - communicates that it is scaffold only; + - tells users to connect it to their user model and password verification; + - avoids implying production-ready auth, complete user management, secure password verification, or a sign-in UI. +- Connected auth selection to shared builder config state. +- Auth changes preserve database, ORM, Docker, and all other builder config values. +- Auth.js credentials can be selected without PostgreSQL. +- Auth.js credentials can be selected with PostgreSQL and no ORM. +- Auth.js credentials can be selected with PostgreSQL and Prisma. +- Added Auth-step validation using the existing schema parsing and compatibility helper path. +- Gated Next navigation on the Auth step when the full config is not schema-compatible. +- Confirmed unsupported auth providers are not exposed. +- Confirmed no Docker controls, preview, download, API route, generator logic, or CLI functionality was added. + +Files changed: + +- `apps/web/components/builder/steps/auth-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/steps.ts` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,260p' context/progress-tracker.md +sed -n '1,280p' .agents/prompts/phase-06/step-7.md +rg --files context apps/web packages/schema/src .agents/prompts/phase-06 +sed -n '1,260p' context/architecture.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1160p' context/build-plan.md +sed -n '1,280p' context/project-overview.md +sed -n '281,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '281,620p' context/ui-rules.md +sed -n '1,360p' apps/web/components/builder/builder-shell.tsx +sed -n '1,260p' apps/web/components/builder/steps/orm-step.tsx +sed -n '1,260p' apps/web/lib/builder/validation.ts +sed -n '1,240p' apps/web/lib/builder/steps.ts +sed -n '1,320p' packages/schema/src/metadata.ts +sed -n '1,320p' packages/schema/src/compatibility.ts +sed -n '1,280p' packages/schema/src/config.ts +git status --short +npm run typecheck -w apps/web +npm run lint -w apps/web +rg '@launchkit/generator|app/api|createProjectZip|download|Clerk|Supabase Auth|NextAuth provider|OAuth|docker|preview|node:test|node --test' apps/web +npm run typecheck -w apps/web +npm run lint -w apps/web +git diff --check +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build -w apps/web +npm run build +git diff -- apps/web/components/builder/builder-shell.tsx apps/web/components/builder/steps/auth-step.tsx apps/web/lib/builder/steps.ts apps/web/lib/builder/validation.ts +git status --short +git diff --stat +``` + +Verification: + +- [x] Auth step renders in the wizard. +- [x] Auth selector supports `none` and `authjs-credentials`. +- [x] Auth options come from schema metadata. +- [x] Selecting Auth.js credentials updates `config.auth`. +- [x] Selecting no auth updates `config.auth`. +- [x] Auth updates preserve database, ORM, Docker, and other config values. +- [x] Auth.js credentials can be selected without PostgreSQL. +- [x] Auth.js credentials can be selected with PostgreSQL and no ORM. +- [x] Auth.js credentials can be selected with PostgreSQL and Prisma. +- [x] Auth.js credentials option includes concise scaffold messaging. +- [x] Auth-step validation uses schema parsing and compatibility helpers. +- [x] Incompatible full config state prevents Next on the Auth step. +- [x] Unsupported auth providers are not rendered. +- [x] No Docker controls were added. +- [x] No preview or download flow was implemented. +- [x] No API route was added. +- [x] No generator logic was added to `apps/web`. +- [x] No CLI functionality was added. +- [x] Later steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` initially failed because `authMetadata` has no `recommended` field in the current schema metadata. The Auth step was updated to guard the optional recommended badge. Rerunning passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` passed across all workspaces when run with elevated permissions for the known Turbopack process/port restriction. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 8: Create Extras step. + +Phase 6 Step 6 completed: Create ORM step + +Changes made: + +- Added ORM step UI to the website wizard. +- Added an ORM selector for `orm: "none"` and `orm: "prisma"`. +- Used `@launchkit/schema` `ormMetadata` for ORM option labels, descriptions, and the Prisma recommended indicator. +- Disabled the Prisma option unless `database: "postgres"` is selected. +- Shows the disabled reason `Requires PostgreSQL` when Prisma is unavailable. +- Shows `No ORM` as the effective selection when PostgreSQL is not selected. +- Connected ORM selection to shared builder config state. +- Preserved all other builder config values when changing ORM selection. +- Guarded Prisma state updates so Prisma cannot be selected without PostgreSQL. +- Added ORM-step validation using the existing schema parsing and compatibility helper path. +- Gated Next navigation on the ORM step when an invalid `database: "none"` plus `orm: "prisma"` config is present. +- Confirmed unsupported ORMs are not exposed. +- Confirmed no auth, Docker, preview, download, API route, generator logic, or CLI functionality was added. + +Files changed: + +- `apps/web/components/builder/steps/orm-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/steps.ts` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,240p' context/progress-tracker.md +sed -n '1,260p' .agents/prompts/phase-06/step-6.md +rg --files +sed -n '1,260p' context/architecture.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1160p' context/build-plan.md +sed -n '1,280p' context/project-overview.md +sed -n '281,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '281,620p' context/ui-rules.md +sed -n '1,360p' apps/web/components/builder/builder-shell.tsx +sed -n '1,280p' apps/web/lib/builder/validation.ts +sed -n '1,260p' apps/web/components/builder/steps/database-step.tsx +sed -n '1,280p' apps/web/components/builder/steps/styling-ui-step.tsx +sed -n '1,240p' apps/web/lib/builder/builder-state.ts +sed -n '1,220p' apps/web/lib/builder/steps.ts +sed -n '1,340p' packages/schema/src/metadata.ts +sed -n '1,320p' packages/schema/src/compatibility.ts +sed -n '1,260p' apps/web/components/builder/wizard-step-panel.tsx +sed -n '1,260p' apps/web/components/builder/steps/framework-step.tsx +sed -n '1,260p' apps/web/components/builder/steps/project-step.tsx +cat apps/web/package.json +git status --short +npm run typecheck -w apps/web +npm run lint -w apps/web +rg '@launchkit/generator|app/api|createProjectZip|download|Drizzle|typeorm|sequelize|SQLite|MySQL|Mongo|Supabase|PlanetScale|node:test|node --test' apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build +npm run build +git diff --check +git diff -- apps/web/components/builder/builder-shell.tsx apps/web/components/builder/steps/orm-step.tsx apps/web/lib/builder/steps.ts apps/web/lib/builder/validation.ts +git diff --stat +git status --short +sed -n '1,220p' context/progress-tracker.md +``` + +Verification: + +- [x] ORM step renders in the wizard. +- [x] ORM selector supports `none` and `prisma`. +- [x] ORM options come from schema metadata. +- [x] Prisma recommended indicator is shown from metadata. +- [x] Prisma is disabled when `database !== "postgres"`. +- [x] Prisma disabled state shows `Requires PostgreSQL`. +- [x] No ORM is shown as selected when PostgreSQL is not selected. +- [x] Selecting Prisma updates `config.orm` only when PostgreSQL is selected. +- [x] Selecting Prisma does not modify `config.database`. +- [x] Selecting no ORM sets `config.orm` to `"none"`. +- [x] ORM updates preserve database, auth, Docker, and other config values. +- [x] Invalid Prisma without PostgreSQL uses schema compatibility validation and prevents Next on the ORM step. +- [x] Unsupported ORMs are not rendered. +- [x] No auth controls were added. +- [x] No Docker controls were added. +- [x] No preview or download flow was implemented. +- [x] No API route was added. +- [x] No generator logic was added to `apps/web`. +- [x] No CLI functionality was added. +- [x] Later steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed after rerunning outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` failed in the sandbox for the same Turbopack process/port restriction. Rerunning with elevated permissions passed across all workspaces. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 7: Create Auth step. + +Phase 6 Step 5 completed: Create database step + +Changes made: + +- Added Database step UI to the website wizard. +- Added a database selector for `database: "none"` and `database: "postgres"`. +- Used `@launchkit/schema` `databaseMetadata` for database option labels, descriptions, and the PostgreSQL recommended indicator. +- Connected database selection to shared builder config state. +- Added dependent reset behavior when switching to `database: "none"`: + - resets `orm: "prisma"` to `orm: "none"`; + - resets `docker: "postgres"` to `docker: "none"`; + - leaves `auth` unchanged because Auth.js credentials may work without a database. +- Extended builder compatibility error mapping so schema compatibility issues are available on every related field path. +- Added database-step validation using schema parsing and compatibility helpers through the existing builder validation path. +- Gated Next navigation on the Database step only when database-related schema or compatibility validation fails. +- Confirmed unsupported databases are not exposed. +- Confirmed no ORM, auth, Docker, preview, download, API route, generator logic, or CLI functionality was added. + +Files changed: + +- `apps/web/components/builder/steps/database-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,260p' context/progress-tracker.md +sed -n '1,260p' .agents/prompts/phase-06/step-5.md +rg --files apps/web/components apps/web/lib packages/schema/src context +sed -n '261,520p' .agents/prompts/phase-06/step-5.md +sed -n '1,260p' context/architecture.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1120p' context/build-plan.md +sed -n '1,260p' context/project-overview.md +sed -n '261,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,260p' context/ui-rules.md +sed -n '261,620p' context/ui-rules.md +sed -n '261,900p' context/progress-tracker.md +sed -n '901,1700p' context/progress-tracker.md +sed -n '1701,2600p' context/progress-tracker.md +git status --short +sed -n '1,340p' apps/web/components/builder/builder-shell.tsx +sed -n '1,260p' apps/web/lib/builder/validation.ts +sed -n '1,260p' apps/web/components/builder/steps/styling-ui-step.tsx +sed -n '1,220p' packages/schema/src/metadata.ts +sed -n '1,240p' packages/schema/src/options.ts +npm run typecheck -w apps/web +npm run lint -w apps/web +rg '@launchkit/generator|app/api|createProjectZip|download|MySQL|SQLite|Mongo|Supabase|PlanetScale|node:test|node --test' apps/web/components apps/web/lib apps/web/app +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build +npm run build +git diff --check +git status --short +git diff --stat +git diff -- apps/web/components/builder/builder-shell.tsx apps/web/components/builder/steps/database-step.tsx apps/web/lib/builder/validation.ts +sed -n '1,220p' context/progress-tracker.md +``` + +Verification: + +- [x] Database step renders in the wizard. +- [x] Database selector supports `none` and `postgres`. +- [x] Database options come from schema metadata. +- [x] PostgreSQL recommended indicator is shown from metadata. +- [x] Selecting PostgreSQL updates `config.database`. +- [x] Selecting no database updates `config.database`. +- [x] Selecting no database resets Prisma ORM to none when needed. +- [x] Selecting no database resets PostgreSQL Docker Compose to none when needed. +- [x] Selecting no database does not reset Auth.js credentials. +- [x] Database validation uses schema parsing and compatibility helpers. +- [x] Unsupported databases are not rendered. +- [x] No ORM controls were added. +- [x] No auth controls were added. +- [x] No Docker controls were added. +- [x] No generator logic was added to `apps/web`. +- [x] No API route or download flow was implemented. +- [x] No CLI functionality was added. +- [x] Later steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed after rerunning outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` failed in the sandbox for the same Turbopack process/port restriction. Rerunning with elevated permissions passed across all workspaces. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 6: Create ORM step. + +Phase 6 Step 4 completed: Create Styling and UI step + +Changes made: + +- Added Styling and UI step UI to the website wizard. +- Displayed Tailwind CSS as the fixed MVP styling choice using `@launchkit/schema` styling metadata. +- Added a UI library selector for `ui: "none"` and `ui: "shadcn"` using `@launchkit/schema` UI metadata. +- Added the recommended indicator for shadcn/ui from schema metadata. +- Connected UI library selection to shared builder config state. +- Kept `config.styling` fixed as `"tailwind"` whenever the UI option changes. +- Extended builder validation to include schema compatibility issues through `validateCompatibility()` from `@launchkit/schema`. +- Gated Next navigation on the Styling and UI step only if styling/UI schema or compatibility validation fails. +- Confirmed unsupported styling systems are not exposed. +- Confirmed no `@launchkit/generator` import, API route, zip download flow, CLI work, or later step implementation was added. + +Files changed: + +- `apps/web/components/builder/steps/styling-ui-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,240p' context/progress-tracker.md +sed -n '1,240p' .agents/prompts/phase-06/step-4.md +rg --files +sed -n '1,260p' context/architecture.md +sed -n '261,620p' context/architecture.md +sed -n '621,1040p' context/architecture.md +sed -n '1,360p' context/build-plan.md +sed -n '361,760p' context/build-plan.md +sed -n '761,1120p' context/build-plan.md +sed -n '1,260p' context/project-overview.md +sed -n '261,620p' context/project-overview.md +sed -n '621,980p' context/project-overview.md +sed -n '1,260p' context/ui-rules.md +sed -n '261,620p' context/ui-rules.md +sed -n '241,900p' context/progress-tracker.md +sed -n '901,1600p' context/progress-tracker.md +sed -n '1601,2300p' context/progress-tracker.md +sed -n '2301,3000p' context/progress-tracker.md +sed -n '3001,3700p' context/progress-tracker.md +git status --short +sed -n '1,320p' apps/web/components/builder/builder-shell.tsx +sed -n '1,260p' apps/web/components/builder/steps/framework-step.tsx +sed -n '1,260p' apps/web/components/builder/steps/project-step.tsx +sed -n '1,260p' apps/web/lib/builder/builder-state.ts +sed -n '1,260p' apps/web/lib/builder/validation.ts +sed -n '1,260p' apps/web/lib/builder/steps.ts +sed -n '1,260p' apps/web/components/builder/wizard-step-panel.tsx +sed -n '1,360p' packages/schema/src/metadata.ts +sed -n '1,320p' packages/schema/src/config.ts +sed -n '1,320p' packages/schema/src/compatibility.ts +cat apps/web/package.json +npm run typecheck -w apps/web +npm run lint -w apps/web +rg '@launchkit/generator|app/api|createProjectZip|download|CSS Modules|Sass|Styled Components|Panda CSS|UnoCSS|node:test|node --test' apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build +npm run build +git diff --check +npm run dev -w apps/web -- --hostname 127.0.0.1 --port 3000 +git status --short +git diff --stat +sed -n '1,220p' context/progress-tracker.md +sed -n '1,240p' apps/web/components/builder/steps/styling-ui-step.tsx +``` + +Verification: + +- [x] Styling and UI step renders in the wizard. +- [x] Tailwind CSS is displayed as the fixed styling choice. +- [x] Tailwind styling metadata comes from `@launchkit/schema`. +- [x] UI options are limited to `none` and `shadcn`. +- [x] UI option metadata comes from `@launchkit/schema`. +- [x] shadcn/ui recommended indicator is shown from metadata. +- [x] Selecting a UI option updates `config.ui`. +- [x] UI updates preserve all other builder config values. +- [x] UI updates keep `config.styling` as `"tailwind"`. +- [x] Styling/UI validation uses schema parsing and compatibility helpers. +- [x] Valid `styling: "tailwind"` with `ui: "none"` or `ui: "shadcn"` can advance. +- [x] Unsupported styling systems are not rendered. +- [x] No generator logic was added to `apps/web`. +- [x] No API route or download flow was implemented. +- [x] No CLI functionality was added. +- [x] Later steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Web app build passed after rerunning outside the sandbox. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed after rerunning outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` failed in the sandbox for the same Turbopack process/port restriction. Rerunning with elevated permissions passed across all workspaces. +- `git diff --check` passed. +- `npm run dev -w apps/web -- --hostname 127.0.0.1 --port 3000` failed in the sandbox because binding to `127.0.0.1:3000` was not permitted. The elevated rerun was rejected because the user will run the dev server locally. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server is not running; the user said they will run it themselves. + +Next suggested step: + +- Phase 6 Step 5: Create database step. + +Phase 6 Step 3 completed: Create framework step + +Changes made: + +- Added Framework step UI for the fixed generated-project foundation. +- Displayed the MVP framework stack: Next.js, TypeScript, App Router, and no `src/` project structure. +- Used `@launchkit/schema` metadata for framework, language, router, and project structure labels/descriptions. +- Added framework-step validation using `@launchkit/schema` through the existing builder config validation path. +- Gated Next navigation only if the fixed framework config is somehow invalid. +- Confirmed the default config remains `framework: "next"`, `language: "typescript"`, `router: "app"`, and `projectStructure: "no-src"`. +- Confirmed unsupported framework, language, router, and structure choices are not exposed. +- Confirmed no `@launchkit/generator` import, API route, zip download flow, CLI work, or later step implementation was added. + +Files changed: + +- `apps/web/components/builder/steps/framework-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,220p' context/progress-tracker.md +sed -n '1,360p' .agents/prompts/phase-06/step-3.md +git status --short +rg --files apps/web/components apps/web/lib packages/schema/src +sed -n '1,1040p' context/architecture.md +sed -n '1,1120p' context/build-plan.md +sed -n '1,760p' context/project-overview.md +sed -n '1,520p' context/ui-rules.md +sed -n '1,260p' apps/web/components/builder/builder-shell.tsx +sed -n '1,260p' apps/web/lib/builder/validation.ts +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build +npm run build +git diff --check +git status --short +git diff --stat +rg '@launchkit/generator|app/api|createProjectZip|node:test|node --test|React Router|Remix|Astro|Vue|Svelte|JavaScript|Pages Router|src/' apps/web +git diff -- apps/web/components/builder apps/web/lib/builder +``` + +Verification: + +- [x] Framework step renders in the wizard. +- [x] Framework step shows Next.js. +- [x] Framework step shows TypeScript. +- [x] Framework step shows App Router. +- [x] Framework step shows no `src/` project structure. +- [x] Framework step uses schema metadata. +- [x] Unsupported framework/language/router/structure choices are not exposed. +- [x] Current config remains valid according to `@launchkit/schema`. +- [x] User can continue with the default config. +- [x] No generator logic was added to `apps/web`. +- [x] No API route or download flow was implemented. +- [x] No CLI functionality was added. +- [x] Later steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed after rerunning outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` failed in the sandbox for the same Turbopack process/port restriction. Rerunning with elevated permissions passed across all workspaces. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 4: Create styling and UI step. + +Phase 6 Step 2 completed: Create project step + +Changes made: + +- Added Project step UI for generated project identity. +- Added a project name input connected to shared builder config state. +- Added project name validation using `@launchkit/schema` `LaunchKitConfigSchema`. +- Added inline validation feedback for edited invalid project names. +- Added a package manager selector using `@launchkit/schema` package manager metadata. +- Connected package manager selection to shared builder config state. +- Added builder config patch/update helpers. +- Gated Next navigation when the Project step config is invalid. +- Kept future wizard steps as placeholders. +- Confirmed no `@launchkit/generator` import, API route, zip download flow, CLI work, or later step implementation was added. + +Files changed: + +- `apps/web/components/builder/steps/project-step.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/components/builder/wizard-navigation.tsx` +- `apps/web/components/builder/wizard-step-panel.tsx` +- `apps/web/lib/builder/builder-state.ts` +- `apps/web/lib/builder/validation.ts` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,220p' context/progress-tracker.md +sed -n '1,360p' .agents/prompts/phase-06/step-2.md +rg --files apps/web/components apps/web/lib apps/web/app packages/schema/src +git status --short +sed -n '1,260p' context/architecture.md +sed -n '1,340p' context/build-plan.md +sed -n '1,260p' context/project-overview.md +sed -n '1,280p' context/ui-rules.md +sed -n '261,980p' context/architecture.md +sed -n '341,1120p' context/build-plan.md +sed -n '261,760p' context/project-overview.md +sed -n '281,520p' context/ui-rules.md +sed -n '1,260p' apps/web/components/builder/builder-shell.tsx +sed -n '1,240p' apps/web/components/builder/wizard-step-panel.tsx +sed -n '1,220p' apps/web/components/builder/wizard-navigation.tsx +sed -n '1,260p' packages/schema/src/metadata.ts +sed -n '1,220p' packages/schema/src/options.ts +sed -n '1,220p' packages/schema/src/config.ts +sed -n '1,220p' packages/schema/src/__tests__/config.test.ts +sed -n '1,220p' packages/schema/src/__tests__/metadata.test.ts +cat apps/web/tsconfig.json +mkdir -p apps/web/components/builder/steps +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build +npm run build +git diff --check +git status --short +git diff --stat +rg '@launchkit/generator|app/api|createProjectZip|download|node:test|node --test' apps/web +git diff -- apps/web/components/builder apps/web/lib/builder +``` + +Verification: + +- [x] Project step renders in the wizard. +- [x] Project name input is connected to builder config state. +- [x] Project name validation uses `@launchkit/schema`. +- [x] Invalid edited project names show concise feedback. +- [x] Invalid project names prevent advancing from the Project step. +- [x] Package manager selector supports `npm` and `pnpm`. +- [x] Package manager options come from schema metadata. +- [x] Package manager selection updates builder config state. +- [x] No generator logic was added to `apps/web`. +- [x] No API route or download flow was implemented. +- [x] No CLI functionality was added. +- [x] Future steps remain placeholders. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed after rerunning outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` failed in the sandbox for the same Turbopack process/port restriction. Rerunning with elevated permissions passed across all workspaces. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not started; the user will run it locally. + +Next suggested step: + +- Phase 6 Step 3: Create framework step. + +Phase 6 Step 1 completed: Create website wizard shell + +Changes made: + +- Created the LaunchKit builder home page in `apps/web/app/page.tsx`. +- Added a client-side builder shell with current-step navigation state. +- Added wizard progress, navigation, and placeholder step panel components. +- Added shared wizard step definitions for all 9 MVP steps. +- Added builder state initialization from `@launchkit/schema` `defaultLaunchKitConfig`. +- Added a compact current-selection panel using the initialized builder config. +- Updated app metadata from the default Create Next App copy to LaunchKit. +- Added `@launchkit/schema` as an explicit `apps/web` workspace dependency and updated `package-lock.json`. +- Confirmed no `@launchkit/generator` import, API route, zip download flow, CLI work, or individual wizard step forms were added. + +Files changed: + +- `apps/web/app/page.tsx` +- `apps/web/app/layout.tsx` +- `apps/web/components/builder/builder-shell.tsx` +- `apps/web/components/builder/wizard-progress.tsx` +- `apps/web/components/builder/wizard-navigation.tsx` +- `apps/web/components/builder/wizard-step-panel.tsx` +- `apps/web/lib/builder/steps.ts` +- `apps/web/lib/builder/builder-state.ts` +- `apps/web/package.json` +- `package-lock.json` +- `context/progress-tracker.md` + +Commands run: + +```bash +sed -n '1,220p' context/progress-tracker.md +sed -n '1,360p' .agents/prompts/phase-06/step-1.md +rg --files apps/web +cat apps/web/package.json +cat package.json +sed -n '1,260p' context/project-overview.md +sed -n '1,260p' context/architecture.md +sed -n '1,260p' context/build-plan.md +sed -n '1,260p' context/ui-rules.md +sed -n '1,220p' apps/web/app/page.tsx +sed -n '1,260p' apps/web/app/globals.css +sed -n '1,220p' packages/schema/src/index.ts +cat packages/schema/package.json +sed -n '1,220p' packages/schema/src/defaults.ts +sed -n '1,260p' packages/schema/src/config.ts +cat apps/web/tsconfig.json +sed -n '1,220p' apps/web/app/layout.tsx +sed -n '1,160p' apps/web/lib/utils.ts +rg '"@launchkit/schema"|workspace:' package-lock.json package.json apps packages +sed -n '261,620p' context/project-overview.md +sed -n '261,620p' context/architecture.md +sed -n '261,620p' context/build-plan.md +sed -n '261,620p' context/ui-rules.md +mkdir -p apps/web/components/builder apps/web/lib/builder +npm install --package-lock-only +npm run typecheck -w apps/web +npm run lint -w apps/web +npm run build -w apps/web +npm run build -w apps/web +npm run typecheck +npm run test +npm run lint +npm run build +npm run build +git diff --check +git status --short +git diff --stat +git diff -- apps/web/app/page.tsx apps/web/app/layout.tsx apps/web/components/builder/builder-shell.tsx apps/web/components/builder/wizard-progress.tsx apps/web/components/builder/wizard-navigation.tsx apps/web/components/builder/wizard-step-panel.tsx apps/web/lib/builder/steps.ts apps/web/lib/builder/builder-state.ts apps/web/package.json package-lock.json +npm run dev -- --hostname 127.0.0.1 --port 3000 +npm run dev -w apps/web -- --hostname 127.0.0.1 --port 3000 +``` + +Verification: + +- [x] Website home page renders the LaunchKit builder shell. +- [x] Wizard defines all 9 planned steps: Project, Framework, Styling and UI, Database, ORM, Auth, Extras, Preview, Download. +- [x] Step progress is visible. +- [x] Back and Next navigation state is implemented. +- [x] Back is disabled on the first step. +- [x] Next is disabled on the last step. +- [x] Placeholder content renders for each step. +- [x] Builder config state initializes from `@launchkit/schema`. +- [x] No generator logic was added to `apps/web`. +- [x] No API route or download flow was implemented. +- [x] No CLI functionality was added. +- [x] Web app typecheck passed. +- [x] Web app lint passed. +- [x] Workspace typecheck passed. +- [x] Workspace tests passed. +- [x] Workspace lint passed. +- [x] Workspace build passed after rerunning outside the sandbox. +- [x] `git diff --check` passed. + +Verification result: + +- `npm run typecheck -w apps/web` passed. +- `npm run lint -w apps/web` passed. +- `npm run build -w apps/web` failed in the sandbox because Turbopack could not create/bind a worker process. Rerunning with elevated permissions passed. +- `npm run typecheck` passed across all workspaces. +- `npm run test` passed across workspaces: generator package ran 111 tests, schema package ran 73 tests, and templates package ran 51 tests. +- `npm run lint` passed. +- `npm run build` failed in the sandbox for the same Turbopack process/port restriction. Rerunning with elevated permissions passed across all workspaces. +- `git diff --check` passed. + +Notes/blockers: + +- No frontend component test pattern or app test script exists for `apps/web`, so no new component test stack was added in this step. +- The Turbopack sandbox failure remains an environment restriction; elevated builds pass. +- A local dev server was not left running because sandboxed localhost binding failed and the elevated dev-server rerun was not approved. + +Next suggested step: + +- Phase 6 Step 2: Create project step. + Phase 5 Step 9 completed: Verify Phase 5 completion Changes made: diff --git a/memory.md b/memory.md index 197189b..e6b4551 100644 --- a/memory.md +++ b/memory.md @@ -1,79 +1,104 @@ -# Memory - LaunchKit Phase 5 Complete +# Memory - Phase 6 Step 12 Website Polish -Last updated: 2026-07-02 13:02 JST +Last updated: 2026-07-03 14:44 JST ## What was built -- Completed Phase 5 Step 7: Auth.js credentials template. - - Added `packages/templates/features/authjs-credentials/auth.ts`. - - Added `packages/templates/features/authjs-credentials/app/api/auth/[...nextauth]/route.ts`. - - Added `packages/templates/features/authjs-credentials/README.md`. - - Added `authjsCredentialsTemplateId`. - - Auth.js credentials feature now contributes `next-auth`, `AUTH_SECRET`, template file references, and generated README notes. - - Tests cover Auth.js feature selection, generated files, env/dependency contributions, README warnings, and compatibility. -- Completed Phase 5 Step 8: Docker PostgreSQL template. - - Added `packages/templates/features/docker-postgres/docker-compose.yml`. - - Added `packages/templates/features/docker-postgres/README.md`. - - Added `dockerPostgresTemplateId`. - - Docker PostgreSQL feature now contributes `docker-compose.yml` and generated README notes without npm dependencies or env vars. - - Tests cover Docker feature selection, generated file output, README guidance, no dependency contribution, and separation from Prisma/Auth.js. -- Completed Phase 5 Step 9: Phase 5 completion verification. - - Added `packages/generator/src/__tests__/phase-5-completion.test.ts`. - - Verified real `packages/templates` files compose through the generator template-loader interface for default, shadcn, PostgreSQL, PostgreSQL + Prisma, Auth.js credentials, PostgreSQL + Docker, and full MVP selections. - - Updated `context/progress-tracker.md`: Phase 5 is now `Complete`; Phase 6 is `Ready`. -- Small in-scope fixes from Step 9 verification: - - `generateProject` now loads the base `base/next` template when a `TemplateLoader` is provided. - - Generated files are merged by normalized path so later generated files override duplicate template paths predictably. - - Base Next.js package metadata is now contributed declaratively by `nextFeature`: Next, React, React DOM, TypeScript, app scripts, and version. - - Generated `package.json` now renders `version`. - - Generated README no longer says real templates will be added later. +Phase 6 Step 12 was implemented for the LaunchKit website MVP. + +Responsive polish was applied across the wizard: + +- `apps/web/components/builder/wizard-step-panel.tsx`: removed the extra dashed inner frame and tightened mobile header spacing. +- `apps/web/components/builder/wizard-progress.tsx`: progress cards now use short labels on mobile/tablet and full labels on large screens. +- `apps/web/components/builder/builder-shell.tsx`: improved project-name and current-selection wrapping. +- Preview components now wrap long names or scroll exact command text safely: + - `dependency-list.tsx` + - `env-var-list.tsx` + - `script-list.tsx` + - `stack-summary.tsx` +- Step option cards now wrap badges/radio indicators cleanly on narrow viewports: + - `project-step.tsx` + - `framework-step.tsx` + - `styling-ui-step.tsx` + - `database-step.tsx` + - `orm-step.tsx` + - `auth-step.tsx` + - `extras-step.tsx` +- `apps/web/components/builder/steps/download-step.tsx`: project name and selected stack values wrap instead of truncating. + +Added `apps/web/lib/builder/phase-6-verification.test.ts` with focused Vitest coverage for: + +- required 9-step wizard order; +- supported MVP option values only; +- project-name validation; +- unsupported package manager rejection; +- Auth.js credentials compatibility without PostgreSQL; +- Prisma and Docker PostgreSQL rejection without PostgreSQL; +- preview data for selected stack, dependencies, dev dependencies, scripts, env vars, and file tree; +- unselected optional feature exclusion; +- `src/` path exclusion; +- full-stack preview additions for shadcn, PostgreSQL, Prisma, Auth.js credentials, and Docker. + +Updated `context/progress-tracker.md` with the Step 12 change log, verification results, and manual-QA handoff. ## Decisions made -- Phase 5 is complete only after verifying real template files compose through the existing injected `TemplateLoader`; no production filesystem template loader was added in this phase. -- Keep Phase 6 as the next boundary. Do not start website UI, CLI work, new product options, or unsupported stacks before the Phase 6 prompt. -- Keep Auth.js credentials scaffold intentionally non-production-complete. Developers must add real user lookup and secure password verification. -- Keep Docker PostgreSQL as a local development helper only. It must require PostgreSQL and must not add npm dependencies or conflicting env vars. -- Keep Prisma v7 setup from earlier work: `prisma.config.ts`, generated client output, ESM package type when Prisma is selected, and `@prisma/adapter-pg`. +Phase 6 remains `In Progress` until manual browser/download QA is completed by the user. Step 12 code and automated verification are complete, but the phase should not be marked complete until the user confirms the website MVP works end to end in a real browser. + +No CLI work was started. No new product options were added. Generator logic remains outside UI components. + +The new verification test follows the current generator contract: Prisma scripts include `db:push`, not `db:migrate`. ## Problems solved -- Step 9 verification found that generated projects with a template loader loaded feature templates but not the base Next.js template. Fixed by loading `base/${plan.baseTemplate}` before selected feature templates. -- Step 9 verification found generated `package.json` lacked base Next/React/TypeScript metadata. Fixed by moving base package metadata into the declarative `nextFeature` contribution. -- Step 9 verification found `version` was merged into the package patch but not rendered into generated `package.json`. Fixed renderer output. -- The workspace build still fails inside the sandbox because Turbopack cannot create/bind a worker process. Rerunning `npm run build` with elevated permissions passes. +The first Step 12 web test run failed because the new verification test expected `db:migrate`; the generator currently exposes `db:push`. The test was corrected. + +The first Step 12 web typecheck failed because an intentionally invalid `packageManager: "yarn"` test value needed to be cast through `unknown` before `LaunchKitConfig`. The test was corrected. + +`npm run build -w apps/web` fails inside the sandbox because Turbopack cannot create/bind worker processes. Rerunning the web build with elevated permissions passed. + +Browser QA could not be completed in-session: + +- the in-app browser connector failed with an internal `sandbox-state-meta` Node REPL error; +- the workspace has no Playwright or `@playwright/test`; +- sandboxed localhost `curl` could not connect; +- elevated localhost `curl` was not allowed; +- elevated `npm run dev -w apps/web` found another Next dev server lock/process; +- the user said they will do manual browser/download QA themselves. ## Current state -- `context/progress-tracker.md` says Phase 5 is complete and Phase 6 is ready. -- Phase 5 template layers are implemented and verified: - - Base Next.js - - Tailwind - - shadcn/ui - - PostgreSQL - - Prisma - - Auth.js credentials - - Docker PostgreSQL -- Verification passed: - - `npm run typecheck -w @launchkit/generator` - - `npm test -w @launchkit/generator` (111 tests) - - `npm run typecheck -w @launchkit/templates` - - `npm test -w @launchkit/templates` (51 tests) - - `npm run typecheck -w @launchkit/schema` - - `npm test -w @launchkit/schema` (73 tests) - - `npm run typecheck` - - `npm run test` - - `npm run lint` - - `git diff --check` - - `npm run build` passed when rerun outside the sandbox after the known Turbopack sandbox failure. -- `rg "node:test|node --test" packages apps package.json` returned no matches. -- At the time of saving, the latest tracker state and memory save should be treated as the handoff source of truth. +Automated verification passed: + +- `npm run test -w apps/web`: 4 files, 23 tests. +- `npm run typecheck -w apps/web`. +- `npm run lint -w apps/web`. +- `npm run build -w apps/web` passed when rerun elevated after the sandbox Turbopack failure. +- `npm run typecheck` across workspaces. +- `npm run test` across workspaces: web 23 tests, generator 111 tests, schema 73 tests, templates 51 tests. +- `npm run lint`. +- `git diff --check`. + +The progress tracker says Phase 6 Step 12 responsive polish and automated verification are complete, but Phase 6 is still `In Progress` pending user-run manual browser/download QA. + +At the time memory was saved, `git status --short` was clean before writing this memory file. ## Next session starts with -Run `/remember restore`, then read `context/progress-tracker.md` and the next Phase 6 prompt. Start Phase 6 Step 1: create the website wizard shell. Keep generator logic in `packages/generator`; the website should import shared schema/options and call generator APIs rather than duplicating generation rules. +Wait for the user to report results from manual QA, or ask them to run the website flow at 375px, 768px, 1280px, and 1440px+ widths: + +1. Enter a valid project name. +2. Choose npm or pnpm. +3. Walk through all 9 wizard steps. +4. Try invalid project names and blocked Prisma/Docker combinations. +5. Verify Preview content and no `src/` paths. +6. Click `Generate ZIP`. +7. Confirm the ZIP downloads, is named from the project name, contains a top-level folder, contains expected files, and excludes unsafe/`src/` paths. + +If manual QA passes, update `context/progress-tracker.md` to mark Phase 6 complete. If QA finds issues, make focused fixes in `apps/web/` only. ## Open questions -- Phase 6 prompt is not yet loaded in this memory. Confirm the exact `.agents/prompts/phase-06/...` file before implementing. -- Decide in Phase 6 whether the preview is computed directly from schema/feature metadata or via a lightweight generator preview path, while preserving the architecture boundary. +Did the user-run browser/download QA pass on mobile, tablet, desktop, and wide desktop? + +Should the repo add a browser automation dependency later for repeatable responsive/download QA, or keep this manual until Phase 7? diff --git a/package-lock.json b/package-lock.json index 4350c9a..43eb1e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,11 @@ "apps/web": { "version": "0.1.0", "dependencies": { + "@launchkit/generator": "0.0.0", + "@launchkit/schema": "0.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jszip": "^3.10.1", "lucide-react": "^1.21.0", "next": "16.2.9", "radix-ui": "^1.6.0", @@ -6153,6 +6156,12 @@ "node": ">=6.6.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -8083,6 +8092,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8880,6 +8895,18 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8933,6 +8960,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -9975,6 +10011,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10273,6 +10315,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10568,6 +10616,27 @@ } } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/recast": { "version": "0.23.12", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.12.tgz", @@ -10837,6 +10906,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -10987,6 +11062,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -11340,6 +11421,15 @@ "node": ">= 0.4" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",