Files
nebula/MIGRATION.md

540 lines
12 KiB
Markdown

# 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
```go
// 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
```go
templ LoginStep1(caps DeviceCapabilities)
templ QRAuthStep()
templ RecoveryStep()
templ SuccessStep(title, redirectUrl string)
```
#### Register
```go
templ DeviceDetectionStep(caps DeviceCapabilities)
templ RegistrationMethodStep(method string)
templ RegistrationSuccessStep()
templ CapabilityItem(id, label string, supported, loading bool)
templ RecoveryKeyDisplay(publicKey, recoveryKey string)
```
#### Authorize
```go
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
```go
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
```go
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
```go
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
```go
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
```go
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
```html
<!-- 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
```html
<form hx-post="/api/auth/register"
hx-target="#step-content"
hx-indicator="#register-spinner">
```
### Auth Method Selection
```html
<div class="auth-method-card"
hx-post="/login/select-method"
hx-vals='{"method":"passkey"}'
hx-target="#btn-signin"
hx-swap="outerHTML">
```
### QR Code Polling
```html
<div id="qr-status"
hx-get="/api/auth/qr-status?session=xyz789"
hx-trigger="every 2s"
hx-target="this">
```
### Tab Switching
```html
<wa-tab-group hx-on:wa-tab-show="
htmx.ajax('GET', '/authorize/panel/' + event.detail.name, {target: '#panel-content'})
">
```
### Drawer Opening
```html
<wa-button hx-get="/drawers/receive"
hx-target="#drawer-container"
hx-swap="innerHTML">
Receive
</wa-button>
```
### Filter with URL State
```html
<wa-tab-group hx-on:wa-tab-show="
htmx.ajax('GET', '/nfts?filter=' + event.detail.name, {target: '#nft-grid', pushUrl: true})
">
```
### Permission Toggle
```html
<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
```go
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
```html
<div id="stats-grid"
hx-get="/api/wallet/stats"
hx-trigger="every 30s"
hx-swap="innerHTML">
```
---
## Layout Templates
### Base Layout
```go
// 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)
```go
// 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
```go
// 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:
```css
@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:
```css
:root {
--wa-color-primary: #17c2ff;
}
```
Layout patterns:
```css
.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
```html
<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>
```