Files
nebula/MIGRATION.md

12 KiB

Templ Migration Guide

Convert HTML prototypes to Go templ templates with HTMX for server-driven UI.

Architecture

Browser                           Go Server
-------                           ---------
[Web Awesome UI]  <-- HTMX -->   [Templ Renderer]
     |                                  |
     | hx-post="/api/sign"              |
     | hx-target="#result"              |
     +--------------------------------->+
                                        |
                               [Handler Logic]
                                        |
     +<---------------------------------+
     | HTML Fragment Response
     |
[DOM Update via HTMX]

Directory Structure

project/
├── views/
│   ├── welcome.templ
│   ├── login.templ
│   ├── register.templ
│   ├── authorize.templ
│   ├── accounts.templ
│   ├── nfts.templ
│   └── service.templ
├── layouts/
│   ├── base.templ
│   ├── centered.templ      # Auth pages
│   └── dashboard.templ     # Authenticated pages
├── components/
│   ├── stepper.templ
│   ├── auth_method_card.templ
│   ├── permission_item.templ
│   ├── token_row.templ
│   ├── nft_card.templ
│   └── session_item.templ
└── handlers/
    └── routes.go

Page Overview

Auth Pages (Popup/Webview Optimized)

Page File Viewport Description
Welcome welcome.templ 360-480px 3-step onboarding stepper
Login login.templ 360-480px WebAuthn sign-in with passkey/security key/QR
Register register.templ 360-480px Device detection + registration wizard
Authorize authorize.templ 360-480px OAuth consent for connect/sign/transaction

Dashboard Pages

Page File Description
Accounts accounts.templ Token balances, transactions, drawers
NFTs nfts.templ Gallery with collections, filters, actions
Service service.templ Connected service detail with permissions

Component Extraction

Shared Components

// components/stepper.templ
templ ProgressDots(current int, total int)
templ OnboardingStepper(current int, steps []string)

// components/auth_method_card.templ
templ AuthMethodCard(method, icon, title, desc string, disabled bool)

// components/permission_item.templ
templ PermissionItem(icon, iconClass, title, desc string)

// components/qr_section.templ
templ QRCodeSection(value, label, fallbackText string)

Page-Specific Components

Login

templ LoginStep1(caps DeviceCapabilities)
templ QRAuthStep()
templ RecoveryStep()
templ SuccessStep(title, redirectUrl string)

Register

templ DeviceDetectionStep(caps DeviceCapabilities)
templ RegistrationMethodStep(method string)
templ RegistrationSuccessStep()
templ CapabilityItem(id, label string, supported, loading bool)
templ RecoveryKeyDisplay(publicKey, recoveryKey string)

Authorize

templ AppIdentityHeader(app AppInfo)
templ WalletSelector(wallets []Wallet, selected string)
templ PermissionList(permissions []Permission)
templ MessagePreview(message, signType string)
templ TransactionPreview(tx Transaction)
templ SwapPreview(from, to TokenAmount)
templ GasEstimate(network, gas, maxFee string)

Accounts

templ StatsGrid(stats WalletStats)
templ TokenBalancesCard(tokens []Token)
templ TokenRow(token Token)
templ RecentTransactionsCard(txs []Transaction)
templ TransactionRow(tx Transaction)
templ ConnectedAccountsCard(accounts []Account)
templ ReceiveDrawer(token string)
templ SendDrawer(tokens []Token)
templ SwapDrawer(tokens []Token)
templ CreateAccountDialog(step int, network string)

NFTs

templ NFTsStatsGrid(stats NFTStats)
templ CollectionsCard(collections []Collection)
templ CollectionCard(collection Collection)
templ NFTsGalleryCard(nfts []NFTItem, filter NFTFilter)
templ NFTFilterBar(filter NFTFilter)
templ NFTGrid(nfts []NFTItem)
templ NFTCard(nft NFTItem)
templ NFTBadge(badgeType string)

Service

templ ServiceHero(info ServiceInfo, status ServiceStatus)
templ UsageStatsGrid(stats UsageStats)
templ PermissionsCard(permissions []Permission)
templ PermissionToggle(scope string, enabled bool, desc string)
templ ServiceInfoCard(oauth OAuthInfo)
templ SessionLogCard(sessions []SessionEvent)
templ SessionLogItem(event SessionEvent)
templ DataAccessCard(access DataAccess)

State Types

Auth Pages

type DeviceCapabilities struct {
    PlatformAuth    bool  // Face ID, Touch ID, Windows Hello
    CrossPlatform   bool  // Security keys (YubiKey)
    ConditionalUI   bool  // Passkey autofill support
}

type LoginState struct {
    Step           string
    SelectedMethod string // "passkey", "security-key", "qr-code"
    Username       string
    Error          string
}

type RegisterState struct {
    Step           int
    SelectedMethod string
    Username       string
    DisplayName    string
    PublicKey      string
    RecoveryKey    string
    Error          string
}

type AuthRequest struct {
    Type        string     // "connect", "sign", "transaction"
    App         AppInfo
    Scopes      []string
    Message     string
    Transaction *TxDetails
    State       string
    Nonce       string
}

type AppInfo struct {
    Name     string
    Domain   string
    LogoURL  string
    Verified bool
}

Dashboard Pages

type WalletData struct {
    Stats        WalletStats
    Tokens       []Token
    Transactions []Transaction
    Accounts     []Account
}

type WalletStats struct {
    TotalBalance  float64
    Change24h     float64
    ChangePercent float64
    AssetCount    int
    PendingCount  int
}

type Token struct {
    Symbol    string
    Name      string
    Balance   float64
    ValueUSD  float64
    IconColor string
}

type NFTsData struct {
    Stats       NFTStats
    Collections []Collection
    NFTs        []NFTItem
    Filter      NFTFilter
}

type NFTFilter struct {
    Tab        string // "all", "listed", "unlisted"
    Collection string
    Sort       string // "recent", "value-high", "value-low", "name"
    View       string // "grid", "list"
}

type ServiceData struct {
    Info        ServiceInfo
    Status      ServiceStatus
    Stats       UsageStats
    Permissions []Permission
    OAuth       OAuthInfo
    Sessions    []SessionEvent
    DataAccess  DataAccess
}

HTMX Patterns

Step Navigation

<!-- Before: JavaScript -->
<wa-button onclick="goToStep(2)">Next</wa-button>

<!-- After: HTMX -->
<wa-button hx-get="/welcome/step/2" hx-target="#step-content">Next</wa-button>

Form Submission

<form hx-post="/api/auth/register"
      hx-target="#step-content"
      hx-indicator="#register-spinner">

Auth Method Selection

<div class="auth-method-card"
     hx-post="/login/select-method"
     hx-vals='{"method":"passkey"}'
     hx-target="#btn-signin"
     hx-swap="outerHTML">

QR Code Polling

<div id="qr-status"
     hx-get="/api/auth/qr-status?session=xyz789"
     hx-trigger="every 2s"
     hx-target="this">

Tab Switching

<wa-tab-group hx-on:wa-tab-show="
  htmx.ajax('GET', '/authorize/panel/' + event.detail.name, {target: '#panel-content'})
">

Drawer Opening

<wa-button hx-get="/drawers/receive" 
           hx-target="#drawer-container" 
           hx-swap="innerHTML">
  Receive
</wa-button>

Filter with URL State

<wa-tab-group hx-on:wa-tab-show="
  htmx.ajax('GET', '/nfts?filter=' + event.detail.name, {target: '#nft-grid', pushUrl: true})
">

Permission Toggle

<wa-switch hx-post="/api/connections/{id}/scopes"
           hx-vals='{"scope": "wallet:sign", "enabled": !this.checked}'
           hx-target="#permission-status"
           hx-swap="innerHTML">

Out-of-Band Updates

templ TransactionResult(tx Transaction) {
    <div id="tx-result">
        <wa-callout variant="success">
            Transaction submitted: { tx.Hash }
        </wa-callout>
    </div>

    <div id="balance-display" hx-swap-oob="true">
        @BalanceCard(tx.NewBalance)
    </div>

    <wa-drawer id="send-drawer" hx-swap-oob="true"></wa-drawer>
}

Stats Auto-Refresh

<div id="stats-grid" 
     hx-get="/api/wallet/stats" 
     hx-trigger="every 30s" 
     hx-swap="innerHTML">

Layout Templates

Base Layout

// layouts/base.templ
templ Base(title string) {
    <!DOCTYPE html>
    <html lang="en" class="wa-cloak">
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <title>{ title } - Sonr Motr Wallet</title>
        <script src="https://cdn.sonr.org/wa/autoloader.js"></script>
    </head>
    <body>
        { children... }
    </body>
    </html>
}

Centered Card (Auth Pages)

// layouts/centered.templ
templ CenteredCard(title string) {
    @Base(title) {
        <wa-page>
            <main class="main-centered">
                <wa-card>
                    { children... }
                </wa-card>
            </main>
        </wa-page>
    }
}

Dashboard Layout

// layouts/dashboard.templ
templ DashboardLayout(activePage string) {
    @Base("Dashboard") {
        <wa-page>
            <div class="dashboard-layout">
                @Sidebar(activePage)
                <main class="main-content">
                    { children... }
                </main>
            </div>
        </wa-page>
    }
}

Viewport Constraints

Auth pages optimized for popup windows and webviews:

Size Dimensions Use Case
Small 360 x 540 Mobile popup
Medium 420 x 600 Default popup
Large 480 x 680 Desktop popup
Mobile 375 x 667 iPhone viewport

CSS breakpoints:

@media (max-width: 400px) {
    .main-centered { padding: var(--wa-space-m); }
    .main-centered wa-card { max-width: 100%; }
}

@media (max-height: 600px) {
    .hero-icon { width: 56px; height: 56px; }
}

Migration Checklist

  1. Layouts first

    • base.templ - HTML head, scripts, body wrapper
    • centered.templ - Auth pages card layout
    • dashboard.templ - Sidebar + main content
  2. Shared components

    • Stepper/Progress dots
    • Auth method cards
    • Permission items
    • QR code sections
    • Token rows
    • NFT cards
  3. Auth pages

    • welcome.templ - Onboarding flow
    • login.templ - Sign-in with WebAuthn
    • register.templ - Registration wizard
    • authorize.templ - OAuth consent
  4. Dashboard pages

    • accounts.templ - Main wallet view
    • nfts.templ - NFT gallery
    • service.templ - Connected service detail
  5. Wire HTMX

    • Replace all onclick handlers with hx-* attributes
    • Add polling for real-time updates
    • Implement drawer/dialog triggers
  6. Connect handlers

    • Route registration
    • API endpoints for HTMX partials
    • WebAuthn integration points

CSS Variables

Primary brand color:

:root {
    --wa-color-primary: #17c2ff;
}

Layout patterns:

.main-centered {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100%;
    padding: var(--wa-space-l);
    box-sizing: border-box;
}

.main-centered wa-card {
    width: 100%;
    max-width: 45ch;  /* Auth pages */
    max-width: 48ch;  /* Wider content */
}

Web Awesome Components Reference

Layout Classes

  • wa-stack - Vertical flex with wa-gap-{size}
  • wa-cluster - Horizontal flex with wa-gap-{size}
  • wa-grid - CSS grid with --min-column-size
  • wa-flank - Space-between horizontal layout

Gap Sizes

3xs, 2xs, xs, s, m, l, xl, 2xl, 3xl

Typography

  • Headings: wa-heading-xs through wa-heading-2xl
  • Captions: wa-caption-xs, wa-caption-s, wa-caption-m
  • Labels: wa-label-s, wa-label-m

Common Components

<wa-button variant="brand">Primary</wa-button>
<wa-button variant="neutral" appearance="outlined">Secondary</wa-button>
<wa-input label="Field" placeholder="Enter value"></wa-input>
<wa-card><div slot="header">Title</div>Content</wa-card>
<wa-drawer label="Title" placement="end"></wa-drawer>
<wa-dialog label="Title"></wa-dialog>
<wa-callout variant="warning"><wa-icon slot="icon" name="alert"></wa-icon>Message</wa-callout>