# Gitea Mobile — Roadmap A mobile-first PWA wrapping the Gitea API for managing issues and pull requests across multiple repos and organizations from an iPhone. ## Tech Stack | Layer | Choice | Why | | -------------- | ------------------------------------------- | ------------------------------------------------------------------- | | **Backend** | Go + Gitea SDK (`code.gitea.io/sdk/gitea`) | Native SDK covers every API surface, single static binary, tiny image | | **Frontend** | HTMX + Go `html/template` + hand-rolled CSS | Zero client-side state, no build step, no node_modules, ~14KB JS | | **Container** | Multi-stage Dockerfile → distroless (~15MB) | Minimal attack surface, fast pull | | **Deployment** | Kustomize manifests + FluxCD GitOps | Matches existing Talos cluster patterns | ## Project Structure ``` / ├── cmd/server/main.go # entrypoint ├── internal/ │ ├── config/config.go # env-based configuration │ ├── gitea/client.go # Gitea SDK wrapper / aggregation layer │ ├── handlers/ # HTTP handlers │ │ ├── issues.go │ │ ├── pulls.go │ │ ├── triage.go │ │ └── auth.go │ ├── middleware/ # auth middleware, logging │ └── templates/ # Go html/template files (for HTMX) ├── static/ # CSS, JS (htmx.min.js), icons, manifest ├── Dockerfile ├── flake.nix └── go.mod ``` --- ## Phase 1: Backend ### 1.1 — Project Scaffolding - Initialize Go module: `go mod init gitea.leeworks.dev/0xwheatyz/gitea-mobile` - Create `flake.nix` with Go 1.22+ toolchain and `air` for live reload - Set up directory structure per the project layout above ### 1.2 — Configuration All config via environment variables (12-factor): | Variable | Purpose | Default | | ---------------- | ------------------------------------------------ | -------- | | `GITEA_URL` | Base URL of the Gitea instance | required | | `GITEA_TOKEN` | API token (or per-user via cookie) | optional | | `LISTEN_ADDR` | Server listen address | `:8080` | | `SESSION_SECRET` | HMAC key for signing session cookies | required | ### 1.3 — Authentication **v1: Token-in-cookie (simple, recommended start)** - User enters their Gitea API token once on a settings page - Token stored in a signed, encrypted HTTP-only cookie - All API calls use the user's token — respects Gitea's permission model - Cookies set with `HttpOnly`, `Secure`, `SameSite=Strict` **v2 (future): Authentik SSO integration** - Traefik IngressRoute already supports Authentik middleware - Map Authentik identity to a stored Gitea token - Consistent with other cluster apps ### 1.4 — Gitea Aggregation Layer `internal/gitea/client.go` — core aggregation functions the raw API doesn't provide: | Function | Purpose | | ------------------------------- | -------------------------------------------------------- | | `ListAllIssues(orgs []string)` | Fan-out across repos with `errgroup`, merge + sort by updated time | | `ListAllPullRequests(orgs []string)` | Same pattern for PRs, includes review status | | `GetTriageQueue()` | Unassigned issues + PRs awaiting review, sorted by priority labels | | `ListOrgsAndRepos()` | Enumerate all orgs the user belongs to, list repos per org | | `ApplyLabel()` | Thin wrapper for labeling issues | | `SubmitReview()` | Approve / request changes / comment on a PR | | `CreateIssue()` | Create an issue with labels in a specified repo | Design decisions: - Concurrent API calls across repos using `errgroup` with a semaphore (cap at 5-10) - In-memory cache with 30-second TTL using `sync.RWMutex` - Cache keys: `orgs-repos`, `issues-{org}`, `pulls-{org}` - Invalidate cache on write operations ### 1.5 — HTTP Handlers Using Go 1.22+ stdlib `http.ServeMux` (no external router needed): | Route | Method | Purpose | | -------------------------------------------- | ------ | --------------------------- | | `/` | GET | Dashboard / triage queue | | `/issues` | GET | All issues across orgs | | `/pulls` | GET | All PRs across orgs | | `/issues/{owner}/{repo}/{index}` | GET | Issue detail | | `/pulls/{owner}/{repo}/{index}` | GET | PR detail + diff stats | | `/issues` | POST | Create issue | | `/issues/{owner}/{repo}/{index}/labels` | POST | Assign labels | | `/pulls/{owner}/{repo}/{index}/review` | POST | Submit PR review | | `/settings` | GET | Token configuration page | | `/settings` | POST | Save token | | `/health` | GET | K8s liveness/readiness probe | Each handler checks the `HX-Request` header to decide whether to return a full page or an HTMX HTML fragment. --- ## Phase 2: Frontend (Mobile-First PWA) ### 2.1 — Layout - Fixed bottom navigation bar (4 tabs): Dashboard, Issues, PRs, Settings - Top bar with org/repo filter dropdown - Pull-to-refresh via HTMX polling or manual trigger - Dark mode via `prefers-color-scheme` media query - iPhone safe areas via `env(safe-area-inset-bottom)` ### 2.2 — Views **Dashboard / Triage (`/`)** - Card-based list of items needing attention - Each card: repo name, issue/PR title, labels (colored badges), age - Tap to expand inline via HTMX `hx-get` loading a detail fragment **Issues List (`/issues`)** - Filter bar: org, repo, state (open/closed), label - Infinite scroll via HTMX `hx-trigger="revealed"` on a sentinel element - Each row: colored dot for state, title, repo badge, label pills, assignee avatar **PR List (`/pulls`)** - Same layout as issues plus: review status icon, merge status indicator - Tap to view PR detail with diff stats **Issue Detail (`/issues/{owner}/{repo}/{index}`)** - Title, body (rendered markdown via Gitea's API markdown endpoint) - Comments thread - Action buttons: Add label (dropdown), Assign, Comment, Close - All actions via HTMX `hx-post` with `hx-swap="outerHTML"` **PR Detail (`/pulls/{owner}/{repo}/{index}`)** - Same as issue detail plus: diff stat summary, file list, review form - Review form: textarea + radio (approve/request changes/comment) + submit - Mergeable status indicator **Create Issue (`/issues/new`)** - Form: searchable repo selector, title, body textarea, label multi-select - Submit via HTMX, redirect to new issue detail ### 2.3 — HTMX Patterns ```html