Vibecode Featurebase
track this build5 phases, 15 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Featurebase, run a feedback portal, changelog, roadmap, and lightweight support widget. The hard boundary is integrated suite, ai, custom domains, sso, and hosted delivery, plus participant network, synthesis workflow, and collaboration.
You are building a lean indie version of Featurebase. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Featurebase · indie build A portal where users post and vote on requests, an admin view that merges duplicates while preserving every voter and comment, themes linking related requests to the evidence behind them, a roadmap generated from status rather than maintained separately, and a changelog that closes the loop with the people who asked. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with PostgreSQL | requests, votes and merges are relational and the merge operation needs real transactions | | Identity | Email-verified accounts with no password, using signed links | a feedback portal does not need a password database and asking for one costs you submissions | | Search | PostgreSQL full text search | duplicate detection at submission time is the single highest-value feature and it needs decent search | | Email | A transactional provider with a verified domain | closing the loop is the point, and an update that lands in spam does not close it | | Deployment | One server behind a reverse proxy with automatic certificates | a public portal needs a certificate a stranger's browser trusts | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the portal. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free - Why: Merging needs transactions and duplicate detection needs full text search. Both are here. - Get it: Install locally or run it in Docker. - Verify: psql connects and reports 16 or higher - [ ] **A transactional email provider with a verified domain** · free tiers usually cover a small portal - Why: Verification links and status notifications are the whole loop. Without delivery, this is a suggestion box nobody hears back from. - Get it: Create an account, add the DNS records they specify, take the credentials. Domain verification is the slow step, so start it first. - Verify: A verification link email arrives in an inbox and is not in spam - [ ] **A domain with DNS you control** · roughly 10 to 15 USD a year - Why: Needed for the certificate and for the email sending records. A public portal on a raw address will not be trusted. - Get it: Any registrar, then add the A record and the email verification records. - Verify: The hostname resolves and serves a valid certificate - [ ] **Real feedback to seed it with** · free - Why: Duplicate detection, themes and distinct voter counts are all meaningless against invented data, and real feedback contains the near-duplicates the whole design is about. - Get it: Import from wherever feedback currently lives: a spreadsheet, a support inbox, a chat channel. A hundred real items is enough. - Verify: At least a hundred real items are loaded, including some near-duplicates ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. - integrated suite, AI, custom domains, SSO, and hosted delivery - large participant panel - recording and transcription infrastructure - advanced research repository - enterprise governance If one of those is essential to you, that is the reason to keep paying for Featurebase, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Featurebase The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a public feedback portal with a roadmap and a changelog, where duplicates get merged rather than accumulating to replace Featurebase. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with PostgreSQL · requests, votes and merges are relational and the merge operation needs real transactions - Identity: Email-verified accounts with no password, using signed links · a feedback portal does not need a password database and asking for one costs you submissions - Search: PostgreSQL full text search · duplicate detection at submission time is the single highest-value feature and it needs decent search - Email: A transactional provider with a verified domain · closing the loop is the point, and an update that lands in spam does not close it - Deployment: One server behind a reverse proxy with automatic certificates · a public portal needs a certificate a stranger's browser trusts ### Data model (create this before Phase 1) - users(id, email, name, verified_at, is_admin, notify_on_status) - posts(id, title, body, author_id, status, board_id, created_at, merged_into_id, pinned) · merged_into_id is what makes a merge non-destructive - votes(post_id, user_id, created_at) · unique on the pair, so one person is one vote - comments(id, post_id, author_id, body, created_at, is_internal) - themes(id, name, description), theme_posts(theme_id, post_id) · a theme groups requests as evidence for a decision - changelog(id, title, body, published_at, linked_post_ids_json) - statuses(key, label, colour, on_roadmap, notify_on_enter) · configurable, because every team's workflow differs - A merge never deletes. It points one post at another and moves the votes, so the evidence survives and the count is right. ### Phase 1 · A portal people can actually post to Build: A public board listing posts by vote count. Submitting requires an email, verified by a signed link rather than a password. One vote per person per post, enforced in the database. Rate limit submission. Trap: Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. Done when: Someone can submit a request without setting a password and receive a verification link that works. Voting twice on one post does not double count, enforced by a database constraint rather than application logic. A burst of automated submissions is throttled. An unverified submission is visible to admins and marked unverified. Do not build yet: No merging, themes, roadmap or changelog. ### Phase 2 · Catching duplicates before they exist Build: As the title is typed, search existing posts and show similar ones with their vote counts and a way to vote instead of posting. Make voting on an existing request at least as easy as submitting a new one. Trap: Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. Done when: Typing a title similar to an existing post surfaces it before submission. Voting on a suggestion from that list is one action and does not require leaving the form. Search returns results in well under a hundred milliseconds against a few thousand posts. Submitting anyway is still possible, since the search will sometimes be wrong. Do not build yet: No merging or admin tools yet. ### Phase 3 · Merging without losing anything Build: An admin merge pointing one post at another. Move votes, deduplicating anyone who voted on both. Move comments with a note about where they came from. The merged post redirects rather than returning nothing, because people have linked to it. Trap: Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. Done when: Merging moves votes and comments to the target. A person who voted on both posts counts once in the target's total, verified by a hand count. The merged post's URL redirects to the target. Unmerging restores the original state, or the interface says clearly that a merge is final. Do not build yet: No themes, roadmap or changelog. ### Phase 4 · Themes, which is where decisions come from Build: Themes grouping posts across boards, with a description of the underlying problem. A theme shows total distinct voters rather than the sum of vote counts, since one person may have voted on several of its posts. Trap: Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. Done when: A theme groups posts and reports distinct voters rather than a sum, verified against a hand count with a deliberate overlap. Adding a post to a theme does not change its own vote count. A theme links back to every post as evidence. Themes can be ranked by distinct voters. Do not build yet: No roadmap or changelog yet. ### Phase 5 · Roadmap and changelog that close the loop Build: A roadmap generated from post status rather than maintained separately. A changelog linking entries to the posts they resolve. Publishing an entry notifies everyone who voted on a linked post, once. Trap: A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. Done when: The roadmap reflects post statuses with no separate maintenance. Publishing a changelog entry notifies voters on linked posts exactly once, verified by sending twice and counting. A voter who opted out receives nothing. The changelog entry shows which requests it resolved. Do not build yet: Moderation and operations come last. ### Phase 6 · Moderation, deletion and running it publicly Build: Moderation: pin, close, hide and ban, with an audit of who did what. Spam controls on submission. A deletion path removing a person's posts, votes and comments together. Backups and a restore. Trap: A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. Done when: An unverified post is not publicly visible until verified. Spam submissions are throttled and do not appear on the board. Deleting a user removes their posts, votes and comments, verified by searching for their content afterwards. A restore reproduces the board and its vote counts, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The integrated suite around the hosted product: help centre, surveys and the surfaces that share one identity and one design. - Custom domains and single sign-on, which are the reasons companies pay for the higher tiers. - Automatic duplicate detection and theme suggestion by model, which the search-at-submission approach substitutes for at a fraction of the effort. - A participant panel or any source of feedback beyond the people who find your portal, which is a real limitation on what the votes represent. ### README must contain - That posting requires no password and verification happens after submission - That duplicate prevention happens at submission time through search, not through cleanup - That merging never deletes and old URLs redirect - That themes count distinct voters rather than summing votes - That the roadmap is generated from status and cannot drift from it ===== AGENTS.md ===== # Agent instructions · Featurebase indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with PostgreSQL, Email-verified accounts with no password, using signed links, PostgreSQL full text search, A transactional provider with a verified domain, One server behind a reverse proxy with automatic certificates. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ===== BUILD_PLAN.md ===== # Build plan · Featurebase A portal where users post and vote on requests, an admin view that merges duplicates while preserving every voter and comment, themes linking related requests to the evidence behind them, a roadmap generated from status rather than maintained separately, and a changelog that closes the loop with the people who asked. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A portal people can actually post to Someone can submit a request and vote without creating a password. ### Steps 1. Build the board and submission Post list sorted by votes, with a submission form asking for as little as possible. Files: `src/board.js` 2. Verify by signed link No password. A signed, expiring link that confirms the address and nothing more. Files: `src/auth.js` 3. Enforce one vote in the database A unique constraint on post and user. Application-level checks race and eventually double count. Files: `migrations/` ### Done when - [ ] A submission succeeds without a password - [ ] A verification link works and marks the address verified - [ ] Double voting is refused by a database constraint - [ ] A submission burst is throttled ### Watch out - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. ## Phase 2 · Catching duplicates before they exist Someone about to post the fourth copy of a request sees the first three. ### Steps 1. Index posts for search A generated search column with an index. Indexing at write time rather than searching with wildcards. Files: `migrations/` 2. Search as the title is typed Debounced, showing vote counts, because a request with two hundred votes is more persuasive than one with two. Files: `src/suggest.js` 3. Make voting from the suggestion one click Without leaving the form. Any friction here and people post the duplicate anyway. Files: `src/suggest.js` ### Done when - [ ] A similar title surfaces existing posts before submission - [ ] Voting from the suggestion list is one action - [ ] Search returns in under a hundred milliseconds at a few thousand posts - [ ] Submitting anyway remains possible ### Watch out - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. ## Phase 3 · Merging without losing anything Two requests become one, and nobody's vote or comment disappears. ### Steps 1. Merge in one transaction Votes moved with deduplication, comments moved with provenance, the source pointed at the target. All or nothing. Files: `src/merge.js` 2. Deduplicate overlapping voters Someone who voted on both is one vote in the target. Test with a hand-counted case. Files: `src/merge.test.js` 3. Redirect the old URL People have shared it. A dead link makes the merge look like deletion. Files: `src/routes.js` ### Done when - [ ] Merging moves votes and comments to the target - [ ] An overlapping voter counts once, verified by hand count - [ ] The merged URL redirects to the target - [ ] Merge behaviour on undo is either supported or clearly stated as final ### Watch out - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. ## Phase 4 · Themes, which is where decisions come from Related requests group into a theme you can act on, with the evidence attached. ### Steps 1. Model themes across boards A problem statement plus its evidence. Themes are how you decide; posts are how you gather. Files: `src/themes.js` 2. Count distinct voters Across the theme's posts, deduplicated. Test with an overlap you constructed. Files: `src/themes.test.js` 3. Link back to evidence Every post in the theme, so a decision can be justified from what people actually said. Files: `src/themes.js` ### Done when - [ ] A theme reports distinct voters rather than a sum - [ ] The count matches a hand count with a deliberate overlap - [ ] Adding to a theme does not alter a post's own count - [ ] A theme links to every post as evidence ### Watch out - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. ## Phase 5 · Roadmap and changelog that close the loop Status changes are visible, and the people who asked are told when it ships. ### Steps 1. Generate the roadmap from status Configurable which statuses appear. No separately maintained list anywhere. Files: `src/roadmap.js` 2. Link changelog entries to posts The link is what makes the notification meaningful and the loop closed. Files: `src/changelog.js` 3. Notify once, respecting preferences An outbox with a sent record per user per entry. Publishing twice must not notify twice. Files: `src/notify.js` ### Done when - [ ] The roadmap reflects statuses with no separate list - [ ] Publishing twice notifies each voter once - [ ] An opted-out voter receives nothing - [ ] A changelog entry shows the requests it resolved ### Watch out - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ## Not in this build - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. ## After v1, if you want it - Private boards for internal requests alongside the public one - A weekly digest of new posts, which is how the loop stays warm without notifications per post - Linking a post to an issue in your tracker, so status updates without a second edit ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Your PostgreSQL connection string. DATABASE_URL=postgres://user:pass@localhost:5432/feedback # Required. Appears in verification links and notifications, so it must be correct before the first submission. PUBLIC_URL=https://feedback.example.com # Required · secret. Transactional provider credentials. EMAIL_SERVER=smtp://user:pass@smtp.provider.com:587 # Required. Must be on the verified sending domain. EMAIL_FROM=feedback@example.com # Required · secret. Signs verification and unsubscribe links. Rotating it invalidates outstanding links. LINK_SECRET=generate-a-long-random-value # Required. Submission rate limit per address. MAX_POSTS_PER_EMAIL_PER_DAY=5 # Required. Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ROADMAP_STATUSES=planned,in-progress,shipped
You are building a lean indie version of Featurebase. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Featurebase · indie build A portal where users post and vote on requests, an admin view that merges duplicates while preserving every voter and comment, themes linking related requests to the evidence behind them, a roadmap generated from status rather than maintained separately, and a changelog that closes the loop with the people who asked. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with PostgreSQL | requests, votes and merges are relational and the merge operation needs real transactions | | Identity | Email-verified accounts with no password, using signed links | a feedback portal does not need a password database and asking for one costs you submissions | | Search | PostgreSQL full text search | duplicate detection at submission time is the single highest-value feature and it needs decent search | | Email | A transactional provider with a verified domain | closing the loop is the point, and an update that lands in spam does not close it | | Deployment | One server behind a reverse proxy with automatic certificates | a public portal needs a certificate a stranger's browser trusts | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the portal. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free - Why: Merging needs transactions and duplicate detection needs full text search. Both are here. - Get it: Install locally or run it in Docker. - Verify: psql connects and reports 16 or higher - [ ] **A transactional email provider with a verified domain** · free tiers usually cover a small portal - Why: Verification links and status notifications are the whole loop. Without delivery, this is a suggestion box nobody hears back from. - Get it: Create an account, add the DNS records they specify, take the credentials. Domain verification is the slow step, so start it first. - Verify: A verification link email arrives in an inbox and is not in spam - [ ] **A domain with DNS you control** · roughly 10 to 15 USD a year - Why: Needed for the certificate and for the email sending records. A public portal on a raw address will not be trusted. - Get it: Any registrar, then add the A record and the email verification records. - Verify: The hostname resolves and serves a valid certificate - [ ] **Real feedback to seed it with** · free - Why: Duplicate detection, themes and distinct voter counts are all meaningless against invented data, and real feedback contains the near-duplicates the whole design is about. - Get it: Import from wherever feedback currently lives: a spreadsheet, a support inbox, a chat channel. A hundred real items is enough. - Verify: At least a hundred real items are loaded, including some near-duplicates ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. - integrated suite, AI, custom domains, SSO, and hosted delivery - large participant panel - recording and transcription infrastructure - advanced research repository - enterprise governance If one of those is essential to you, that is the reason to keep paying for Featurebase, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Featurebase The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a public feedback portal with a roadmap and a changelog, where duplicates get merged rather than accumulating to replace Featurebase. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with PostgreSQL · requests, votes and merges are relational and the merge operation needs real transactions - Identity: Email-verified accounts with no password, using signed links · a feedback portal does not need a password database and asking for one costs you submissions - Search: PostgreSQL full text search · duplicate detection at submission time is the single highest-value feature and it needs decent search - Email: A transactional provider with a verified domain · closing the loop is the point, and an update that lands in spam does not close it - Deployment: One server behind a reverse proxy with automatic certificates · a public portal needs a certificate a stranger's browser trusts ### Data model (create this before Phase 1) - users(id, email, name, verified_at, is_admin, notify_on_status) - posts(id, title, body, author_id, status, board_id, created_at, merged_into_id, pinned) · merged_into_id is what makes a merge non-destructive - votes(post_id, user_id, created_at) · unique on the pair, so one person is one vote - comments(id, post_id, author_id, body, created_at, is_internal) - themes(id, name, description), theme_posts(theme_id, post_id) · a theme groups requests as evidence for a decision - changelog(id, title, body, published_at, linked_post_ids_json) - statuses(key, label, colour, on_roadmap, notify_on_enter) · configurable, because every team's workflow differs - A merge never deletes. It points one post at another and moves the votes, so the evidence survives and the count is right. ### Phase 1 · A portal people can actually post to Build: A public board listing posts by vote count. Submitting requires an email, verified by a signed link rather than a password. One vote per person per post, enforced in the database. Rate limit submission. Trap: Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. Done when: Someone can submit a request without setting a password and receive a verification link that works. Voting twice on one post does not double count, enforced by a database constraint rather than application logic. A burst of automated submissions is throttled. An unverified submission is visible to admins and marked unverified. Do not build yet: No merging, themes, roadmap or changelog. ### Phase 2 · Catching duplicates before they exist Build: As the title is typed, search existing posts and show similar ones with their vote counts and a way to vote instead of posting. Make voting on an existing request at least as easy as submitting a new one. Trap: Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. Done when: Typing a title similar to an existing post surfaces it before submission. Voting on a suggestion from that list is one action and does not require leaving the form. Search returns results in well under a hundred milliseconds against a few thousand posts. Submitting anyway is still possible, since the search will sometimes be wrong. Do not build yet: No merging or admin tools yet. ### Phase 3 · Merging without losing anything Build: An admin merge pointing one post at another. Move votes, deduplicating anyone who voted on both. Move comments with a note about where they came from. The merged post redirects rather than returning nothing, because people have linked to it. Trap: Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. Done when: Merging moves votes and comments to the target. A person who voted on both posts counts once in the target's total, verified by a hand count. The merged post's URL redirects to the target. Unmerging restores the original state, or the interface says clearly that a merge is final. Do not build yet: No themes, roadmap or changelog. ### Phase 4 · Themes, which is where decisions come from Build: Themes grouping posts across boards, with a description of the underlying problem. A theme shows total distinct voters rather than the sum of vote counts, since one person may have voted on several of its posts. Trap: Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. Done when: A theme groups posts and reports distinct voters rather than a sum, verified against a hand count with a deliberate overlap. Adding a post to a theme does not change its own vote count. A theme links back to every post as evidence. Themes can be ranked by distinct voters. Do not build yet: No roadmap or changelog yet. ### Phase 5 · Roadmap and changelog that close the loop Build: A roadmap generated from post status rather than maintained separately. A changelog linking entries to the posts they resolve. Publishing an entry notifies everyone who voted on a linked post, once. Trap: A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. Done when: The roadmap reflects post statuses with no separate maintenance. Publishing a changelog entry notifies voters on linked posts exactly once, verified by sending twice and counting. A voter who opted out receives nothing. The changelog entry shows which requests it resolved. Do not build yet: Moderation and operations come last. ### Phase 6 · Moderation, deletion and running it publicly Build: Moderation: pin, close, hide and ban, with an audit of who did what. Spam controls on submission. A deletion path removing a person's posts, votes and comments together. Backups and a restore. Trap: A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. Done when: An unverified post is not publicly visible until verified. Spam submissions are throttled and do not appear on the board. Deleting a user removes their posts, votes and comments, verified by searching for their content afterwards. A restore reproduces the board and its vote counts, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The integrated suite around the hosted product: help centre, surveys and the surfaces that share one identity and one design. - Custom domains and single sign-on, which are the reasons companies pay for the higher tiers. - Automatic duplicate detection and theme suggestion by model, which the search-at-submission approach substitutes for at a fraction of the effort. - A participant panel or any source of feedback beyond the people who find your portal, which is a real limitation on what the votes represent. ### README must contain - That posting requires no password and verification happens after submission - That duplicate prevention happens at submission time through search, not through cleanup - That merging never deletes and old URLs redirect - That themes count distinct voters rather than summing votes - That the roadmap is generated from status and cannot drift from it ===== AGENTS.md ===== # Agent instructions · Featurebase indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with PostgreSQL, Email-verified accounts with no password, using signed links, PostgreSQL full text search, A transactional provider with a verified domain, One server behind a reverse proxy with automatic certificates. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ===== BUILD_PLAN.md ===== # Build plan · Featurebase A portal where users post and vote on requests, an admin view that merges duplicates while preserving every voter and comment, themes linking related requests to the evidence behind them, a roadmap generated from status rather than maintained separately, and a changelog that closes the loop with the people who asked. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A portal people can actually post to Someone can submit a request and vote without creating a password. ### Steps 1. Build the board and submission Post list sorted by votes, with a submission form asking for as little as possible. Files: `src/board.js` 2. Verify by signed link No password. A signed, expiring link that confirms the address and nothing more. Files: `src/auth.js` 3. Enforce one vote in the database A unique constraint on post and user. Application-level checks race and eventually double count. Files: `migrations/` ### Done when - [ ] A submission succeeds without a password - [ ] A verification link works and marks the address verified - [ ] Double voting is refused by a database constraint - [ ] A submission burst is throttled ### Watch out - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. ## Phase 2 · Catching duplicates before they exist Someone about to post the fourth copy of a request sees the first three. ### Steps 1. Index posts for search A generated search column with an index. Indexing at write time rather than searching with wildcards. Files: `migrations/` 2. Search as the title is typed Debounced, showing vote counts, because a request with two hundred votes is more persuasive than one with two. Files: `src/suggest.js` 3. Make voting from the suggestion one click Without leaving the form. Any friction here and people post the duplicate anyway. Files: `src/suggest.js` ### Done when - [ ] A similar title surfaces existing posts before submission - [ ] Voting from the suggestion list is one action - [ ] Search returns in under a hundred milliseconds at a few thousand posts - [ ] Submitting anyway remains possible ### Watch out - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. ## Phase 3 · Merging without losing anything Two requests become one, and nobody's vote or comment disappears. ### Steps 1. Merge in one transaction Votes moved with deduplication, comments moved with provenance, the source pointed at the target. All or nothing. Files: `src/merge.js` 2. Deduplicate overlapping voters Someone who voted on both is one vote in the target. Test with a hand-counted case. Files: `src/merge.test.js` 3. Redirect the old URL People have shared it. A dead link makes the merge look like deletion. Files: `src/routes.js` ### Done when - [ ] Merging moves votes and comments to the target - [ ] An overlapping voter counts once, verified by hand count - [ ] The merged URL redirects to the target - [ ] Merge behaviour on undo is either supported or clearly stated as final ### Watch out - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. ## Phase 4 · Themes, which is where decisions come from Related requests group into a theme you can act on, with the evidence attached. ### Steps 1. Model themes across boards A problem statement plus its evidence. Themes are how you decide; posts are how you gather. Files: `src/themes.js` 2. Count distinct voters Across the theme's posts, deduplicated. Test with an overlap you constructed. Files: `src/themes.test.js` 3. Link back to evidence Every post in the theme, so a decision can be justified from what people actually said. Files: `src/themes.js` ### Done when - [ ] A theme reports distinct voters rather than a sum - [ ] The count matches a hand count with a deliberate overlap - [ ] Adding to a theme does not alter a post's own count - [ ] A theme links to every post as evidence ### Watch out - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. ## Phase 5 · Roadmap and changelog that close the loop Status changes are visible, and the people who asked are told when it ships. ### Steps 1. Generate the roadmap from status Configurable which statuses appear. No separately maintained list anywhere. Files: `src/roadmap.js` 2. Link changelog entries to posts The link is what makes the notification meaningful and the loop closed. Files: `src/changelog.js` 3. Notify once, respecting preferences An outbox with a sent record per user per entry. Publishing twice must not notify twice. Files: `src/notify.js` ### Done when - [ ] The roadmap reflects statuses with no separate list - [ ] Publishing twice notifies each voter once - [ ] An opted-out voter receives nothing - [ ] A changelog entry shows the requests it resolved ### Watch out - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ## Not in this build - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. ## After v1, if you want it - Private boards for internal requests alongside the public one - A weekly digest of new posts, which is how the loop stays warm without notifications per post - Linking a post to an issue in your tracker, so status updates without a second edit ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Your PostgreSQL connection string. DATABASE_URL=postgres://user:pass@localhost:5432/feedback # Required. Appears in verification links and notifications, so it must be correct before the first submission. PUBLIC_URL=https://feedback.example.com # Required · secret. Transactional provider credentials. EMAIL_SERVER=smtp://user:pass@smtp.provider.com:587 # Required. Must be on the verified sending domain. EMAIL_FROM=feedback@example.com # Required · secret. Signs verification and unsubscribe links. Rotating it invalidates outstanding links. LINK_SECRET=generate-a-long-random-value # Required. Submission rate limit per address. MAX_POSTS_PER_EMAIL_PER_DAY=5 # Required. Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ROADMAP_STATUSES=planned,in-progress,shipped
You are building a production product version of Featurebase. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Featurebase · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Featurebase, run a feedback portal, changelog, roadmap, and lightweight support widget. The hard boundary is integrated suite, ai, custom domains, sso, and hosted delivery, plus participant network, synthesis workflow, and collaboration. ## Product outcome A portal where posting is easy, duplicates are prevented rather than cleaned up, merging preserves every vote and link, themes count people rather than votes, and shipping something tells the people who asked. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - PostgreSQL - public HTTPS deployment - Resend API key - optional video-call links ## Explicit non-goals for v1 - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. - integrated suite, AI, custom domains, SSO, and hosted delivery - large participant panel - recording and transcription infrastructure - advanced research repository - enterprise governance ## Success criteria - Double voting is confirmed refused by a database constraint - A merge is confirmed to preserve votes, comments and the old URL - Theme distinct voter counts match a hand count with an overlap - Publishing a changelog twice notifies each voter once - Deleting a user is confirmed to leave nothing findable ===== BRIEF.md ===== # Build brief · Featurebase The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a public feedback portal with a roadmap and a changelog, where duplicates get merged rather than accumulating to replace Featurebase. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with PostgreSQL · requests, votes and merges are relational and the merge operation needs real transactions - Identity: Email-verified accounts with no password, using signed links · a feedback portal does not need a password database and asking for one costs you submissions - Search: PostgreSQL full text search · duplicate detection at submission time is the single highest-value feature and it needs decent search - Email: A transactional provider with a verified domain · closing the loop is the point, and an update that lands in spam does not close it - Deployment: One server behind a reverse proxy with automatic certificates · a public portal needs a certificate a stranger's browser trusts ### Data model (create this before Phase 1) - users(id, email, name, verified_at, is_admin, notify_on_status) - posts(id, title, body, author_id, status, board_id, created_at, merged_into_id, pinned) · merged_into_id is what makes a merge non-destructive - votes(post_id, user_id, created_at) · unique on the pair, so one person is one vote - comments(id, post_id, author_id, body, created_at, is_internal) - themes(id, name, description), theme_posts(theme_id, post_id) · a theme groups requests as evidence for a decision - changelog(id, title, body, published_at, linked_post_ids_json) - statuses(key, label, colour, on_roadmap, notify_on_enter) · configurable, because every team's workflow differs - A merge never deletes. It points one post at another and moves the votes, so the evidence survives and the count is right. ### Phase 1 · A portal people can actually post to Build: A public board listing posts by vote count. Submitting requires an email, verified by a signed link rather than a password. One vote per person per post, enforced in the database. Rate limit submission. Trap: Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. Done when: Someone can submit a request without setting a password and receive a verification link that works. Voting twice on one post does not double count, enforced by a database constraint rather than application logic. A burst of automated submissions is throttled. An unverified submission is visible to admins and marked unverified. Do not build yet: No merging, themes, roadmap or changelog. ### Phase 2 · Catching duplicates before they exist Build: As the title is typed, search existing posts and show similar ones with their vote counts and a way to vote instead of posting. Make voting on an existing request at least as easy as submitting a new one. Trap: Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. Done when: Typing a title similar to an existing post surfaces it before submission. Voting on a suggestion from that list is one action and does not require leaving the form. Search returns results in well under a hundred milliseconds against a few thousand posts. Submitting anyway is still possible, since the search will sometimes be wrong. Do not build yet: No merging or admin tools yet. ### Phase 3 · Merging without losing anything Build: An admin merge pointing one post at another. Move votes, deduplicating anyone who voted on both. Move comments with a note about where they came from. The merged post redirects rather than returning nothing, because people have linked to it. Trap: Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. Done when: Merging moves votes and comments to the target. A person who voted on both posts counts once in the target's total, verified by a hand count. The merged post's URL redirects to the target. Unmerging restores the original state, or the interface says clearly that a merge is final. Do not build yet: No themes, roadmap or changelog. ### Phase 4 · Themes, which is where decisions come from Build: Themes grouping posts across boards, with a description of the underlying problem. A theme shows total distinct voters rather than the sum of vote counts, since one person may have voted on several of its posts. Trap: Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. Done when: A theme groups posts and reports distinct voters rather than a sum, verified against a hand count with a deliberate overlap. Adding a post to a theme does not change its own vote count. A theme links back to every post as evidence. Themes can be ranked by distinct voters. Do not build yet: No roadmap or changelog yet. ### Phase 5 · Roadmap and changelog that close the loop Build: A roadmap generated from post status rather than maintained separately. A changelog linking entries to the posts they resolve. Publishing an entry notifies everyone who voted on a linked post, once. Trap: A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. Done when: The roadmap reflects post statuses with no separate maintenance. Publishing a changelog entry notifies voters on linked posts exactly once, verified by sending twice and counting. A voter who opted out receives nothing. The changelog entry shows which requests it resolved. Do not build yet: Moderation and operations come last. ### Phase 6 · Moderation, deletion and running it publicly Build: Moderation: pin, close, hide and ban, with an audit of who did what. Spam controls on submission. A deletion path removing a person's posts, votes and comments together. Backups and a restore. Trap: A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. Done when: An unverified post is not publicly visible until verified. Spam submissions are throttled and do not appear on the board. Deleting a user removes their posts, votes and comments, verified by searching for their content afterwards. A restore reproduces the board and its vote counts, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The integrated suite around the hosted product: help centre, surveys and the surfaces that share one identity and one design. - Custom domains and single sign-on, which are the reasons companies pay for the higher tiers. - Automatic duplicate detection and theme suggestion by model, which the search-at-submission approach substitutes for at a fraction of the effort. - A participant panel or any source of feedback beyond the people who find your portal, which is a real limitation on what the votes represent. ### README must contain - That posting requires no password and verification happens after submission - That duplicate prevention happens at submission time through search, not through cleanup - That merging never deletes and old URLs redirect - That themes count distinct voters rather than summing votes - That the roadmap is generated from status and cannot drift from it ===== ARCHITECTURE.md ===== # Architecture · Featurebase ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with PostgreSQL | requests, votes and merges are relational and the merge operation needs real transactions | | Identity | Email-verified accounts with no password, using signed links | a feedback portal does not need a password database and asking for one costs you submissions | | Search | PostgreSQL full text search | duplicate detection at submission time is the single highest-value feature and it needs decent search | | Email | A transactional provider with a verified domain | closing the loop is the point, and an update that lands in spam does not close it | | Deployment | One server behind a reverse proxy with automatic certificates | a public portal needs a certificate a stranger's browser trusts | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Identity | Email verification without passwords. | Any verification mechanism, provided the barrier stays after the contribution rather than before it. | | Search | Duplicate prevention at submission. | Any search engine. Doing it at submission rather than as cleanup is the decision that matters. | | Merge | Combining posts without loss. | Not swappable for deletion. The redirect and the preserved comments are the point. | | Themes | Grouping evidence for a decision. | Distinct voter counting is a correctness property, not a presentation choice. | | Notify | Closing the loop once per person. | Any transport. The once-per-person record is what stops a republish from spamming everyone who ever voted. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Your PostgreSQL connection string. - `PUBLIC_URL` · required · Appears in verification links and notifications, so it must be correct before the first submission. - `EMAIL_SERVER` · required, secret · Transactional provider credentials. - `EMAIL_FROM` · required · Must be on the verified sending domain. - `LINK_SECRET` · required, secret · Signs verification and unsubscribe links. Rotating it invalidates outstanding links. - `MAX_POSTS_PER_EMAIL_PER_DAY` · required · Submission rate limit per address. - `ROADMAP_STATUSES` · required · Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Featurebase product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with PostgreSQL, Email-verified accounts with no password, using signed links, PostgreSQL full text search, A transactional provider with a verified domain, One server behind a reverse proxy with automatic certificates. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. - A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. ===== MILESTONES.md ===== # Delivery milestones · Featurebase Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A portal people can actually post to Someone can submit a request and vote without creating a password. ### Steps 1. Build the board and submission Post list sorted by votes, with a submission form asking for as little as possible. Files: `src/board.js` 2. Verify by signed link No password. A signed, expiring link that confirms the address and nothing more. Files: `src/auth.js` 3. Enforce one vote in the database A unique constraint on post and user. Application-level checks race and eventually double count. Files: `migrations/` ### Done when - [ ] A submission succeeds without a password - [ ] A verification link works and marks the address verified - [ ] Double voting is refused by a database constraint - [ ] A submission burst is throttled ### Watch out - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. ## M2 · Catching duplicates before they exist Someone about to post the fourth copy of a request sees the first three. ### Steps 1. Index posts for search A generated search column with an index. Indexing at write time rather than searching with wildcards. Files: `migrations/` 2. Search as the title is typed Debounced, showing vote counts, because a request with two hundred votes is more persuasive than one with two. Files: `src/suggest.js` 3. Make voting from the suggestion one click Without leaving the form. Any friction here and people post the duplicate anyway. Files: `src/suggest.js` ### Done when - [ ] A similar title surfaces existing posts before submission - [ ] Voting from the suggestion list is one action - [ ] Search returns in under a hundred milliseconds at a few thousand posts - [ ] Submitting anyway remains possible ### Watch out - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. ## M3 · Merging without losing anything Two requests become one, and nobody's vote or comment disappears. ### Steps 1. Merge in one transaction Votes moved with deduplication, comments moved with provenance, the source pointed at the target. All or nothing. Files: `src/merge.js` 2. Deduplicate overlapping voters Someone who voted on both is one vote in the target. Test with a hand-counted case. Files: `src/merge.test.js` 3. Redirect the old URL People have shared it. A dead link makes the merge look like deletion. Files: `src/routes.js` ### Done when - [ ] Merging moves votes and comments to the target - [ ] An overlapping voter counts once, verified by hand count - [ ] The merged URL redirects to the target - [ ] Merge behaviour on undo is either supported or clearly stated as final ### Watch out - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. ## M4 · Themes, which is where decisions come from Related requests group into a theme you can act on, with the evidence attached. ### Steps 1. Model themes across boards A problem statement plus its evidence. Themes are how you decide; posts are how you gather. Files: `src/themes.js` 2. Count distinct voters Across the theme's posts, deduplicated. Test with an overlap you constructed. Files: `src/themes.test.js` 3. Link back to evidence Every post in the theme, so a decision can be justified from what people actually said. Files: `src/themes.js` ### Done when - [ ] A theme reports distinct voters rather than a sum - [ ] The count matches a hand count with a deliberate overlap - [ ] Adding to a theme does not alter a post's own count - [ ] A theme links to every post as evidence ### Watch out - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. ## M5 · Roadmap and changelog that close the loop Status changes are visible, and the people who asked are told when it ships. ### Steps 1. Generate the roadmap from status Configurable which statuses appear. No separately maintained list anywhere. Files: `src/roadmap.js` 2. Link changelog entries to posts The link is what makes the notification meaningful and the loop closed. Files: `src/changelog.js` 3. Notify once, respecting preferences An outbox with a sent record per user per entry. Publishing twice must not notify twice. Files: `src/notify.js` ### Done when - [ ] The roadmap reflects statuses with no separate list - [ ] Publishing twice notifies each voter once - [ ] An opted-out voter receives nothing - [ ] A changelog entry shows the requests it resolved ### Watch out - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ## M6 · Moderation, deletion and running it publicly (production only) A public portal survives contact with the public, and personal data can be removed. ### Steps 1. Hold posts until verified Publicly invisible until the link is followed. This alone removes most spam from the board. Files: `src/moderation.js` 2. Add moderation with an audit Pin, close, hide, ban, each recorded with who and when. Files: `src/moderation.js` 3. Make deletion complete Posts, votes, comments and notification records. Verify by searching for their content after. Files: `src/delete.js` ### Done when - [ ] Unverified posts are not publicly visible - [ ] Spam submissions are throttled and stay off the board - [ ] Deleting a user leaves nothing findable - [ ] A restore reproduces the board and vote counts, and is dated ### Watch out - A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. ===== OPERATIONS.md ===== # Operations · Featurebase ## Backup Nightly database backups off the machine. The portal holds email addresses of people who gave you feedback, so the backup carries the same obligations as the database. ## Restore Restore and confirm vote counts match a known post and that a merged URL still redirects. Both are easy to break in a restore and both are visible to users. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Email delivery and bounce rates, submission rate, and the proportion of posts that remain unverified. A rising unverified proportion usually means delivery broke rather than that people lost interest. ## Incident checklist If notifications double-send, check the sent record before anything else, since that is the control. If spam reaches the board, the verification gate failed rather than the rate limiter, because verified-before-visible is the stronger of the two. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Double voting is confirmed refused by a database constraint - [ ] A merge is confirmed to preserve votes, comments and the old URL - [ ] Theme distinct voter counts match a hand count with an overlap - [ ] Publishing a changelog twice notifies each voter once - [ ] Deleting a user is confirmed to leave nothing findable ## Launch constraint Do not market omitted Featurebase capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Your PostgreSQL connection string. DATABASE_URL=postgres://user:pass@localhost:5432/feedback # Required. Appears in verification links and notifications, so it must be correct before the first submission. PUBLIC_URL=https://feedback.example.com # Required · secret. Transactional provider credentials. EMAIL_SERVER=smtp://user:pass@smtp.provider.com:587 # Required. Must be on the verified sending domain. EMAIL_FROM=feedback@example.com # Required · secret. Signs verification and unsubscribe links. Rotating it invalidates outstanding links. LINK_SECRET=generate-a-long-random-value # Required. Submission rate limit per address. MAX_POSTS_PER_EMAIL_PER_DAY=5 # Required. Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ROADMAP_STATUSES=planned,in-progress,shipped
# Featurebase · indie build A portal where users post and vote on requests, an admin view that merges duplicates while preserving every voter and comment, themes linking related requests to the evidence behind them, a roadmap generated from status rather than maintained separately, and a changelog that closes the loop with the people who asked. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with PostgreSQL | requests, votes and merges are relational and the merge operation needs real transactions | | Identity | Email-verified accounts with no password, using signed links | a feedback portal does not need a password database and asking for one costs you submissions | | Search | PostgreSQL full text search | duplicate detection at submission time is the single highest-value feature and it needs decent search | | Email | A transactional provider with a verified domain | closing the loop is the point, and an update that lands in spam does not close it | | Deployment | One server behind a reverse proxy with automatic certificates | a public portal needs a certificate a stranger's browser trusts | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the portal. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free - Why: Merging needs transactions and duplicate detection needs full text search. Both are here. - Get it: Install locally or run it in Docker. - Verify: psql connects and reports 16 or higher - [ ] **A transactional email provider with a verified domain** · free tiers usually cover a small portal - Why: Verification links and status notifications are the whole loop. Without delivery, this is a suggestion box nobody hears back from. - Get it: Create an account, add the DNS records they specify, take the credentials. Domain verification is the slow step, so start it first. - Verify: A verification link email arrives in an inbox and is not in spam - [ ] **A domain with DNS you control** · roughly 10 to 15 USD a year - Why: Needed for the certificate and for the email sending records. A public portal on a raw address will not be trusted. - Get it: Any registrar, then add the A record and the email verification records. - Verify: The hostname resolves and serves a valid certificate - [ ] **Real feedback to seed it with** · free - Why: Duplicate detection, themes and distinct voter counts are all meaningless against invented data, and real feedback contains the near-duplicates the whole design is about. - Get it: Import from wherever feedback currently lives: a spreadsheet, a support inbox, a chat channel. A hundred real items is enough. - Verify: At least a hundred real items are loaded, including some near-duplicates ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. - integrated suite, AI, custom domains, SSO, and hosted delivery - large participant panel - recording and transcription infrastructure - advanced research repository - enterprise governance If one of those is essential to you, that is the reason to keep paying for Featurebase, and the README should say so rather than pretend.
# Build brief · Featurebase The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a public feedback portal with a roadmap and a changelog, where duplicates get merged rather than accumulating to replace Featurebase. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with PostgreSQL · requests, votes and merges are relational and the merge operation needs real transactions - Identity: Email-verified accounts with no password, using signed links · a feedback portal does not need a password database and asking for one costs you submissions - Search: PostgreSQL full text search · duplicate detection at submission time is the single highest-value feature and it needs decent search - Email: A transactional provider with a verified domain · closing the loop is the point, and an update that lands in spam does not close it - Deployment: One server behind a reverse proxy with automatic certificates · a public portal needs a certificate a stranger's browser trusts ### Data model (create this before Phase 1) - users(id, email, name, verified_at, is_admin, notify_on_status) - posts(id, title, body, author_id, status, board_id, created_at, merged_into_id, pinned) · merged_into_id is what makes a merge non-destructive - votes(post_id, user_id, created_at) · unique on the pair, so one person is one vote - comments(id, post_id, author_id, body, created_at, is_internal) - themes(id, name, description), theme_posts(theme_id, post_id) · a theme groups requests as evidence for a decision - changelog(id, title, body, published_at, linked_post_ids_json) - statuses(key, label, colour, on_roadmap, notify_on_enter) · configurable, because every team's workflow differs - A merge never deletes. It points one post at another and moves the votes, so the evidence survives and the count is right. ### Phase 1 · A portal people can actually post to Build: A public board listing posts by vote count. Submitting requires an email, verified by a signed link rather than a password. One vote per person per post, enforced in the database. Rate limit submission. Trap: Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. Done when: Someone can submit a request without setting a password and receive a verification link that works. Voting twice on one post does not double count, enforced by a database constraint rather than application logic. A burst of automated submissions is throttled. An unverified submission is visible to admins and marked unverified. Do not build yet: No merging, themes, roadmap or changelog. ### Phase 2 · Catching duplicates before they exist Build: As the title is typed, search existing posts and show similar ones with their vote counts and a way to vote instead of posting. Make voting on an existing request at least as easy as submitting a new one. Trap: Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. Done when: Typing a title similar to an existing post surfaces it before submission. Voting on a suggestion from that list is one action and does not require leaving the form. Search returns results in well under a hundred milliseconds against a few thousand posts. Submitting anyway is still possible, since the search will sometimes be wrong. Do not build yet: No merging or admin tools yet. ### Phase 3 · Merging without losing anything Build: An admin merge pointing one post at another. Move votes, deduplicating anyone who voted on both. Move comments with a note about where they came from. The merged post redirects rather than returning nothing, because people have linked to it. Trap: Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. Done when: Merging moves votes and comments to the target. A person who voted on both posts counts once in the target's total, verified by a hand count. The merged post's URL redirects to the target. Unmerging restores the original state, or the interface says clearly that a merge is final. Do not build yet: No themes, roadmap or changelog. ### Phase 4 · Themes, which is where decisions come from Build: Themes grouping posts across boards, with a description of the underlying problem. A theme shows total distinct voters rather than the sum of vote counts, since one person may have voted on several of its posts. Trap: Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. Done when: A theme groups posts and reports distinct voters rather than a sum, verified against a hand count with a deliberate overlap. Adding a post to a theme does not change its own vote count. A theme links back to every post as evidence. Themes can be ranked by distinct voters. Do not build yet: No roadmap or changelog yet. ### Phase 5 · Roadmap and changelog that close the loop Build: A roadmap generated from post status rather than maintained separately. A changelog linking entries to the posts they resolve. Publishing an entry notifies everyone who voted on a linked post, once. Trap: A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. Done when: The roadmap reflects post statuses with no separate maintenance. Publishing a changelog entry notifies voters on linked posts exactly once, verified by sending twice and counting. A voter who opted out receives nothing. The changelog entry shows which requests it resolved. Do not build yet: Moderation and operations come last. ### Phase 6 · Moderation, deletion and running it publicly Build: Moderation: pin, close, hide and ban, with an audit of who did what. Spam controls on submission. A deletion path removing a person's posts, votes and comments together. Backups and a restore. Trap: A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. Done when: An unverified post is not publicly visible until verified. Spam submissions are throttled and do not appear on the board. Deleting a user removes their posts, votes and comments, verified by searching for their content afterwards. A restore reproduces the board and its vote counts, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The integrated suite around the hosted product: help centre, surveys and the surfaces that share one identity and one design. - Custom domains and single sign-on, which are the reasons companies pay for the higher tiers. - Automatic duplicate detection and theme suggestion by model, which the search-at-submission approach substitutes for at a fraction of the effort. - A participant panel or any source of feedback beyond the people who find your portal, which is a real limitation on what the votes represent. ### README must contain - That posting requires no password and verification happens after submission - That duplicate prevention happens at submission time through search, not through cleanup - That merging never deletes and old URLs redirect - That themes count distinct voters rather than summing votes - That the roadmap is generated from status and cannot drift from it
# Agent instructions · Featurebase indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with PostgreSQL, Email-verified accounts with no password, using signed links, PostgreSQL full text search, A transactional provider with a verified domain, One server behind a reverse proxy with automatic certificates. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need.
# Build plan · Featurebase A portal where users post and vote on requests, an admin view that merges duplicates while preserving every voter and comment, themes linking related requests to the evidence behind them, a roadmap generated from status rather than maintained separately, and a changelog that closes the loop with the people who asked. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A portal people can actually post to Someone can submit a request and vote without creating a password. ### Steps 1. Build the board and submission Post list sorted by votes, with a submission form asking for as little as possible. Files: `src/board.js` 2. Verify by signed link No password. A signed, expiring link that confirms the address and nothing more. Files: `src/auth.js` 3. Enforce one vote in the database A unique constraint on post and user. Application-level checks race and eventually double count. Files: `migrations/` ### Done when - [ ] A submission succeeds without a password - [ ] A verification link works and marks the address verified - [ ] Double voting is refused by a database constraint - [ ] A submission burst is throttled ### Watch out - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. ## Phase 2 · Catching duplicates before they exist Someone about to post the fourth copy of a request sees the first three. ### Steps 1. Index posts for search A generated search column with an index. Indexing at write time rather than searching with wildcards. Files: `migrations/` 2. Search as the title is typed Debounced, showing vote counts, because a request with two hundred votes is more persuasive than one with two. Files: `src/suggest.js` 3. Make voting from the suggestion one click Without leaving the form. Any friction here and people post the duplicate anyway. Files: `src/suggest.js` ### Done when - [ ] A similar title surfaces existing posts before submission - [ ] Voting from the suggestion list is one action - [ ] Search returns in under a hundred milliseconds at a few thousand posts - [ ] Submitting anyway remains possible ### Watch out - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. ## Phase 3 · Merging without losing anything Two requests become one, and nobody's vote or comment disappears. ### Steps 1. Merge in one transaction Votes moved with deduplication, comments moved with provenance, the source pointed at the target. All or nothing. Files: `src/merge.js` 2. Deduplicate overlapping voters Someone who voted on both is one vote in the target. Test with a hand-counted case. Files: `src/merge.test.js` 3. Redirect the old URL People have shared it. A dead link makes the merge look like deletion. Files: `src/routes.js` ### Done when - [ ] Merging moves votes and comments to the target - [ ] An overlapping voter counts once, verified by hand count - [ ] The merged URL redirects to the target - [ ] Merge behaviour on undo is either supported or clearly stated as final ### Watch out - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. ## Phase 4 · Themes, which is where decisions come from Related requests group into a theme you can act on, with the evidence attached. ### Steps 1. Model themes across boards A problem statement plus its evidence. Themes are how you decide; posts are how you gather. Files: `src/themes.js` 2. Count distinct voters Across the theme's posts, deduplicated. Test with an overlap you constructed. Files: `src/themes.test.js` 3. Link back to evidence Every post in the theme, so a decision can be justified from what people actually said. Files: `src/themes.js` ### Done when - [ ] A theme reports distinct voters rather than a sum - [ ] The count matches a hand count with a deliberate overlap - [ ] Adding to a theme does not alter a post's own count - [ ] A theme links to every post as evidence ### Watch out - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. ## Phase 5 · Roadmap and changelog that close the loop Status changes are visible, and the people who asked are told when it ships. ### Steps 1. Generate the roadmap from status Configurable which statuses appear. No separately maintained list anywhere. Files: `src/roadmap.js` 2. Link changelog entries to posts The link is what makes the notification meaningful and the loop closed. Files: `src/changelog.js` 3. Notify once, respecting preferences An outbox with a sent record per user per entry. Publishing twice must not notify twice. Files: `src/notify.js` ### Done when - [ ] The roadmap reflects statuses with no separate list - [ ] Publishing twice notifies each voter once - [ ] An opted-out voter receives nothing - [ ] A changelog entry shows the requests it resolved ### Watch out - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ## Not in this build - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. ## After v1, if you want it - Private boards for internal requests alongside the public one - A weekly digest of new posts, which is how the loop stays warm without notifications per post - Linking a post to an issue in your tracker, so status updates without a second edit
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Your PostgreSQL connection string. DATABASE_URL=postgres://user:pass@localhost:5432/feedback # Required. Appears in verification links and notifications, so it must be correct before the first submission. PUBLIC_URL=https://feedback.example.com # Required · secret. Transactional provider credentials. EMAIL_SERVER=smtp://user:pass@smtp.provider.com:587 # Required. Must be on the verified sending domain. EMAIL_FROM=feedback@example.com # Required · secret. Signs verification and unsubscribe links. Rotating it invalidates outstanding links. LINK_SECRET=generate-a-long-random-value # Required. Submission rate limit per address. MAX_POSTS_PER_EMAIL_PER_DAY=5 # Required. Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ROADMAP_STATUSES=planned,in-progress,shipped
# Featurebase · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Featurebase, run a feedback portal, changelog, roadmap, and lightweight support widget. The hard boundary is integrated suite, ai, custom domains, sso, and hosted delivery, plus participant network, synthesis workflow, and collaboration. ## Product outcome A portal where posting is easy, duplicates are prevented rather than cleaned up, merging preserves every vote and link, themes count people rather than votes, and shipping something tells the people who asked. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - PostgreSQL - public HTTPS deployment - Resend API key - optional video-call links ## Explicit non-goals for v1 - The integrated help centre and survey suite. - Custom domains and single sign-on. - Model-based duplicate detection and theme suggestion. - A participant panel beyond people who find the portal. - integrated suite, AI, custom domains, SSO, and hosted delivery - large participant panel - recording and transcription infrastructure - advanced research repository - enterprise governance ## Success criteria - Double voting is confirmed refused by a database constraint - A merge is confirmed to preserve votes, comments and the old URL - Theme distinct voter counts match a hand count with an overlap - Publishing a changelog twice notifies each voter once - Deleting a user is confirmed to leave nothing findable
# Build brief · Featurebase The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a public feedback portal with a roadmap and a changelog, where duplicates get merged rather than accumulating to replace Featurebase. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with PostgreSQL · requests, votes and merges are relational and the merge operation needs real transactions - Identity: Email-verified accounts with no password, using signed links · a feedback portal does not need a password database and asking for one costs you submissions - Search: PostgreSQL full text search · duplicate detection at submission time is the single highest-value feature and it needs decent search - Email: A transactional provider with a verified domain · closing the loop is the point, and an update that lands in spam does not close it - Deployment: One server behind a reverse proxy with automatic certificates · a public portal needs a certificate a stranger's browser trusts ### Data model (create this before Phase 1) - users(id, email, name, verified_at, is_admin, notify_on_status) - posts(id, title, body, author_id, status, board_id, created_at, merged_into_id, pinned) · merged_into_id is what makes a merge non-destructive - votes(post_id, user_id, created_at) · unique on the pair, so one person is one vote - comments(id, post_id, author_id, body, created_at, is_internal) - themes(id, name, description), theme_posts(theme_id, post_id) · a theme groups requests as evidence for a decision - changelog(id, title, body, published_at, linked_post_ids_json) - statuses(key, label, colour, on_roadmap, notify_on_enter) · configurable, because every team's workflow differs - A merge never deletes. It points one post at another and moves the votes, so the evidence survives and the count is right. ### Phase 1 · A portal people can actually post to Build: A public board listing posts by vote count. Submitting requires an email, verified by a signed link rather than a password. One vote per person per post, enforced in the database. Rate limit submission. Trap: Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. Done when: Someone can submit a request without setting a password and receive a verification link that works. Voting twice on one post does not double count, enforced by a database constraint rather than application logic. A burst of automated submissions is throttled. An unverified submission is visible to admins and marked unverified. Do not build yet: No merging, themes, roadmap or changelog. ### Phase 2 · Catching duplicates before they exist Build: As the title is typed, search existing posts and show similar ones with their vote counts and a way to vote instead of posting. Make voting on an existing request at least as easy as submitting a new one. Trap: Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. Done when: Typing a title similar to an existing post surfaces it before submission. Voting on a suggestion from that list is one action and does not require leaving the form. Search returns results in well under a hundred milliseconds against a few thousand posts. Submitting anyway is still possible, since the search will sometimes be wrong. Do not build yet: No merging or admin tools yet. ### Phase 3 · Merging without losing anything Build: An admin merge pointing one post at another. Move votes, deduplicating anyone who voted on both. Move comments with a note about where they came from. The merged post redirects rather than returning nothing, because people have linked to it. Trap: Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. Done when: Merging moves votes and comments to the target. A person who voted on both posts counts once in the target's total, verified by a hand count. The merged post's URL redirects to the target. Unmerging restores the original state, or the interface says clearly that a merge is final. Do not build yet: No themes, roadmap or changelog. ### Phase 4 · Themes, which is where decisions come from Build: Themes grouping posts across boards, with a description of the underlying problem. A theme shows total distinct voters rather than the sum of vote counts, since one person may have voted on several of its posts. Trap: Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. Done when: A theme groups posts and reports distinct voters rather than a sum, verified against a hand count with a deliberate overlap. Adding a post to a theme does not change its own vote count. A theme links back to every post as evidence. Themes can be ranked by distinct voters. Do not build yet: No roadmap or changelog yet. ### Phase 5 · Roadmap and changelog that close the loop Build: A roadmap generated from post status rather than maintained separately. A changelog linking entries to the posts they resolve. Publishing an entry notifies everyone who voted on a linked post, once. Trap: A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. Done when: The roadmap reflects post statuses with no separate maintenance. Publishing a changelog entry notifies voters on linked posts exactly once, verified by sending twice and counting. A voter who opted out receives nothing. The changelog entry shows which requests it resolved. Do not build yet: Moderation and operations come last. ### Phase 6 · Moderation, deletion and running it publicly Build: Moderation: pin, close, hide and ban, with an audit of who did what. Spam controls on submission. A deletion path removing a person's posts, votes and comments together. Backups and a restore. Trap: A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board. Done when: An unverified post is not publicly visible until verified. Spam submissions are throttled and do not appear on the board. Deleting a user removes their posts, votes and comments, verified by searching for their content afterwards. A restore reproduces the board and its vote counts, and the date is recorded. Do not build yet: Nothing after this. ### Out of scope (and why) - The integrated suite around the hosted product: help centre, surveys and the surfaces that share one identity and one design. - Custom domains and single sign-on, which are the reasons companies pay for the higher tiers. - Automatic duplicate detection and theme suggestion by model, which the search-at-submission approach substitutes for at a fraction of the effort. - A participant panel or any source of feedback beyond the people who find your portal, which is a real limitation on what the votes represent. ### README must contain - That posting requires no password and verification happens after submission - That duplicate prevention happens at submission time through search, not through cleanup - That merging never deletes and old URLs redirect - That themes count distinct voters rather than summing votes - That the roadmap is generated from status and cannot drift from it
# Architecture · Featurebase ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with PostgreSQL | requests, votes and merges are relational and the merge operation needs real transactions | | Identity | Email-verified accounts with no password, using signed links | a feedback portal does not need a password database and asking for one costs you submissions | | Search | PostgreSQL full text search | duplicate detection at submission time is the single highest-value feature and it needs decent search | | Email | A transactional provider with a verified domain | closing the loop is the point, and an update that lands in spam does not close it | | Deployment | One server behind a reverse proxy with automatic certificates | a public portal needs a certificate a stranger's browser trusts | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Identity | Email verification without passwords. | Any verification mechanism, provided the barrier stays after the contribution rather than before it. | | Search | Duplicate prevention at submission. | Any search engine. Doing it at submission rather than as cleanup is the decision that matters. | | Merge | Combining posts without loss. | Not swappable for deletion. The redirect and the preserved comments are the point. | | Themes | Grouping evidence for a decision. | Distinct voter counting is a correctness property, not a presentation choice. | | Notify | Closing the loop once per person. | Any transport. The once-per-person record is what stops a republish from spamming everyone who ever voted. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Your PostgreSQL connection string. - `PUBLIC_URL` · required · Appears in verification links and notifications, so it must be correct before the first submission. - `EMAIL_SERVER` · required, secret · Transactional provider credentials. - `EMAIL_FROM` · required · Must be on the verified sending domain. - `LINK_SECRET` · required, secret · Signs verification and unsubscribe links. Rotating it invalidates outstanding links. - `MAX_POSTS_PER_EMAIL_PER_DAY` · required · Submission rate limit per address. - `ROADMAP_STATUSES` · required · Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Featurebase product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with PostgreSQL, Email-verified accounts with no password, using signed links, PostgreSQL full text search, A transactional provider with a verified domain, One server behind a reverse proxy with automatic certificates. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. - A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board.
# Delivery milestones · Featurebase Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A portal people can actually post to Someone can submit a request and vote without creating a password. ### Steps 1. Build the board and submission Post list sorted by votes, with a submission form asking for as little as possible. Files: `src/board.js` 2. Verify by signed link No password. A signed, expiring link that confirms the address and nothing more. Files: `src/auth.js` 3. Enforce one vote in the database A unique constraint on post and user. Application-level checks race and eventually double count. Files: `migrations/` ### Done when - [ ] A submission succeeds without a password - [ ] A verification link works and marks the address verified - [ ] Double voting is refused by a database constraint - [ ] A submission burst is throttled ### Watch out - Requiring account creation before someone can post loses most of the feedback you would have received, because people submit in the thirty seconds they are annoyed. Verify by email link after submission rather than requiring a password before it, so the barrier comes after the contribution rather than before. ## M2 · Catching duplicates before they exist Someone about to post the fourth copy of a request sees the first three. ### Steps 1. Index posts for search A generated search column with an index. Indexing at write time rather than searching with wildcards. Files: `migrations/` 2. Search as the title is typed Debounced, showing vote counts, because a request with two hundred votes is more persuasive than one with two. Files: `src/suggest.js` 3. Make voting from the suggestion one click Without leaving the form. Any friction here and people post the duplicate anyway. Files: `src/suggest.js` ### Done when - [ ] A similar title surfaces existing posts before submission - [ ] Voting from the suggestion list is one action - [ ] Search returns in under a hundred milliseconds at a few thousand posts - [ ] Submitting anyway remains possible ### Watch out - Merging duplicates after the fact is real work that never finishes, and the vote counts are misleading until it is done. Search at submission time is the cheapest possible intervention and prevents most duplicates, provided voting on the suggestion is one click rather than a navigation away. ## M3 · Merging without losing anything Two requests become one, and nobody's vote or comment disappears. ### Steps 1. Merge in one transaction Votes moved with deduplication, comments moved with provenance, the source pointed at the target. All or nothing. Files: `src/merge.js` 2. Deduplicate overlapping voters Someone who voted on both is one vote in the target. Test with a hand-counted case. Files: `src/merge.test.js` 3. Redirect the old URL People have shared it. A dead link makes the merge look like deletion. Files: `src/routes.js` ### Done when - [ ] Merging moves votes and comments to the target - [ ] An overlapping voter counts once, verified by hand count - [ ] The merged URL redirects to the target - [ ] Merge behaviour on undo is either supported or clearly stated as final ### Watch out - Deleting the duplicate loses its comments and breaks every link anyone shared, including from support tickets and chat. Keep the post and point it at the target, so an old link redirects and the discussion history stays intact. ## M4 · Themes, which is where decisions come from Related requests group into a theme you can act on, with the evidence attached. ### Steps 1. Model themes across boards A problem statement plus its evidence. Themes are how you decide; posts are how you gather. Files: `src/themes.js` 2. Count distinct voters Across the theme's posts, deduplicated. Test with an overlap you constructed. Files: `src/themes.test.js` 3. Link back to evidence Every post in the theme, so a decision can be justified from what people actually said. Files: `src/themes.js` ### Done when - [ ] A theme reports distinct voters rather than a sum - [ ] The count matches a hand count with a deliberate overlap - [ ] Adding to a theme does not alter a post's own count - [ ] A theme links to every post as evidence ### Watch out - Summing vote counts across a theme's posts double counts anyone who voted on more than one, which inflates the theme and misranks it against others. Count distinct voters, which is the number that actually answers how many people want this. ## M5 · Roadmap and changelog that close the loop Status changes are visible, and the people who asked are told when it ships. ### Steps 1. Generate the roadmap from status Configurable which statuses appear. No separately maintained list anywhere. Files: `src/roadmap.js` 2. Link changelog entries to posts The link is what makes the notification meaningful and the loop closed. Files: `src/changelog.js` 3. Notify once, respecting preferences An outbox with a sent record per user per entry. Publishing twice must not notify twice. Files: `src/notify.js` ### Done when - [ ] The roadmap reflects statuses with no separate list - [ ] Publishing twice notifies each voter once - [ ] An opted-out voter receives nothing - [ ] A changelog entry shows the requests it resolved ### Watch out - A roadmap maintained as its own list drifts from the actual statuses within weeks, and then it lies to people. Generate it from status so it cannot disagree, and let configuring which statuses appear be the only control you need. ## M6 · Moderation, deletion and running it publicly (production only) A public portal survives contact with the public, and personal data can be removed. ### Steps 1. Hold posts until verified Publicly invisible until the link is followed. This alone removes most spam from the board. Files: `src/moderation.js` 2. Add moderation with an audit Pin, close, hide, ban, each recorded with who and when. Files: `src/moderation.js` 3. Make deletion complete Posts, votes, comments and notification records. Verify by searching for their content after. Files: `src/delete.js` ### Done when - [ ] Unverified posts are not publicly visible - [ ] Spam submissions are throttled and stay off the board - [ ] Deleting a user leaves nothing findable - [ ] A restore reproduces the board and vote counts, and is dated ### Watch out - A public form collecting email addresses will receive spam within days of being indexed, and the first response is usually to disable submissions. Rate limit by address and by network, and require the verification link before a post is publicly visible, so spam never reaches the board.
# Operations · Featurebase ## Backup Nightly database backups off the machine. The portal holds email addresses of people who gave you feedback, so the backup carries the same obligations as the database. ## Restore Restore and confirm vote counts match a known post and that a merged URL still redirects. Both are easy to break in a restore and both are visible to users. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Email delivery and bounce rates, submission rate, and the proportion of posts that remain unverified. A rising unverified proportion usually means delivery broke rather than that people lost interest. ## Incident checklist If notifications double-send, check the sent record before anything else, since that is the control. If spam reaches the board, the verification gate failed rather than the rate limiter, because verified-before-visible is the stronger of the two. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Double voting is confirmed refused by a database constraint - [ ] A merge is confirmed to preserve votes, comments and the old URL - [ ] Theme distinct voter counts match a hand count with an overlap - [ ] Publishing a changelog twice notifies each voter once - [ ] Deleting a user is confirmed to leave nothing findable ## Launch constraint Do not market omitted Featurebase capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Your PostgreSQL connection string. DATABASE_URL=postgres://user:pass@localhost:5432/feedback # Required. Appears in verification links and notifications, so it must be correct before the first submission. PUBLIC_URL=https://feedback.example.com # Required · secret. Transactional provider credentials. EMAIL_SERVER=smtp://user:pass@smtp.provider.com:587 # Required. Must be on the verified sending domain. EMAIL_FROM=feedback@example.com # Required · secret. Signs verification and unsubscribe links. Rotating it invalidates outstanding links. LINK_SECRET=generate-a-long-random-value # Required. Submission rate limit per address. MAX_POSTS_PER_EMAIL_PER_DAY=5 # Required. Which statuses appear on the roadmap. The roadmap is generated from these and maintained nowhere else. ROADMAP_STATUSES=planned,in-progress,shipped
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Featurebase because people pay either for access to participants or for a research repository that keeps evidence usable across an organization. The recurring cost buys recruitment, consent, scheduling, media storage, transcription, tagging consistency, notifications, search, and retention, not just the visible interface.
xintegrated suite, AI, custom domains, SSO, and hosted delivery
xlarge participant panel
xrecording and transcription infrastructure
xadvanced research repository
xenterprise governance
Featurebase pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | 1 seat; live chat; unlimited conversations; 0 included AI resolutions. |
| growth | — | $29/user | 1+ paid seats; AI support charged per resolution. |
| professional | — | $59/user | 1+ paid seats; 20 free Lite seats; AI support charged per resolution. |
| enterprise | — | $99/user | 1+ paid seats; 50 free Lite seats; AI support charged per resolution. |
free tier1 seat; live chat; unlimited conversations; 0 included AI resolutions.
billingAnnual public pricing per paid seat; exact month-to-month seat prices were not exposed in the checked page output.
hidden costsEvery paid support tier adds $0.49 per AI resolution on top of seat fees; the startup program advertises 86% off and 1 year of Fibi AI free only for qualifying companies founded under 2 years ago with fewer than 6 employees.
verified 2026-08-12 · source ↗
Vibecode Featurebase
Kinda. The core of Featurebase is buildable in a weekend with the prompt on this page, but there are real gaps: integrated suite, AI, custom domains, SSO, and hosted delivery, large participant panel. Read the honest list above before committing.
How much does Featurebase cost?
Featurebase costs about $59/month (Growth, checked 2026-07-31), which is $708 per year.
What do I lose by replacing Featurebase?
Honestly: integrated suite, AI, custom domains, SSO, and hosted delivery; large participant panel; recording and transcription infrastructure; advanced research repository; enterprise governance. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Featurebase?
Yes: Fider (Open-source customer-feedback portal with voting and status workflows.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.