Skip to content

Add an in-app feedback sheet - #154

Merged
TeoSlayer merged 2 commits into
TeoSlayer:mainfrom
pstayets:feat/in-app-feedback
Sep 14, 2026
Merged

TeoSlayer merged 2 commits into
TeoSlayer:mainfrom
pstayets:feat/in-app-feedback

Conversation

@pstayets

@pstayets pstayets commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

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:

  • the New session form, under the buttons (new-session); attaches the kind, machine, whether it was reachable, and the form's error if it refused
  • the session password gate, after Decrypt terminal (session-gate); attaches the host and whether a saved password failed, never the password
  • the card shown when a session ended, went missing, or could not be opened (session-ended)
  • the empty sessions list, under the three first-run steps (first-run)
  • error notices on the sessions, session, and machines pages, as "Report" beside Retry (sessions-error, session-error, machines-error); attaches the error text
  • the terminal-linked notice after shell login, under Done (signed-in)
  • the vault setup, unlock, and error gates, under the card (vault-setup, vault-unlock, vault-error)
  • the account menu, between Account and Sign out, on the rail and in the phone top bar (account-menu)
  • the Account page actions row (account)
  • the delete account form, "Tell us why you are leaving" (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.json plus 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 new feedback table (migration 011, both stores). With FEEDBACK_TO set they are also forwarded through the existing mailer as a plain note; a provider failure is logged and the message is still kept. wrangler.jsonc sets FEEDBACK_TO to founders@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.json gets a "Send feedback" card, public/llms.txt a paragraph, the app README its variable, and .env.example the 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

  • Added or updated regression tests where behavior changed: 27 new tests, covering route validation and forwarding (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)
  • Ran npm run check and npm run build:web: root typecheck, 96 tests and the installer, Docker entrypoint, deploy guard, landing SEO, and mobile checks pass; the web build succeeds. In app/: npm run typecheck clean, npm run lint shows only pre-existing warnings, npm test 868 passed, 3 skipped
  • Ran go test -race ./... and go vet ./...: pass; gofmt -l empty; govulncheck reports no vulnerabilities (no Go changed)
  • Considered bearer-link, terminal-input, secret, and privacy implications: see above
  • Did not include real share links, terminal contents, credentials, or Cloudflare configuration: the wrangler.jsonc change is one public address, no secret

Not run: the store conformance suite against Postgres (npm run test:pg), because no database was available on this machine. The new feedback statements follow the comments table'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

@pstayets
pstayets requested a review from TeoSlayer as a code owner September 14, 2026 22:00
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
pstayets force-pushed the feat/in-app-feedback branch from 49149ef to 1d01ff4 Compare September 14, 2026 22:03
@TeoSlayer
TeoSlayer merged commit 0752528 into TeoSlayer:main Sep 14, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants