-
-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
GitTickets is a client Swift package plus an optional developer-deployed serverless relay.
GitHub has no anonymous write surface. Every issue, every comment, every attachment requires an authenticated request. Three options exist, and only one fits "my mom can report a bug":
| Approach | Verdict |
|---|---|
| Embed a PAT in the app binary | NEVER. Trivially extractable; rotating breaks every shipped build. |
| OAuth Device Flow (end-user signs in to GitHub) | Fine for developer-targeted apps; wrong default for consumer apps because every user needs a GitHub account. |
| GitHub App + tiny developer-hosted relay | Default. Relay holds an installation token scoped to one repo (Issues: write). Anyone can submit without a GitHub account. |
GitTickets ships both relay (default) and Device Flow (opt-in) so adopters pick per app.
┌──────────────────────────┐ HMAC-signed POST ┌───────────────────────┐
│ Host app │ ─────────────────────▶ │ Relay (Vercel/CF) │
│ ┌────────────────────┐ │ │ - Verifies HMAC │
│ │ GitTickets SDK │ │ ◀── { issueURL, ... } │ - Mints App JWT │
│ │ │ │ │ - Stores attachments │
│ │ IssueSubmitter ◀───┼──┤ RelaySubmitter │ - POSTs to GitHub │
│ │ protocol │ │ │ - Per-IP rate-limits │
│ └────────────────────┘ │ └──────────┬────────────┘
│ │ │ │
│ └──────────────┼──── DeviceFlowSubmitter (opt-in) ──▶│
└──────────────────────────┘ ASWebAuthSession + user token │
▼
┌────────────────────┐
│ github.com │
│ /repos/.../issues│
│ label: gittickets│
│ <!-- id: UUID -->│
└────────────────────┘
The UI layer never branches on auth mode — RelaySubmitter and DeviceFlowSubmitter both conform to an internal IssueSubmitter protocol with submit(_ report: Report) async throws -> SubmittedIssue.
One entry point:
GitTickets.configure(.init(
repo: .init(owner: "alanw", name: "MyApp", visibility: .public),
auth: .relay(url: URL(string: "https://relay.example.com")!, sharedSecret: .myAppSecret),
theme: .default,
diagnostics: .default,
privacy: .default,
myIssues: .default
))One menu integration per UI framework:
- SwiftUI:
.commands { GitTicketsCommands { showingReport = true } }— drops "Report an Issue…" into the Help menu (or whicheverCommandGroupPlacementyou pass). Pair withGitTicketsMyIssuesCommands { showingMyIssues = true }for the Phase 2 list. Both items carry default SF Symbol icons (exclamationmark.bubble+tray). - AppKit:
GitTicketsMenuItemFactory.makeReportIssueItem()returns anNSMenuItem(with the matching SF Symbol set onitem.image) wired toReportWindowController.sharedby default. - UIKit:
present(UINavigationController(rootViewController: GitTicketsViewController()), animated: true). The view controller subclassesUIHostingController<GitTicketsView>and ships withtitle = "Report an Issue".
See Build Sequence for the PR ordering. The file tree under Sources/GitTickets/:
-
PublicAPI/—GitTickets.swift,Configuration.swift,AuthMode.swift,Models.swift(incl.IssueComment+CachedReport), errors, theme. -
Auth/—IssueSubmitterprotocol;Relay/(HMAC, payload, client, submitter);DeviceFlow/(coordinator, token store, submitter). -
Networking/—HTTPClient,RateLimitBackoff,UserAgent. The two submitters each own their GitHub-side API calls (relay path →RelayClient; device-flow path → inlineGET /repos/.../issues/N+/commentshelpers onDeviceFlowSubmitter). -
Storage/—SubmissionCache(SQLite),DeviceIdentity,TokenStore,Keychainwrapper. -
Diagnostics/— collector, blob, redaction pipeline,OSLogTailer,DeviceInfo. -
Screenshot/— platform impls (macOS usesScreenCaptureKiton macOS 14+ with a 13 fallback; iOS usesUIGraphicsImageRenderer). -
UI/SwiftUI/,UI/AppKit/,UI/UIKit/— the three integration paths plus the redesigned form / detail / My Reports views. -
Bodybuilder/— assembles the markdown body, embeds the correlation marker; ships anextractUserBody(from:)helper that the detail view's "Your report" card uses to strip the diagnostics block + marker.
Submission IDs are UUIDs embedded as <!-- gittickets-id: UUID --> HTML comments in the issue body. The SDK caches every submitted ID + the assembled body + the kind locally (SQLite). GitTicketsMyIssuesView reads GitTickets.cachedSubmissions() for instant first paint and GitTickets.refreshMyIssues() to ask the active submitter for fresh state.
-
Relay path:
POST {relayURL}/my-issueswith the local ID list. Relay doesGET /repos/.../issues?labels=gittickets&state=all, matches embedded UUIDs server-side, returns metadata + comment counts. Comments load viaPOST {relayURL}/commentsper opened issue. -
Device Flow path: SDK walks the cache's known issue numbers and
GET /repos/.../issues/Nper record (cheaper than walking GitHub Search). Comments load viaGET /repos/.../issues/N/commentsdirectly with the user's token. A 401 from either endpoint wipes the dead token and throws.deviceFlowNotAuthorizedso the form re-prompts.
Tapping a row opens IssueDetailView which surfaces three sections: the cached issue body (via GitTickets.cachedReport(for:)), an "Open on GitHub" link, and the comment thread rendered through AttributedString(markdown:) with interpretedSyntax: .inlineOnlyPreservingWhitespace. Mark-as-read state lives in SQLite via GitTickets.markRepliesRead(submissionID:count:).
GitTicketsTheme (accent override, three fonts, corner radius, header image source enum, submit button style enum) reaches the views via two paths, in precedence order:
-
Configuration.theme— set at app launch viaGitTickets.configure(_:). The top-level views (GitTicketsView,GitTicketsMyIssuesView,IssueDetailView) all resolveconfiguration?.theme ?? envTheme, so this wins for most adopters. -
\.gitTicketsThemeSwiftUI environment value — useful when you want a per-presentation override without rewiringConfiguration.
Defaults inherit the host app's Color.accentColor and use system semantic surface colors (.windowBackgroundColor / .systemGroupedBackground etc.), so the package adapts to light/dark and any host accent automatically. The visual look + tokens for the v1.0 design come from design/design_handoff_gittickets_views_generic/ — see the design folder's README for the full equivalences table.
- Don't ship a PAT-in-binary mode. Documented in Threat Model.
- Don't auto-capture screenshots. User must tap "Add Screenshot."
- Don't bundle fonts or color assets. Borrowed appearance beats imposed appearance.
- Don't depend on any production Swift package. System frameworks only.
- Don't try to render
.github/ISSUE_TEMPLATE/*.ymlIssue Forms in v1 — see Footgun — Issue Forms Are Web-UI-Only (web-UI-only field semantics; the GitHub API exposes raw markdown). v1.1 candidate.
Last updated: 2026-06-06 — refreshed for v1.0 shipped (UI inits + Phase 2 paths + theming precedence)
Edited in the repo's wiki/ folder and managed by Memophant. See Wiki Maintenance.
Start here
Architecture
Operations
Reference
- Patterns & Gotchas
- Build Sequence — shipped record
- Wiki Maintenance
Adopter-facing docs
Design