Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add alpharigel/ios-feedback-skill --skill "ios-feedback-button"
Install specific skill from multi-skill repository
# Description
Add a feedback button to an iOS app that creates GitHub issues via a backend API. Use when the user wants to add in-app feedback, bug reporting, or feature request functionality that flows through to GitHub Issues.
# SKILL.md
name: ios-feedback-button
description: Add a feedback button to an iOS app that creates GitHub issues via a backend API. Use when the user wants to add in-app feedback, bug reporting, or feature request functionality that flows through to GitHub Issues.
Add Feedback Button to iOS App (with GitHub Issue Creation)
Add an in-app feedback system that lets users submit feedback from the iOS app, stores it in the backend database, and automatically creates labeled GitHub issues.
Step 0: Understand the Project
Before writing any code, run these commands to understand the project's structure and conventions:
# Detect the GitHub repo (for the GitHub Issues API target)
git remote get-url origin
# Find backend routers/routes
find . -path '*/routers/*.py' -o -path '*/routes/*.py' -o -path '*/endpoints/*.py' 2>/dev/null | head -20
# Find backend services
find . -path '*/services/*.py' -o -path '*/service/*.py' 2>/dev/null | head -20
# Find backend models/schemas
find . -name 'models.py' -o -name 'schemas.py' -o -name 'types.py' 2>/dev/null | grep -v node_modules | grep -v .venv | head -10
# Find database files
find . -name 'database.py' -o -name 'db.py' 2>/dev/null | grep -v node_modules | grep -v .venv | head -10
# Find backend tests
find . -path '*/tests/test_*.py' -o -path '*/test/*.py' 2>/dev/null | grep -v node_modules | grep -v .venv | head -20
# Find iOS views, models, and network layer
find . -path '*/Views/*/*.swift' 2>/dev/null | head -30
find . -path '*/Models/*.swift' 2>/dev/null | head -20
find . -path '*/Network/*.swift' -o -path '*/API/*.swift' -o -path '*/Services/*.swift' 2>/dev/null | head -20
# Find the root/tab view (where the FAB will go)
find . -name 'ContentView.swift' -o -name 'MainView.swift' -o -name 'TabView.swift' -o -name 'RootView.swift' 2>/dev/null | head -5
# Find auth pattern
find . -name 'auth.py' -o -name 'auth.swift' -o -name 'Auth*.swift' 2>/dev/null | grep -v node_modules | grep -v .venv | head -10
Then read the key files from the results above to understand:
- Where to put new files — match existing directory conventions (e.g., if routers are in app/routers/, add feedback.py there)
- How to write models — match the existing model/schema style (Pydantic, SQLAlchemy, etc.)
- How to write the iOS network layer — match the existing API client pattern
- How to register routes — find the main app file and match how other routers are included
- How auth works — use the same auth dependency/middleware pattern for the feedback endpoint
Match the project's existing patterns exactly. Do not write any code until you've read and understood the existing conventions.
Architecture Overview
The feedback system has 4 layers:
iOS UI (FeedbackView) → iOS Network (FeedbackService) → Backend API (POST /feedback) → GitHub Issues API
Key design principles:
- Graceful degradation: GitHub issue creation is best-effort — if it fails, feedback is still saved
- Auto-collected metadata: Device model, OS version, app version are collected automatically on the client
- User enrichment: Backend looks up the authenticated user's name/email to include in the GitHub issue
- Always-visible FAB: Feedback button is a floating action button visible across all tabs
Implementation Steps
Step 1: Backend — Database Table
Add a feedback table to the database schema:
CREATE TABLE IF NOT EXISTS feedback (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
feedback_title TEXT NOT NULL,
feedback_text TEXT,
app_version TEXT,
device_model TEXT,
os_version TEXT,
user_name TEXT,
user_email TEXT,
github_issue_url TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
);
Step 2: Backend — Pydantic Models
class FeedbackCreate(BaseModel):
feedback_title: str
feedback_text: str | None = None
app_version: str | None = None
device_model: str | None = None
os_version: str | None = None
class FeedbackResponse(BaseModel):
id: UUID
user_id: UUID
feedback_title: str
feedback_text: str | None
app_version: str | None
device_model: str | None
os_version: str | None
user_name: str | None
user_email: str | None
github_issue_url: str | None
created_at: datetime
Step 3: Backend — Feedback Service
Create a service module with these functions:
create_feedback(db, user_id, data)— Insert feedback record, return itcreate_github_issue(feedback_title, feedback_text, user_name, user_email, metadata)— Call GitHub Issues API:- Read
GITHUB_PAT_ISSUESenv var (GitHub PAT withissues:writescope) - POST to
https://api.github.com/repos/{REPO}/issues - Title format:
[Feedback] {feedback_title} - Body includes: user feedback, metadata (device, OS, app version, user info, timestamp)
- Labels:
["feedback"] - Return
html_urlon success,Noneon failure - Catch all exceptions — never let GitHub failures break feedback submission
update_github_url(db, feedback_id, url)— Update thegithub_issue_urlcolumn after issue creation
Step 4: Backend — API Endpoint
Create POST /feedback endpoint:
@router.post("/feedback", response_model=FeedbackResponse, status_code=201)
async def create_feedback_endpoint(data: FeedbackCreate, user_id=Depends(get_current_user), db=Depends(get_db)):
# 1. Look up user details (name, email) from users table
# 2. Save feedback to database
# 3. Attempt GitHub issue creation (async, non-blocking to user)
# 4. If issue created, update github_issue_url in DB and response
# 5. Return feedback record
Register the router in main.py.
Step 5: Backend — Tests
Write tests covering:
- test_create_feedback — minimal feedback (title only) creates record
- test_create_feedback_with_metadata — all metadata fields are persisted
- test_create_feedback_missing_title_rejected — 422 for missing title
- test_github_issue_creation — mock GitHub API, validate issue URL returned
- test_github_issue_failure_still_saves — feedback saved even if GitHub fails
Step 6: iOS — Data Model (DTO)
Create FeedbackDTO.swift:
struct CreateFeedbackDTO: Codable {
let feedbackTitle: String
let feedbackText: String?
let appVersion: String?
let deviceModel: String?
let osVersion: String?
init(title: String, details: String?) {
self.feedbackTitle = title
self.feedbackText = details
// Auto-collect device metadata
self.appVersion = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "?") (\(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "?"))"
self.deviceModel = UIDevice.current.model
self.osVersion = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
}
}
struct FeedbackDTO: Codable {
let id: UUID
let feedbackTitle: String
let feedbackText: String?
let githubIssueUrl: String?
let createdAt: String
}
Step 7: iOS — Network Service
Create FeedbackService.swift:
struct FeedbackService {
static func submit(_ dto: CreateFeedbackDTO) async throws -> FeedbackDTO {
// POST to /feedback with auth header
// Return decoded FeedbackDTO
}
}
Step 8: iOS — Feedback View
Create FeedbackView.swift:
- Form fields:
- Required: "What would you like to see improved?" (title, single-line TextField)
- Optional: "Details" (multi-line TextEditor)
- Submit button: Disabled until title has non-whitespace content; shows loading state
- Success state: Green checkmark overlay + "Thanks for your feedback!" message, auto-dismiss after ~1.2 seconds
- Error state: Red error text, re-enable submit button
- Keyboard:
@FocusStateto auto-focus title field on appear
Step 9: iOS — Floating Action Button
Add a persistent feedback FAB to the app's root/tab view:
// In the main ContentView or TabView, overlay a floating button
.overlay(alignment: .bottomLeading) {
Button { showingFeedback = true } label: {
Image(systemName: "bubble.left.fill")
.font(.title2)
.foregroundStyle(.white)
.frame(width: 50, height: 50)
.background(.blue)
.clipShape(Circle())
.shadow(radius: 4)
}
.padding()
}
.sheet(isPresented: $showingFeedback) {
FeedbackView()
}
The FAB should be visible on all tabs, not just one screen.
Step 10: Environment Setup
- Set
GITHUB_PAT_ISSUESenv var on the backend host (e.g., Railway, Heroku, etc.) - The PAT needs
reposcope (or fine-grainedissues:writeon the target repo) - Create a
feedbacklabel in the GitHub repo if it doesn't exist - If the env var is missing, feedback still saves — just no GitHub issue created
Checklist
- [ ] Database:
feedbacktable created with migration - [ ] Backend: Pydantic models for create/response
- [ ] Backend: Feedback service (create, GitHub issue, update URL)
- [ ] Backend:
POST /feedbackendpoint with auth - [ ] Backend: Tests (5 cases minimum)
- [ ] iOS:
CreateFeedbackDTOwith auto-collected metadata - [ ] iOS:
FeedbackServicenetwork layer - [ ] iOS:
FeedbackViewwith form, loading, success/error states - [ ] iOS: FAB in root view, visible on all tabs
- [ ] Env:
GITHUB_PAT_ISSUESconfigured on backend host - [ ] GitHub:
feedbacklabel created in repo
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.