# Feedback Button — Design **Date:** 2026-03-03 **Status:** Approved **Product:** Peregrine (`PRNG`) --- ## Overview A floating feedback button visible on every Peregrine page that lets beta testers file Forgejo issues directly from the UI. Supports optional attachment of diagnostic data (logs, recent listings) and screenshots — all with explicit per-item user consent and PII masking before anything leaves the app. The backend is intentionally decoupled from Streamlit so it can be wrapped in a FastAPI route when Peregrine moves to a proper Vue/Nuxt frontend. --- ## Goals - Zero-friction bug reporting for beta testers - Privacy-first: nothing is sent without explicit consent + PII preview - Future-proof: backend callable from Streamlit now, FastAPI/Vue later - GitHub support as a config option once public mirrors are active --- ## Architecture ### Files | File | Role | |---|---| | `scripts/feedback_api.py` | Pure Python backend — no Streamlit imports | | `app/feedback.py` | Thin Streamlit UI shell — floating button + dialog | | `app/components/screenshot_capture.py` | Custom Streamlit component using `html2canvas` | | `app/app.py` | One-line addition: inject feedback button in sidebar block | | `.env` / `.env.example` | Add `FORGEJO_API_TOKEN`, `FORGEJO_REPO` | ### Config additions (`.env`) ``` FORGEJO_API_TOKEN=... FORGEJO_REPO=pyr0ball/peregrine # GITHUB_TOKEN= # future — filed when public mirror is active # GITHUB_REPO= # future ``` --- ## Backend (`scripts/feedback_api.py`) Pure Python. No Streamlit dependency. All functions return plain dicts or bytes. ### Functions | Function | Signature | Purpose | |---|---|---| | `collect_context` | `(page: str) → dict` | Page name, app version (git describe), tier, LLM backend, OS, timestamp | | `collect_logs` | `(n: int = 100) → str` | Tail of `.streamlit.log`; `mask_pii()` applied before return | | `collect_listings` | `(n: int = 5) → list[dict]` | Recent jobs from DB — `title`, `company`, `url` only | | `mask_pii` | `(text: str) → str` | Regex: emails → `[email redacted]`, phones → `[phone redacted]` | | `build_issue_body` | `(form, context, attachments) → str` | Assembles final markdown issue body | | `create_forgejo_issue` | `(title, body, labels) → dict` | POST to Forgejo API; returns `{number, url}` | | `upload_attachment` | `(issue_number, image_bytes, filename) → str` | POST screenshot to issue assets; returns attachment URL | | `screenshot_page` | `(port: int) → bytes` | Server-side Playwright fallback screenshot; returns PNG bytes | ### Issue creation — two-step 1. `create_forgejo_issue()` → issue number 2. `upload_attachment(issue_number, ...)` → attachment auto-linked by Forgejo ### Labels Always applied: `beta-feedback`, `needs-triage` Type-based: `bug` / `feature-request` / `question` ### Future multi-destination `feedback_api.py` checks both `FORGEJO_API_TOKEN` and `GITHUB_TOKEN` (when present) and files to whichever destinations are configured. No structural changes needed when GitHub support is added. --- ## UI Flow (`app/feedback.py`) ### Floating button A real Streamlit button inside a keyed container. CSS injected via `st.markdown(unsafe_allow_html=True)` applies `position: fixed; bottom: 2rem; right: 2rem; z-index: 9999` to the container. Hidden entirely when `IS_DEMO=true`. ### Dialog — Step 1: Form - **Type selector:** Bug / Feature Request / Other - **Title:** short text input - **Description:** free-text area - **Reproduction steps:** appears only when Bug is selected (adaptive) ### Dialog — Step 2: Consent + Attachments ``` ┌─ Include diagnostic data? ─────────────────────────────┐ │ [toggle] │ │ └─ if on → expandable preview of exactly what's sent │ │ (logs tailed + masked, listings title/company/url) │ ├─ Screenshot ───────────────────────────────────────────┤ │ [📸 Capture current view] → inline thumbnail preview │ │ [📎 Upload screenshot] → inline thumbnail preview │ ├─ Attribution ──────────────────────────────────────────┤ │ [ ] Include my name & email (shown from user.yaml) │ └────────────────────────────────────────────────────────┘ [Submit] ``` ### Post-submit - Success: "Issue filed → [view on Forgejo]" with clickable link - Error: friendly message + copy-to-clipboard fallback (issue body as text) --- ## Screenshot Component (`app/components/screenshot_capture.py`) Uses `st.components.v1.html()` with `html2canvas` loaded from CDN (no build step). On capture, JS renders the visible viewport to a canvas, encodes as base64 PNG, and returns it to Python via the component value. Server-side Playwright (`screenshot_page()`) is the fallback when the JS component can't return data (e.g., cross-origin iframe restrictions). It screenshots `localhost:` from the server — captures layout/UI state but not user session state. Both paths return `bytes`. The UI shows an inline thumbnail so the user can review before submitting. --- ## Privacy & PII Rules | Data | Included? | Condition | |---|---|---| | App logs | Optional | User toggles on + sees masked preview | | Job listings | Optional (title/company/url only) | User toggles on | | Cover letters / notes | Never | — | | Resume content | Never | — | | Name + email | Optional | User checks attribution checkbox | | Screenshots | Optional | User captures or uploads | `mask_pii()` is applied to all text before it appears in the preview and before submission. Users see exactly what will be sent. --- ## Future: FastAPI wrapper When Peregrine moves to Vue/Nuxt: ```python # server.py (FastAPI) from scripts.feedback_api import build_issue_body, create_forgejo_issue, upload_attachment @app.post("/api/feedback") async def submit_feedback(payload: FeedbackPayload): body = build_issue_body(payload.form, payload.context, payload.attachments) result = create_forgejo_issue(payload.title, body, payload.labels) if payload.screenshot: upload_attachment(result["number"], payload.screenshot, "screenshot.png") return {"url": result["url"]} ``` The Streamlit layer is replaced by a Vue `` component that POSTs to this endpoint. Backend unchanged. --- ## Out of Scope - Rate limiting (beta testers are trusted; add later if abused) - Issue deduplication - In-app issue status tracking - Video / screen recording