feat: Add PRD Generator app and Docker infrastructure#3
feat: Add PRD Generator app and Docker infrastructure#3flatfinderai-cyber wants to merge 14 commits intomainfrom
Conversation
…files - Interactive 3-step workflow for Product Requirements Documents - Quality gates specification (critical for Ralph-loop automation) - Real-time PRD preview - Direct JSON export for ralph-tui execution - TypeScript + Next.js 14 + React 18 + Tailwind CSS - Complete documentation and distribution info
- DISTRIBUTION-MANIFEST.md: Master registry with all app info - DISTRIBUTION-INFO.md: Designr Hub distribution package details - YoutubeApp/DISTRIBUTION-INFO.md: YouTube App distribution info - Includes installation, quality gates, and deployment instructions
- docker-compose.yml: Multi-service setup (Hub, Labs, PRD Generator, Postgres, Supabase, Redis) - Dockerfiles: Containerized apps for production deployment - .env.example: Complete environment configuration template - Updated PRDGenerator package.json with npm publishing metadata and exports
Reviewer's GuideAdds a new PRD Generator Next.js app with an interactive Q&A workflow that produces PRD.json files for Ralph-loop, wires it for local/dev and npm distribution, and introduces shared Docker/Docker Compose infrastructure plus distribution manifests for all apps. Sequence diagram for PRD generation and download flowsequenceDiagram
actor User
participant Browser
participant HomePage
participant PRDGenerator
participant QuestionSet
participant PRDPreview
participant usePRDStore
User->>Browser: Open PRDGenerator URL
Browser->>HomePage: Render Home component
HomePage->>PRDGenerator: Mount PRDGenerator
loop For each question set
PRDGenerator->>QuestionSet: Render current questions with value
User->>QuestionSet: Select option or type answer
QuestionSet->>PRDGenerator: onChange(value)
PRDGenerator-->>PRDGenerator: handleAnswer(questionId, value)
User->>PRDGenerator: Click Next
PRDGenerator-->>PRDGenerator: handleNext()
end
User->>PRDGenerator: Click Generate PRD on last step
PRDGenerator-->>PRDGenerator: handleNext() set showPreview true
PRDGenerator->>PRDPreview: Render with collected answers
PRDPreview-->>PRDPreview: Derive featureGoal, qualityGates, featureName
User->>PRDGenerator: Click Download PRD.json
PRDGenerator->>usePRDStore: Read prd state
PRDGenerator-->>PRDGenerator: handleDownload() create Blob and URL
PRDGenerator->>Browser: Trigger link.click() download
Browser-->>User: Save prd_branchName.json file
Class diagram for PRD Generator app structureclassDiagram
class HomePage {
+render() ReactNode
}
class RootLayout {
+metadata Metadata
+render(children ReactNode) ReactNode
}
class Header {
+render() ReactNode
}
class PRDGenerator {
-currentSet number
-answers Record_string_string_or_string_array
-showPreview boolean
+PRDGenerator() ReactNode
+handleAnswer(questionId string, value string_or_string_array) void
+handleNext() void
+handleBack() void
+handleDownload() void
}
class QuestionSet {
+question Question
+value string_or_string_array
+onChange(value string_or_string_array) void
+render() ReactNode
}
class PRDPreview {
+answers Record_string_string_or_string_array
+render() ReactNode
-getQualityGateCommand(gate string) string
}
class Question {
+id string
+text string
+options Option_array
+isOpen boolean
+placeholder string
}
class Option {
+label string
+value string
}
class PRDData {
+project string
+branchName string
+description string
+qualityGates string_array
+userStories UserStory_array
}
class UserStory {
+id string
+title string
+description string
+acceptanceCriteria string_array
+priority number
+passes boolean
+dependsOn string_array
}
class PRDStore {
+prd PRDData
+setPRD(prd PRDData) void
+clearPRD() void
}
class usePRDStore {
<<hook>>
}
HomePage --> Header
HomePage --> PRDGenerator
RootLayout o-- HomePage
PRDGenerator --> QuestionSet
PRDGenerator --> PRDPreview
PRDGenerator --> usePRDStore
QuestionSet --> Question
Question --> Option
usePRDStore --> PRDStore
PRDStore --> PRDData
PRDData --> UserStory
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 4 security issues, 6 other issues, and left some high level feedback:
Security issues:
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
General comments:
- The download flow in
PRDGeneratorrelies onprdfrom the Zustand store, butsetPRDis never called anywhere (includingPRDPreview), sohandleDownloadwill never have data to export; consider either building the PRD JSON directly fromanswersinhandleDownloador populating the store when the preview is generated. - The
docker-compose.ymlincludes hardcoded credentials and secrets (e.g.,POSTGRES_PASSWORD: designrand a fixedJWT_SECRET), which is risky even for local setups; it would be better to move these into environment variables (referenced via${...}) and keep the real values in a non-committed.envfile.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The download flow in `PRDGenerator` relies on `prd` from the Zustand store, but `setPRD` is never called anywhere (including `PRDPreview`), so `handleDownload` will never have data to export; consider either building the PRD JSON directly from `answers` in `handleDownload` or populating the store when the preview is generated.
- The `docker-compose.yml` includes hardcoded credentials and secrets (e.g., `POSTGRES_PASSWORD: designr` and a fixed `JWT_SECRET`), which is risky even for local setups; it would be better to move these into environment variables (referenced via `${...}`) and keep the real values in a non-committed `.env` file.
## Individual Comments
### Comment 1
<location> `PRDGenerator/components/PRDPreview.tsx:30-35` </location>
<code_context>
+ }
+ };
+
+ const featureName = primaryFunctionality
+ .split(" ")
+ .slice(0, 3)
+ .join("-")
+ .toLowerCase()
+ .replace(/[^a-z-]/g, "");
+
+ return (
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard `primaryFunctionality` before calling string methods to avoid runtime/type issues.
Because `answers.q3` is typed as `string | string[]`, `primaryFunctionality` may not always be a string, so `.split` can throw at runtime (e.g., if `q3` becomes multi-select). Normalize to a string before using string methods, for example:
```ts
const primaryFunctionalityRaw = answers.q3 ?? "Core functionality not specified";
const primaryFunctionality = Array.isArray(primaryFunctionalityRaw)
? primaryFunctionalityRaw.join(" ")
: primaryFunctionalityRaw;
```
This keeps the runtime safe and the typing clear without casts.
</issue_to_address>
### Comment 2
<location> `PRDGenerator/components/PRDPreview.tsx:14-23` </location>
<code_context>
+ const qualityGates = answers.q5 || "pnpm";
</code_context>
<issue_to_address>
**issue (bug_risk):** Handle the "Other" quality gate option (and potential arrays) explicitly instead of defaulting to pnpm.
When q5 is set to "Other - specify below", `qualityGates` becomes "other", but `getQualityGateCommand` still falls through to the default pnpm command, so the preview never reflects a custom command.
Since `answers.q5` is `string | string[]`, avoid casting to `string` and normalize first, e.g.:
```ts
const rawQualityGates = answers.q5 ?? "pnpm";
const qualityGateKey = Array.isArray(rawQualityGates)
? rawQualityGates[0]
: rawQualityGates;
const qualityGateCommand = getQualityGateCommand(qualityGateKey);
```
If you want to support a real custom command when `value === "other"`, consider adding a separate free-text question (e.g. `q7`) and branching on that here.
</issue_to_address>
### Comment 3
<location> `PRDGenerator/components/PRDGenerator.tsx:107-117` </location>
<code_context>
+ }
+ };
+
+ const handleDownload = () => {
+ if (prd) {
+ const dataStr = JSON.stringify(prd, null, 2);
+ const dataBlob = new Blob([dataStr], { type: "application/json" });
+ const url = URL.createObjectURL(dataBlob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = `prd-${prd.branchName}.json`;
+ link.click();
+ }
+ };
</code_context>
<issue_to_address>
**suggestion (performance):** Revoke the created object URL and clean up the anchor to avoid resource leaks in `handleDownload`.
The object URL and temporary `<a>` element are never cleaned up, which can cause memory to build up in long‑lived sessions. Consider appending the link to the DOM, triggering the click, then removing it and revoking the URL:
```ts
const handleDownload = () => {
if (!prd) return;
const dataStr = JSON.stringify(prd, null, 2);
const dataBlob = new Blob([dataStr], { type: "application/json" });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement("a");
link.href = url;
link.download = `prd-${prd.branchName}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
```
```suggestion
const handleDownload = () => {
if (!prd) return;
const dataStr = JSON.stringify(prd, null, 2);
const dataBlob = new Blob([dataStr], { type: "application/json" });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement("a");
link.href = url;
link.download = `prd-${prd.branchName}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
```
</issue_to_address>
### Comment 4
<location> `PRDGenerator/package.json:35-44` </location>
<code_context>
+ "node": ">=18.0.0",
+ "pnpm": ">=8.0.0"
+ },
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ },
+ "./components": {
+ "types": "./dist/components/index.d.ts",
+ "default": "./dist/components/index.js"
+ },
+ "./store": {
+ "types": "./dist/lib/store.d.ts",
+ "default": "./dist/lib/store.js"
+ }
+ },
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
</code_context>
<issue_to_address>
**issue (bug_risk):** Align the library-style `main/types/exports` with the actual Next.js build outputs to avoid broken published packages.
The package is configured as a publishable library (`main`/`types`/`exports` -> `./dist/...`), but the build is `next build && tsc --emitDeclarationOnly` without an `outDir`. `next build` writes to `.next`, and `tsc` will place `.d.ts` files next to sources, so the referenced `dist/*.js` / `dist/*.d.ts` files will not be created and consumers will see module resolution failures.
To fix this:
- If this is just a Next app, drop `main`, `types`, and `exports` so the package isn’t advertised as a library.
- If it is a library, add a build step that actually emits to `dist` (e.g. `tsc` with `outDir: "dist"` or a bundler) and ensure the `exports` paths match the real outputs.
</issue_to_address>
### Comment 5
<location> `DISTRIBUTION-MANIFEST.md:190-191` </location>
<code_context>
+## 🔗 Integration & APIs
+
+### Supabase Connection
+All apps connect to same Supabase instance:
+```
+Database: PostgreSQL
</code_context>
<issue_to_address>
**issue (typo):** Add missing article in the Supabase connection sentence.
Change this to “All apps connect to the same Supabase instance:” to fix the grammar and improve readability.
```suggestion
### Supabase Connection
All apps connect to the same Supabase instance:
```
</issue_to_address>
### Comment 6
<location> `PRDGenerator/README.md:86` </location>
<code_context>
+2. Execute one story per iteration
+3. Run quality gates automatically
+4. Mark stories complete when passing
+5. Continue until all stories done
+
+## Architecture
</code_context>
<issue_to_address>
**issue (typo):** Improve grammar in the phrase “until all stories done.”
Consider rephrasing to “Continue until all stories are done” or “Continue until all stories are completed.”
```suggestion
5. Continue until all stories are done
```
</issue_to_address>
### Comment 7
<location> `docker-compose.yml:32` </location>
<code_context>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>
### Comment 8
<location> `docker-compose.yml:33` </location>
<code_context>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>
### Comment 9
<location> `.env.example:11` </location>
<code_context>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>
### Comment 10
<location> `.env.example:12` </location>
<code_context>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>Your trial expires on January 22, 2026. Please upgrade to continue using Sourcery ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "default": "./dist/index.js" | ||
| }, | ||
| "./components": { | ||
| "types": "./dist/components/index.d.ts", | ||
| "default": "./dist/components/index.js" | ||
| }, | ||
| "./store": { |
There was a problem hiding this comment.
issue (bug_risk): Align the library-style main/types/exports with the actual Next.js build outputs to avoid broken published packages.
The package is configured as a publishable library (main/types/exports -> ./dist/...), but the build is next build && tsc --emitDeclarationOnly without an outDir. next build writes to .next, and tsc will place .d.ts files next to sources, so the referenced dist/*.js / dist/*.d.ts files will not be created and consumers will see module resolution failures.
To fix this:
- If this is just a Next app, drop
main,types, andexportsso the package isn’t advertised as a library. - If it is a library, add a build step that actually emits to
dist(e.g.tscwithoutDir: "dist"or a bundler) and ensure theexportspaths match the real outputs.
| environment: | ||
| NODE_ENV: development | ||
| NEXT_PUBLIC_SUPABASE_URL: http://supabase:8000 | ||
| NEXT_PUBLIC_SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
There was a problem hiding this comment.
security (generic-api-key): Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
Source: gitleaks
- Install pnpm dependencies (react, lucide-react, typescript types) - Add explicit type annotation for 'prev' parameter in handleAnswer - Add type guard for primaryFunctionality string validation in PRDPreview - All 191 TypeScript errors now resolved to 0 - typecheck passes successfully
- Remove trailing whitespace from lines 3-6 - Fix table column separators (use dashes format) - Change emphasis to proper heading level (#### instead of **) - Wrap all bare URLs in markdown link format - Add language tags (bash) to code blocks - Fix all Trunk and markdownlint violations
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Updated Supabase keys in the example environment file.
Replaced sensitive keys with placeholders and environment variables for security.
Updated Supabase keys to indicate placeholders.
Changed Supabase key values to simple CHANGEME placeholders to avoid triggering security scanners
Clear sensitive keys in the example environment file.
Removed placeholder keys for Supabase environment variables.
What's New
PRD Generator App
Interactive Q&A tool for creating PRD.json files optimized for Ralph-loop automation.
Features:
Tech: Next.js 14 + React 18 + TypeScript + Tailwind CSS
Distribution & Packaging
Documentation
Changed Files
Quality Checklist
Closes #1
Summary by Sourcery
Add a new PRD Generator Next.js app and wire it into the Designr ecosystem with shared Docker-based local infrastructure and distribution docs.
New Features:
Enhancements: