19 KiB
19 KiB
Milestone 5: Developer Portal Frontend
Status: Decided Goal: Web interface for developer account and app management.
Decision
htmx + Go Templates for server-rendered UI from the single Go container:
Rendering: Go html/template
Interactivity: htmx (partial page updates)
Styling: Tailwind CSS (compiled at build time)
Charts: Chart.js (lightweight)
Icons: Heroicons or Lucide
Build: Embed static assets in Go binary
Rationale
- Single container - No separate Node.js server needed
- Server-rendered - All HTML generated by Go templates
- htmx for interactivity - AJAX without JavaScript framework
- Embedded assets - CSS/JS bundled into Go binary via
embed - Simple deployment - Just the Go binary, nothing else
- Low resource usage - Perfect for Synology NAS
Architecture
┌─────────────────────────────────────────┐
│ mosis-portal container │
│ ┌────────────────────────────────────┐ │
│ │ Go binary │ │
│ │ ├── Chi router │ │
│ │ ├── html/template rendering │ │
│ │ ├── Embedded static assets │ │
│ │ │ ├── tailwind.css (built) │ │
│ │ │ ├── htmx.min.js │ │
│ │ │ └── chart.min.js │ │
│ │ └── SQLite database │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
Overview
The developer portal is the primary interface for developers to manage their accounts, create apps, submit versions, and view analytics.
Pages Required
Public Pages
| Page | URL | Purpose |
|---|---|---|
| Landing | / |
Marketing, sign up CTA |
| Sign In | /login |
OAuth + password login |
| Sign Up | /register |
Create account |
| Docs | /docs/* |
Documentation (separate site?) |
Authenticated Pages
| Page | URL | Purpose |
|---|---|---|
| Dashboard | /dashboard |
App list, quick stats |
| App Details | /apps/:id |
Single app overview |
| App Settings | /apps/:id/settings |
Edit app metadata |
| App Versions | /apps/:id/versions |
Version history |
| Submit Version | /apps/:id/versions/new |
Upload new version |
| App Analytics | /apps/:id/analytics |
Telemetry dashboard |
| Create App | /apps/new |
New app wizard |
| API Keys | /settings/keys |
Manage API keys |
| Signing Keys | /settings/signing |
Manage signing keys |
| Profile | /settings/profile |
Account settings |
Wireframes
Dashboard
┌─────────────────────────────────────────────────────────────┐
│ [Logo] Dashboard Apps Docs Settings [Avatar ▼] │
├─────────────────────────────────────────────────────────────┤
│ │
│ Welcome back, Developer! │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Total Apps │ │ Downloads │ │ Active Users│ │
│ │ 12 │ │ 45,230 │ │ 1,234 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Your Apps [+ New App] │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [Icon] My Calculator v1.2.0 Published 1.2K ↓ │ │
│ │ [Icon] Notes App v2.0.1 Published 5.4K ↓ │ │
│ │ [Icon] Weather Widget v0.9.0 In Review --- │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
App Details
┌─────────────────────────────────────────────────────────────┐
│ ← Back to Apps │
├─────────────────────────────────────────────────────────────┤
│ │
│ [Icon] My Calculator │
│ com.developer.calculator │
│ ● Published │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Overview │ │ Versions │ │Analytics │ │ Settings │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Latest Version: 1.2.0 [Submit New Version] │
│ Published: Jan 15, 2024 │
│ Downloads: 1,234 │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Downloads over time │ │
│ │ [Chart: line graph] │ │
│ └────────────────────────────────────────┘ │
│ │
│ Recent Crashes [View All →] │
│ • attempt to index nil (v1.2.0) - 23 reports │
│ • memory limit exceeded (v1.1.0) - 5 reports │
│ │
└─────────────────────────────────────────────────────────────┘
Submit Version
┌─────────────────────────────────────────────────────────────┐
│ Submit New Version - My Calculator │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 2 of 3: Upload Package │
│ ○ Details ● Upload ○ Review │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────┐ │ │
│ │ │ .mosis │ Drop your package here │ │
│ │ │ 📦 │ or click to browse │ │
│ │ └─────────┘ │ │
│ │ │ │
│ │ Max size: 50 MB │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ✓ Package validated │
│ ✓ Signature verified │
│ ✓ Permissions: storage, network │
│ │
│ [Back] [Continue to Review] │
│ │
└─────────────────────────────────────────────────────────────┘
Tech Stack Options
Option A: Next.js 14
Framework: Next.js 14 (App Router)
UI: shadcn/ui + Tailwind CSS
State: React Query + Zustand
Forms: React Hook Form + Zod
Charts: Recharts or Tremor
Auth: NextAuth.js
| Pros | Cons |
|---|---|
| SSR for SEO landing page | Complexity |
| Great developer experience | React knowledge required |
| Full-stack capability | Heavier bundle |
| Large ecosystem |
Option B: SvelteKit
Framework: SvelteKit
UI: Skeleton UI or custom
State: Svelte stores
Forms: Superforms
Charts: Chart.js or LayerCake
Auth: Lucia
| Pros | Cons |
|---|---|
| Fast, small bundles | Smaller ecosystem |
| Simple state management | Fewer UI libraries |
| Great DX | Less hiring pool |
Option C: Astro + React Islands
Framework: Astro
UI: React components (islands)
State: Nanostores
Forms: React Hook Form
Charts: Recharts
Auth: Custom
| Pros | Cons |
|---|---|
| Ultra-fast static pages | More setup |
| Partial hydration | Newer approach |
| Use React where needed | Less documented patterns |
Option D: htmx + Go Templates
Framework: Go templates + htmx
UI: Tailwind CSS
State: Server-side
Forms: Native HTML
Charts: Chart.js
Auth: Server sessions
| Pros | Cons |
|---|---|
| Simple, fast | Limited interactivity |
| No JS framework | Less polished UX |
| Server-rendered | Complex UI harder |
UI Component Library
Option A: shadcn/ui
Base: Radix UI primitives
Styling: Tailwind CSS
Approach: Copy-paste components
| Pros | Cons |
|---|---|
| High quality | React only |
| Full control | Manual updates |
| Accessible |
Option B: Tailwind UI
Base: Headless UI
Styling: Tailwind CSS
Approach: Copy-paste templates
| Pros | Cons |
|---|---|
| Beautiful designs | Paid ($299) |
| Production-ready | Templates, not components |
Option C: Mantine
Base: Custom components
Styling: CSS-in-JS or CSS
Approach: npm package
| Pros | Cons |
|---|---|
| Complete solution | Opinionated |
| Many components | Larger bundle |
| Good docs |
Key Features (htmx Implementation)
File Upload with Progress
<!-- htmx file upload with progress indicator -->
<form hx-post="/api/apps/{{.App.ID}}/versions"
hx-encoding="multipart/form-data"
hx-target="#upload-result"
hx-indicator="#upload-spinner">
<div class="dropzone" id="dropzone">
<input type="file" name="package" accept=".mosis" required
class="hidden" id="file-input">
<label for="file-input" class="cursor-pointer">
<svg><!-- upload icon --></svg>
<p>Drop your .mosis package here or click to browse</p>
<p class="text-sm text-gray-500">Max size: 50 MB</p>
</label>
</div>
<div id="upload-spinner" class="htmx-indicator">
Uploading...
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
<div id="upload-result"></div>
Dynamic App List
<!-- Server renders this, htmx updates it -->
<div id="app-list"
hx-get="/partials/apps"
hx-trigger="load, newApp from:body"
hx-swap="innerHTML">
{{range .Apps}}
<div class="app-card">
<img src="{{.IconURL}}" alt="{{.Name}}">
<h3>{{.Name}}</h3>
<span class="badge {{.StatusClass}}">{{.Status}}</span>
<a href="/apps/{{.ID}}" hx-boost="true">View →</a>
</div>
{{end}}
</div>
Form with Validation
<!-- Create app form with server-side validation -->
<form hx-post="/apps" hx-target="#form-result" hx-swap="outerHTML">
<div class="form-group">
<label for="name">App Name</label>
<input type="text" name="name" id="name" required
hx-post="/api/validate/name"
hx-trigger="blur"
hx-target="next .error">
<span class="error"></span>
</div>
<div class="form-group">
<label for="package_id">Package ID</label>
<input type="text" name="package_id" id="package_id"
placeholder="com.yourname.appname" required
hx-post="/api/validate/package-id"
hx-trigger="blur"
hx-target="next .error">
<span class="error"></span>
</div>
<button type="submit" class="btn btn-primary">Create App</button>
</form>
Analytics Chart
<!-- Chart.js for analytics -->
<div class="chart-container">
<canvas id="downloads-chart"></canvas>
</div>
<script>
// Data injected from Go template
const chartData = {{.ChartDataJSON}};
new Chart(document.getElementById('downloads-chart'), {
type: 'line',
data: {
labels: chartData.labels,
datasets: [{
label: 'Downloads',
data: chartData.values,
borderColor: '#6366f1',
tension: 0.3
}]
}
});
</script>
Go Template Structure
Base Layout
// templates/layouts/base.html
{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}} - Mosis Developer Portal</title>
<link href="/static/tailwind.css" rel="stylesheet">
<script src="/static/htmx.min.js"></script>
</head>
<body class="bg-gray-50" hx-boost="true">
{{template "navbar" .}}
<main class="container mx-auto py-8">
{{template "content" .}}
</main>
</body>
</html>
{{end}}
Page Template
// templates/pages/dashboard.html
{{define "content"}}
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
<div class="grid grid-cols-3 gap-6 mb-8">
{{template "stat-card" dict "Label" "Total Apps" "Value" .Stats.TotalApps}}
{{template "stat-card" dict "Label" "Downloads" "Value" .Stats.Downloads}}
{{template "stat-card" dict "Label" "Active Users" "Value" .Stats.ActiveUsers}}
</div>
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">Your Apps</h2>
<a href="/apps/new" class="btn btn-primary">+ New App</a>
</div>
<div id="app-list" hx-get="/partials/apps" hx-trigger="load">
Loading...
</div>
{{end}}
Authentication Flow
Login Page (Go Template)
{{define "content"}}
<div class="min-h-screen flex items-center justify-center">
<div class="card w-96 bg-white shadow-lg rounded-lg p-6">
<h1 class="text-2xl font-bold text-center mb-6">Sign in to Mosis</h1>
<div class="space-y-4">
<a href="/auth/github" class="btn btn-github w-full flex items-center justify-center gap-2">
<svg><!-- GitHub icon --></svg>
Continue with GitHub
</a>
<a href="/auth/google" class="btn btn-google w-full flex items-center justify-center gap-2">
<svg><!-- Google icon --></svg>
Continue with Google
</a>
<div class="divider">or</div>
<form hx-post="/auth/login" hx-target="#login-error" class="space-y-4">
<input type="email" name="email" placeholder="Email"
class="input input-bordered w-full" required>
<input type="password" name="password" placeholder="Password"
class="input input-bordered w-full" required>
<div id="login-error" class="text-red-500 text-sm"></div>
<button type="submit" class="btn btn-primary w-full">Sign in</button>
</form>
</div>
</div>
</div>
{{end}}
Auth Middleware (Go)
// middleware/auth.go
func RequireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := sessionStore.Get(r, "session")
if err != nil || session.Values["developer_id"] == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
next.ServeHTTP(w, r)
})
}
// Usage in router
r.Group(func(r chi.Router) {
r.Use(RequireAuth)
r.Get("/dashboard", handlers.Dashboard)
r.Get("/apps", handlers.AppList)
r.Get("/apps/{id}", handlers.AppDetail)
})
Responsive Design
Breakpoints
| Size | Width | Target |
|---|---|---|
| sm | 640px | Mobile landscape |
| md | 768px | Tablet |
| lg | 1024px | Desktop |
| xl | 1280px | Large desktop |
Mobile Navigation
// Hamburger menu for mobile
<Sheet>
<SheetTrigger className="md:hidden">
<MenuIcon />
</SheetTrigger>
<SheetContent side="left">
<Navigation />
</SheetContent>
</Sheet>
Accessibility
Requirements
- Keyboard navigation
- Screen reader support
- Color contrast (WCAG AA)
- Focus indicators
- Alt text for images
- Form labels
- Error announcements
Testing
# Lighthouse audit
npx lighthouse http://localhost:3000 --view
# axe-core
npm install @axe-core/react
Deliverables
- Framework selection (htmx + Go Templates)
- UI component library selection (Tailwind CSS + Chart.js)
- Design system (colors, typography)
- Page wireframes (see above)
- Authentication flow (server sessions + OAuth)
- Dashboard implementation
- App management pages
- Version submission flow
- Settings pages
- Responsive design
- Accessibility audit
Open Questions
Dark mode support?→ Defer to post-MVP (Tailwind makes it easy to add later)Internationalization (i18n)?→ English only for MVPCustom domain for docs vs integrated?→ Integrated into same Go server at /docs- Email notifications UI? → Consider for v1.1