Files
MosisService/DEV_PORTAL_M05_FRONTEND.md

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

  1. Single container - No separate Node.js server needed
  2. Server-rendered - All HTML generated by Go templates
  3. htmx for interactivity - AJAX without JavaScript framework
  4. Embedded assets - CSS/JS bundled into Go binary via embed
  5. Simple deployment - Just the Go binary, nothing else
  6. 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

  1. Dark mode support? → Defer to post-MVP (Tailwind makes it easy to add later)
  2. Internationalization (i18n)? → English only for MVP
  3. Custom domain for docs vs integrated? → Integrated into same Go server at /docs
  4. Email notifications UI? → Consider for v1.1

References