Add an in-app feedback sheet - #154
Merged
Merged
Conversation
Nothing in the app opened an issue or a message to the team: the only way to say something was to find the repository, sign in to GitHub and fill a template. The sheet is one click from the places where something goes wrong and from the account menu, and it sends the message to the service. What travels with a message is on the screen: which control opened the sheet, the route, the build and the browser, and a few facts the control attached, such as the kind of session being started and whether its machine was reachable. Nothing from a terminal: the app never has the plaintext, and the sheet says so. Where the link sits: the new session form, the session password gate, a session that ended, the empty sessions list, error notices beside Retry, the terminal-linked notice, the vault gates, the account menu, the Account page, and the delete account form. The service keeps each message in a feedback table and, when FEEDBACK_TO is set, forwards it by email; a mail failure is logged and the message is still kept. The hosted Worker sets it to founders@pilotprotocol.network, the address the Terms and Privacy pages already give. Five per person, then one every twelve minutes. Deleting an account keeps its messages and clears who sent them, which the privacy policy now says, as do the web app guide and llms.txt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ueq619VZvd44TY7bF43KDR
pstayets
force-pushed
the
feat/in-app-feedback
branch
from
September 14, 2026 22:03
49149ef to
1d01ff4
Compare
# Conflicts: # CHANGELOG.md
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Nothing in the web app let a person tell the team something. The only path was to find the repository, sign in to GitHub, and fill a template, and in the app's whole history no outside user has done that. This adds a feedback sheet inside the app: a kind (Something broke, An idea, A question), a message, a list of exactly what travels with it, and a ticked-by-default "you can write back to me at …" box.
Where it opens from. Every current pop-up, and the moments where people actually get stuck:
new-session); attaches the kind, machine, whether it was reachable, and the form's error if it refusedsession-gate); attaches the host and whether a saved password failed, never the passwordsession-ended)first-run)sessions-error,session-error,machines-error); attaches the error textshell login, under Done (signed-in)vault-setup,vault-unlock,vault-error)account-menu)account)delete-account)What travels with a message, all shown before Send: the control that opened the sheet, the route (path only: no query string, which can name a session, and no hash, which is where a share link keeps its key), the build version stamped by Vite from the root
package.jsonplus the short commit in CI, the browser as "Chrome 129 on macOS", and at most eight short facts the surface attached. Nothing from a terminal, ever: the app only holds ciphertext, and the sheet says so with a request not to paste any.Service.
POST /api/feedback, signed-in only. Messages go into a newfeedbacktable (migration 011, both stores). WithFEEDBACK_TOset they are also forwarded through the existing mailer as a plain note; a provider failure is logged and the message is still kept.wrangler.jsoncsetsFEEDBACK_TOtofounders@pilotprotocol.network, the address the Terms and Privacy pages already give, for the hosted Worker; Compose passes it through from.env. Per-person limit of five, then one every twelve minutes, on top of the existing per-address limiter. Deleting an account keeps its messages and clears who sent them, the same way the activity trail keeps what was typed.Docs. The privacy policy gains feedback in what we keep, email, retention, and deletion, and its last-updated date moves. The web app guide in
docs/content.jsongets a "Send feedback" card,public/llms.txta paragraph, the app README its variable, and.env.examplethe line.Security and privacy. The endpoint needs a Firebase identity and an organization membership. Every field is validated and bounded: kind is an enum, the message is 1–4000 characters, the surface is an identifier or becomes "unknown", the route is stripped to a path, context is at most eight entries with identifier keys and 200-character values, and the user agent is cut at 300. The mail body escapes everything the sender or their browser supplied, and the reply address sits in the body rather than a Reply-To header so the sender's choice is not made by a mail client. Nothing stored is echoed back beyond the id. No new dependencies.
Verification
server/routes/feedback.test.ts), the forwarded mail and its escaping (server/lib/mail-feedback.test.ts), store conformance including deletion (server/lib/store-conformance.test.ts), the API route including the flood (server/app.test.ts), and the client helpers (src/lib/feedback.test.ts)npm run checkandnpm run build:web: root typecheck, 96 tests and the installer, Docker entrypoint, deploy guard, landing SEO, and mobile checks pass; the web build succeeds. Inapp/:npm run typecheckclean,npm run lintshows only pre-existing warnings,npm test868 passed, 3 skippedgo test -race ./...andgo vet ./...: pass;gofmt -lempty;govulncheckreports no vulnerabilities (no Go changed)wrangler.jsoncchange is one public address, no secretNot run: the store conformance suite against Postgres (
npm run test:pg), because no database was available on this machine. The newfeedbackstatements follow thecommentstable's pattern exactly and migration 011 is additive, but a Postgres run in CI or locally before merge is the check that remains.Changelog
Added under Unreleased → Added.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ueq619VZvd44TY7bF43KDR