feat: implement mobile-first HTMX templates and CSS

Build all frontend views using Go html/template with HTMX interactions
and mobile-first CSS with dark mode and iPhone safe areas.

Templates:
- layout.html: base layout with fixed bottom nav (Dashboard, Issues, PRs, Settings)
- dashboard.html: card-based triage view with priority indicators
- issues.html: issue list with filter bar (org, state) and infinite scroll
- pulls.html: PR list with diff stats and merge status
- issue_detail.html: issue detail with comments, label assignment
- pull_detail.html: PR detail with diff stats and review form
- create_issue.html: issue creation form with searchable repo selector

CSS (static/style.css):
- Mobile-first dark mode with CSS variables
- iPhone safe areas via env(safe-area-inset-bottom)
- Responsive: 2-column grid at 640px+ breakpoint
- Light mode support via prefers-color-scheme
- Card, label, badge, form, button component styles
- HTMX loading indicator and spinner animation
- Reduced motion support

HTMX patterns:
- hx-get with hx-target for SPA-like navigation
- hx-trigger="revealed" for infinite scroll
- hx-post with hx-swap for inline form actions
- Filter bar with hx-trigger="change" and hx-include

Closes leeworks-agents/gitea-mobile#5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
agent-company
2026-03-26 04:15:06 +00:00
parent 712dc5632c
commit 81496c775e
10 changed files with 763 additions and 40 deletions
+44
View File
@@ -0,0 +1,44 @@
{{define "content"}}
<h1>Issues</h1>
<div class="filter-bar">
<select name="org" hx-get="/issues" hx-trigger="change" hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true" hx-include="[name='state']">
<option value="">All orgs</option>
{{range .Orgs}}
<option value="{{.}}" {{if eq . $.SelectedOrg}}selected{{end}}>{{.}}</option>
{{end}}
</select>
<select name="state" hx-get="/issues" hx-trigger="change" hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true" hx-include="[name='org']">
<option value="open" {{if eq .SelectedState "open"}}selected{{end}}>Open</option>
<option value="closed" {{if eq .SelectedState "closed"}}selected{{end}}>Closed</option>
</select>
</div>
{{if .Error}}
<p class="empty">{{.Error}}</p>
{{else if not .Issues}}
<p class="empty">No issues found.</p>
{{else}}
<div id="issue-list">
{{range .Issues}}
<div class="card" hx-get="/issues/{{.RepoOwner}}/{{.RepoName}}/{{.Number}}" hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true">
<div class="card-title">{{.Title}}</div>
<div class="card-meta">
<span>{{.RepoOwner}}/{{.RepoName}} #{{.Number}}</span>
{{range .Labels}}
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
{{if .Assignee}}
<span>{{.Assignee.Login}}</span>
{{end}}
</div>
</div>
{{end}}
{{if .HasMore}}
<div class="scroll-sentinel" hx-get="/issues?page={{.NextPage}}&org={{.SelectedOrg}}&state={{.SelectedState}}" hx-trigger="revealed" hx-swap="outerHTML" hx-target="this">
<div class="spinner htmx-indicator"></div>
</div>
{{end}}
</div>
{{end}}
{{end}}