docs: migrate all planning docs from React to GoFiber + HTMX

- New ARCHITECTURE_HTMX.md — comprehensive HTMX architecture doc
- ARCHITECTURE.md — updated to point to HTMX version
- UI_COMPONENTS.md — replaced React tree with HTMX partials
- SPRINT_PLAN.md — all sprint tasks updated for HTMX
- BUILD_SYSTEM.md — simplified (no Vite, no npm)
- TIMELINE.md — 3.5 week HTMX timeline
- PROGRESS.md — updated status + HTMX
- AGENTS.md — updated checkpoint with HTMX decision
This commit is contained in:
swanadiva
2026-07-07 12:15:19 +07:00
parent c215d68c81
commit a0c8483c0e
8 changed files with 1442 additions and 2500 deletions
+218 -495
View File
@@ -1,529 +1,252 @@
# Hostkeeper V2 — Sprint Plan (File-by-File Task Breakdown)
# Hostkeeper V2 — Sprint Plan (HTMX)
> **Status**: V2 Planning Complete
> **Last Updated**: 2026-06-29
> **Framework**: GoFiber v2 (backend), React/TypeScript (frontend), Electron (desktop)
> **Status**: Migrasi React → HTMX
> **Last Updated**: 2026-07-07
> **Framework**: GoFiber v2 + HTMX + Alpine.js + TailwindCSS v4
---
## Sprint 0 — Project Scaffolding
## Sprint 0 — Setup GoFiber + HTMX
**Goal**: Empty Electron app with GoFiber backend serving "Hello World".
**Goal**: GoFiber server running, layout HTML renders, TailwindCSS works.
### Day 0: Setup (1 day)
### Tasks
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 0.1 | Go module for backend | `app/backend/go.mod` | `go mod init` runs clean |
| 0.2 | Install GoFiber + deps | `app/backend/go.mod` | `go get github.com/gofiber/fiber/v2` |
| 0.3 | Minimal fiber server | `app/backend/main.go` | Server starts on random port |
| 0.4 | Health endpoint | `app/backend/main.go` | `curl localhost:PORT/api/health` returns JSON |
| 0.5 | Middleware setup | `app/backend/middleware/middleware.go` | CORS, logger, recover, compress registered |
| 0.6 | Vite + React scaffold | `app/frontend/package.json`, `app/frontend/src/App.tsx` | `npm run dev` shows React app |
| 0.7 | Electron shell | `app/electron/main.ts`, `app/electron/preload.ts`, `app/electron/package.json` | `npx electron .` opens window |
| 0.8 | Electron spawns Go | `app/electron/main.ts` | Go process starts, API responds |
| 0.9 | Electron loads frontend | `app/electron/main.ts` | Window shows React app |
| 0.10 | Bridge placeholder dirs | `app/backend/bridge/`, `app/backend/ws/` | Directories exist |
| 0.11 | Build script | `scripts/build.sh` | Script runs without error |
| 0.12 | End-to-end verify | — | Electron window shows "Hostkeeper V2" title |
| # | Task | Files | Verify |
|---|------|-------|--------|
| 0.1 | Go module init | `app/backend/go.mod` | `go mod init` clean |
| 0.2 | Install GoFiber + deps | `go.mod` | `go get github.com/gofiber/fiber/v2` |
| 0.3 | Minimal Fiber server | `app/backend/main.go` | Server starts on :1947 |
| 0.4 | Static files serving | `main.go` | `/static/` serves files |
| 0.5 | HTML template engine | `main.go` + `views/layout.html` | Template renders |
| 0.6 | Download HTMX + Alpine | `static/js/htmx.min.js`, `static/js/alpine.min.js` | JS files exist |
| 0.7 | Setup TailwindCSS v4 | `static/css/output.css` | Tailwind classes work |
| 0.8 | Custom CSS (Lumina) | `static/css/custom.css` | dot-grid, glassmorphism, keyframes |
| 0.9 | Layout template | `views/layout.html` | Sidebar + header + main |
| 0.10 | Nav template | `views/partials/nav.html` | Nav items with active state |
| 0.11 | Index + routing | `views/index.html` + `handler/dashboard.go` | Dashboard renders |
| 0.12 | Health endpoint | `handler/health.go` | `curl :1947/api/health` |
#### Detailed Files
### Files to Create (Sprint 0)
**`app/backend/go.mod`**:
```go
module git.tukangketik.id/swanadiva/hostkeeper/app/backend
go 1.26
require (
github.com/gofiber/fiber/v2 v2.52.5
github.com/gofiber/contrib/websocket v1.3.3
github.com/fasthttp/websocket v1.5.12
)
```
**`app/backend/main.go`**:
```go
package main
import (
"fmt"
"log"
"math/rand"
"os"
"os/signal"
"syscall"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func main() {
port := rand.Intn(60000) + 10000 // random port 10000-70000
app := fiber.New(fiber.Config{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
})
// Middleware
app.Use(recover.New())
app.Use(logger.New())
app.Use(compress.New(compress.Config{
Level: compress.LevelBestSpeed,
}))
app.Use(cors.New(cors.Config{
AllowOrigins: "*",
AllowMethods: "GET,POST,PUT,PATCH,DELETE,OPTIONS",
AllowHeaders: "Origin,Content-Type,Authorization",
}))
// Health check
app.Get("/api/health", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"status": "ok",
"version": "2.0.0",
})
})
// Start server
go func() {
fmt.Printf("HOSTKEEPER_PORT=%d\n", port)
log.Fatal(app.Listen(fmt.Sprintf(":%d", port)))
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
app.Shutdown()
}
```
**`app/electron/main.ts`**:
```typescript
import { app, BrowserWindow } from 'electron';
import { spawn, ChildProcess } from 'child_process';
import * as path from 'path';
let mainWindow: BrowserWindow;
let goProcess: ChildProcess;
function startGoBackend() {
const goBinary = path.join(__dirname, '../../backend/hostkeeper-server');
goProcess = spawn(goBinary, [], { stdio: 'pipe' });
goProcess.stdout?.on('data', (data) => {
const output = data.toString();
const match = output.match(/HOSTKEEPER_PORT=(\d+)/);
if (match) {
const port = match[1];
mainWindow.loadURL(`http://localhost:${port}`);
}
});
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
title: 'Hostkeeper V2',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
// Load from Go backend (or dev server in development)
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173');
} else {
startGoBackend();
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
goProcess?.kill();
app.quit();
});
```
**`scripts/build.sh`**:
```bash
#!/bin/bash
set -e
echo "=== Building Hostkeeper V2 ==="
# Build Go backend
echo "Building Go backend..."
cd app/backend
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o ../electron/hostkeeper-server .
cd ../..
# Build frontend
echo "Building frontend..."
cd app/frontend
npm run build
cd ../..
# Package Electron
echo "Packaging Electron..."
cd app/electron
npx electron-builder --mac --arm64
cd ../..
echo "=== Build complete ==="
app/backend/
├── main.go
├── go.mod
├── go.sum
├── internal/
│ ├── handler/
│ │ ├── dashboard.go
│ │ └── health.go
│ └── middleware/
│ └── viewdata.go
├── static/
│ ├── css/
│ │ ├── output.css
│ │ └── custom.css
│ └── js/
│ ├── htmx.min.js
│ └── alpine.min.js
└── views/
├── layout.html
├── index.html
└── partials/
└── nav.html
```
---
## Sprint 1 — Foundation (Day 1-5)
## Sprint 1 — Dashboard + Host List
**Goal**: Full CRUD for hosts/keys/snippets + web UI with host list.
**Goal**: Dashboard halaman dengan host list, search, filter, add host modal.
### Day 1-2: Data API Layer
### Tasks
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 1.1 | Host CRUD handlers | `app/backend/bridge/hosts.go` | All 6 endpoints work via curl |
| 1.2 | Key CRUD handlers | `app/backend/bridge/keys.go` | All 4 endpoints work via curl |
| 1.3 | Snippet CRUD handlers | `app/backend/bridge/snippets.go` | All 4 endpoints work via curl |
| 1.4 | Storage integration | Import `pkg/storage` | Read/write actual JSON files |
| 1.5 | Error responses | `app/backend/bridge/errors.go` | Consistent error format |
| 1.6 | Route registration | `app/backend/main.go` | All routes registered |
| # | Task | Files |
|---|------|-------|
| 1.1 | Model Host | `internal/model/host.go` |
| 1.2 | Dashboard handler | `internal/handler/dashboard.go` |
| 1.3 | Dashboard template | `views/partials/dashboard.html` |
| 1.4 | Host list partial | `views/partials/host_list.html` |
| 1.5 | Host card partial | `views/partials/host_card.html` |
| 1.6 | Search HTMX | `hx-get="/hosts?q=..." hx-trigger="keyup delay:200ms"` |
| 1.7 | Filter status | `hx-get="/hosts?filter=active"` + `<select>` |
| 1.8 | View toggle (grid/list) | Alpine `x-data` |
| 1.9 | Add host modal | `views/partials/host_modal.html` + Alpine |
| 1.10 | POST /hosts | Handler create host |
| 1.11 | Background transfers | `views/partials/dashboard.html` |
### Files to Create (Sprint 1)
**Example handler** (`bridge/hosts.go`):
```go
func ListHosts(c *fiber.Ctx) error {
hosts, err := storage.ListHosts(c.Context())
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"items": hosts, "total": len(hosts)})
}
```
### Day 3: Groups + Config API
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 1.7 | Group CRUD handlers | `app/backend/bridge/groups.go` | Tree structure returned |
| 1.8 | Config handlers | `app/backend/bridge/config.go` | Get/set config works |
| 1.9 | Vault handlers | `app/backend/bridge/vault.go` | Lock/unlock/unlock status |
| 1.10 | Group tree builder | `app/backend/bridge/groups.go` | Recursive tree populated |
### Day 4: Web UI Scaffold
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 1.11 | React Router | `app/frontend/src/router.tsx` | Navigate between screens |
| 1.12 | Layout component | `app/frontend/src/components/Layout.tsx` | Sidebar + tab bar + content |
| 1.13 | Sidebar component | `app/frontend/src/components/Sidebar.tsx` | Group tree + search |
| 1.14 | Theme system | `app/frontend/src/styles/themes.css` | CSS variables for dark/light |
| 1.15 | API client | `app/frontend/src/api/client.ts` | Fetch wrapper with errors |
| 1.16 | Zustand stores | `app/frontend/src/stores/*.ts` | Host, tab, vault stores |
| 1.17 | Keyboard shortcuts | `app/frontend/src/hooks/useKeyboard.ts` | Cmd+N, Cmd+K work |
### Day 5: Host List Screen
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 1.18 | HostList screen | `app/frontend/src/screens/HostListScreen.tsx` | Hosts displayed |
| 1.19 | HostCard component | `app/frontend/src/components/HostCard.tsx` | Host info shown |
| 1.20 | GroupTree component | `app/frontend/src/components/GroupTree.tsx` | Collapsible tree |
| 1.21 | SearchBar component | `app/frontend/src/components/SearchBar.tsx` | Filter works |
| 1.22 | EmptyState component | `app/frontend/src/components/EmptyState.tsx` | Shown when no hosts |
| 1.23 | Context menu | `app/frontend/src/components/ContextMenu.tsx` | Right-click menu |
---
## Sprint 2 — Core Features (Day 6-10)
**Goal**: Host form + SSH terminal (the critical feature).
### Day 6-7: Host Form + CRUD UI
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 2.1 | HostForm screen | `app/frontend/src/screens/HostFormScreen.tsx` | Add/edit host works |
| 2.2 | AuthSection component | `app/frontend/src/components/AuthSection.tsx` | Password/key selector |
| 2.3 | TagsInput component | `app/frontend/src/components/TagsInput.tsx` | Add/remove tags |
| 2.4 | GroupPicker component | `app/frontend/src/components/GroupPicker.tsx` | Tree picker |
| 2.5 | Form validation | `app/frontend/src/utils/validation.ts` | All field validation |
| 2.6 | Toast notifications | `app/frontend/src/components/Toast.tsx` | Success/error toasts |
### Day 8-10: SSH Terminal (MOST CRITICAL)
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 2.7 | WebSocket handler | `app/backend/ws/terminal.go` | WS connection accepted |
| 2.8 | SSH session management | `app/backend/ws/session.go` | Connect, stream, disconnect |
| 2.9 | Terminal resize | `app/backend/ws/terminal.go` | Resize messages handled |
| 2.10 | XTermWrapper component | `app/frontend/src/components/XTermWrapper.tsx` | Terminal renders |
| 2.11 | TerminalScreen | `app/frontend/src/screens/TerminalScreen.tsx` | Full terminal view |
| 2.12 | TabBar component | `app/frontend/src/components/TabBar.tsx` | Multi-tab support |
| 2.13 | Tab management | `app/frontend/src/stores/tabStore.ts` | Add/close/switch tabs |
| 2.14 | SnippetPanel | `app/frontend/src/components/SnippetPanel.tsx` | Slide-in snippet list |
| 2.15 | Connection overlay | `app/frontend/src/components/ConnectionOverlay.tsx` | Status + reconnect |
| 2.16 | Terminal toolbar | `app/frontend/src/components/TerminalToolbar.tsx` | Host info + actions |
**WebSocket handler** (`ws/terminal.go`):
```go
package ws
import (
"encoding/json"
"log"
"time"
"github.com/gofiber/contrib/websocket"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/ssh"
)
func HandleTerminal(c *websocket.Conn) {
hostID := c.Query("host_id")
cols := c.QueryInt("cols", 80)
rows := c.QueryInt("rows", 24)
// Get host from storage
host, err := storage.GetHost(c.Context(), hostID)
if err != nil {
c.WriteJSON(fiber.Map{"type": "error", "message": "Host not found"})
return
}
// Create SSH client
client := ssh.NewClient(host, 30*time.Second)
if err := client.Connect(c.Context()); err != nil {
c.WriteJSON(fiber.Map{"type": "error", "message": err.Error()})
return
}
defer client.Close()
// Create session + PTY
session, err := client.GetClient().NewSession()
if err != nil {
c.WriteJSON(fiber.Map{"type": "error", "message": "Session failed"})
return
}
defer session.Close()
modes := ssh.TerminalModes{ssh.ECHO: 1}
session.RequestPty("xterm-256color", rows, cols, modes)
stdin, _ := session.StdinPipe()
stdout, _ := session.StdoutPipe()
if err := session.Shell(); err != nil {
c.WriteJSON(fiber.Map{"type": "error", "message": "Shell failed"})
return
}
c.WriteJSON(fiber.Map{"type": "status", "state": "connected"})
// Goroutine: SSH stdout -> WebSocket
go func() {
buf := make([]byte, 4096)
for {
n, err := stdout.Read(buf)
if err != nil {
break
}
if err := c.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil {
break
}
}
}()
// Main loop: WebSocket -> SSH stdin
for {
_, msg, err := c.ReadMessage()
if err != nil {
break
}
// Check for resize message
if len(msg) > 0 && msg[0] == '{' {
var ctrl struct {
Type string `json:"type"`
Cols int `json:"cols"`
Rows int `json:"rows"`
}
if json.Unmarshal(msg, &ctrl) == nil && ctrl.Type == "resize" {
session.WindowChange(ctrl.Rows, ctrl.Cols)
continue
}
}
// Regular input
stdin.Write(msg)
}
}
internal/model/host.go
views/partials/dashboard.html
views/partials/host_list.html
views/partials/host_card.html
views/partials/host_modal.html
views/partials/toast.html
static/js/clipboard.js ← mulai buat
```
---
## Sprint 3 — SFTP + Polish (Day 11-15)
## Sprint 2 — Snippets + Keychain
**Goal**: SFTP browser + security + settings.
**Goal**: Snippet library + credential keychain dengan CRUD.
### Day 11-12: SFTP Browser
### Tasks
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 3.1 | SFTP REST handlers | `app/backend/bridge/sftp.go` | All 7 endpoints work |
| 3.2 | SFTPScreen | `app/frontend/src/screens/SFTPScreen.tsx` | Dual pane visible |
| 3.3 | FileList component | `app/frontend/src/components/FileList.tsx` | Files listed with columns |
| 3.4 | LocalPane component | `app/frontend/src/components/LocalPane.tsx` | Local files |
| 3.5 | RemotePane component | `app/frontend/src/components/RemotePane.tsx` | Remote files |
| 3.6 | TransferQueue | `app/frontend/src/components/TransferQueue.tsx` | Transfer progress |
| 3.7 | SFTPToolbar | `app/frontend/src/components/SFTPToolbar.tsx` | Upload/download buttons |
| 3.8 | Context menu | `app/frontend/src/components/SFTPContextMenu.tsx` | File operations |
| # | Task | Files |
|---|------|-------|
| 2.1 | Model Snippet | `internal/model/snippet.go` |
| 2.2 | Model Keychain | `internal/model/keychain.go` |
| 2.3 | Snippets handler | `internal/handler/snippets.go` |
| 2.4 | Keychain handler | `internal/handler/keychain.go` |
| 2.5 | Snippets template | `views/partials/snippets.html` |
| 2.6 | Snippet grid partial | `views/partials/snippet_grid.html` |
| 2.7 | Snippet modal | `views/partials/snippet_modal.html` |
| 2.8 | Keychain template | `views/partials/keychain.html` |
| 2.9 | Key grid partial | `views/partials/key_grid.html` |
| 2.10 | Key modal | `views/partials/key_modal.html` |
| 2.11 | Clipboard JS | `static/js/clipboard.js` |
| 2.12 | Passphrase reveal + strength | `static/js/utils.js` |
### Day 13: Security + Vault
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 3.9 | VaultScreen | `app/frontend/src/screens/VaultScreen.tsx` | Password prompt |
| 3.10 | KnownHostDialog | `app/frontend/src/components/KnownHostDialog.tsx` | Fingerprint verify |
| 3.11 | PasswordPrompt | `app/frontend/src/components/PasswordPrompt.tsx` | Reusable prompt |
| 3.12 | Auto-lock logic | `app/frontend/src/hooks/useAutoLock.ts` | Locks after inactivity |
### Day 14: Settings Screen
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 3.13 | SettingsScreen | `app/frontend/src/screens/SettingsScreen.tsx` | Tabbed settings |
| 3.14 | AppearanceSettings | `app/frontend/src/components/settings/Appearance.tsx` | Theme, font |
| 3.15 | TerminalSettings | `app/frontend/src/components/settings/Terminal.tsx` | Scrollback, cursor |
| 3.16 | ConnectionSettings | `app/frontend/src/components/settings/Connection.tsx` | Timeout, keepalive |
| 3.17 | VaultSettings | `app/frontend/src/components/settings/Vault.tsx` | Password, auto-lock |
| 3.18 | GeneralSettings | `app/frontend/src/components/settings/General.tsx` | About, data dir |
### Day 15: Mobile Setup
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 3.19 | gomobile build script | `mobile/gomobile/build.sh` | Compiles to .aar/.xcframework |
| 3.20 | Android WebView app | `mobile/android/` | App runs on emulator |
| 3.21 | iOS WebView app | `mobile/ios/` | App runs on simulator |
| 3.22 | Mobile responsive CSS | `app/frontend/src/styles/mobile.css` | Touch-friendly |
---
## Sprint 4 — Advanced Features (Day 16-20)
**Goal**: Port forwarding + import/export + jump hosts.
### Day 16-17: Port Forwarding
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 4.1 | Forward model + storage | `app/backend/bridge/portforward.go` | CRUD works |
| 4.2 | Local forwarding | `app/backend/ws/portforward.go` | Local tunnel works |
| 4.3 | Remote forwarding | `app/backend/ws/portforward.go` | Remote tunnel works |
| 4.4 | Dynamic forwarding | `app/backend/ws/portforward.go` | SOCKS5 proxy works |
| 4.5 | PortForwardScreen | `app/frontend/src/screens/PortForwardScreen.tsx` | List + add form |
| 4.6 | ForwardCard component | `app/frontend/src/components/ForwardCard.tsx` | Status + controls |
### Day 18: Import/Export
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 4.7 | SSH config parser | `app/backend/bridge/import.go` | Parses ~/.ssh/config |
| 4.8 | CSV import | `app/backend/bridge/import.go` | Parses CSV |
| 4.9 | Export handler | `app/backend/bridge/export.go` | Encrypted JSON export |
| 4.10 | ImportScreen | `app/frontend/src/screens/ImportScreen.tsx` | Drag & drop UI |
### Day 19-20: Jump Hosts + Proxy
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 4.11 | Jump host chain | `app/backend/ws/jump.go` | Multi-hop SSH works |
| 4.12 | SOCKS5 proxy | `app/backend/ws/proxy.go` | Proxy before SSH |
| 4.13 | AdvancedHostForm | `app/frontend/src/components/AdvancedHostForm.tsx` | Jump host selector |
---
## Sprint 5 — Platform Polish (Day 21-25)
**Goal**: Desktop polish + performance + mobile.
### Day 21-22: Desktop Enhancements
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 5.1 | Native menu bar | `app/electron/menu.ts` | File/Edit/View/Help |
| 5.2 | System tray | `app/electron/tray.ts` | Tray icon + menu |
| 5.3 | Global shortcuts | `app/electron/shortcuts.ts` | Cmd+`, Cmd+N |
| 5.4 | Window state | `app/electron/state.ts` | Position/size saved |
| 5.5 | Auto-update | `app/electron/updater.ts` | Update prompt |
| 5.6 | OS Keychain | `app/electron/keychain.ts` | Store vault key |
### Day 23: Performance Optimization
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 5.7 | SSH connection pool | `app/backend/ws/pool.go` | Reuse connections |
| 5.8 | Buffer pools | `app/backend/ws/buffers.go` | sync.Pool for buffers |
| 5.9 | Virtual scrolling | `app/frontend/src/components/VirtualList.tsx` | 1000+ hosts smooth |
| 5.10 | Code splitting | `app/frontend/src/router.tsx` | Lazy load screens |
| 5.11 | React.memo | Various components | No unnecessary re-renders |
| 5.12 | WebGL renderer | `app/frontend/src/components/XTermWrapper.tsx` | Fast terminal render |
### Day 24-25: Mobile Polish
| # | Task | Files to Create | Verify |
|---|------|----------------|--------|
| 5.13 | Android gomobile | `mobile/android/` | .aar compiled |
| 5.14 | iOS gomobile | `mobile/ios/` | .xcframework compiled |
| 5.15 | Mobile keyboard | `app/frontend/src/components/MobileKeyboard.tsx` | Terminal toolbar |
| 5.16 | Gesture navigation | `app/frontend/src/hooks/useGesture.ts` | Swipe to switch tabs |
| 5.17 | Landscape handling | `app/frontend/src/styles/mobile.css` | Responsive layout |
| 5.18 | Bluetooth keyboard | `app/frontend/src/hooks/useKeyboard.ts` | External keyboard |
---
## Dependency Graph
### Files to Create (Sprint 2)
```
Sprint 0 (Scaffolding)
├── Sprint 1 (Foundation)
│ │
│ ├── Sprint 2 (Core Features)
│ │ │
│ │ ├── Sprint 3 (SFTP + Polish)
│ │ │ │
│ │ │ └── Sprint 5 (Platform Polish)
│ │ │
│ │ └── Sprint 4 (Advanced Features)
│ │ │
│ │ └── Sprint 5 (Platform Polish)
internal/model/snippet.go
internal/model/keychain.go
internal/handler/snippets.go
internal/handler/keychain.go
views/partials/snippets.html
views/partials/snippet_grid.html
views/partials/snippet_modal.html
views/partials/keychain.html
views/partials/key_grid.html
views/partials/key_modal.html
static/js/clipboard.js
static/js/utils.js
```
**Critical path**: Sprint 0 → Sprint 1 → Sprint 2 → Sprint 5
**Parallel tracks**:
- Sprint 3 and Sprint 4 can be done in parallel
- Sprint 5 can start after Sprint 2 (desktop polish), Sprint 3 (SFTP mobile), Sprint 4 (advanced mobile)
---
## File Creation Summary
## Sprint 3 — Settings + Brief Overlay
| Sprint | New Files | Directories |
|--------|-----------|-------------|
| Sprint 0 | 12 | 6 |
| Sprint 1 | 23 | 2 |
| Sprint 2 | 16 | 2 |
| Sprint 3 | 22 | 4 |
| Sprint 4 | 13 | 1 |
| Sprint 5 | 18 | 2 |
| **Total** | **104** | **17** |
**Goal**: Settings page with profile form, toggles, connected devices.
### Tasks
| # | Task | Files |
|---|------|-------|
| 3.1 | Model Device + Config | `internal/model/device.go`, `internal/model/config.go` |
| 3.2 | Settings handler | `internal/handler/settings.go` |
| 3.3 | Brief handler | `internal/handler/brief.go` |
| 3.4 | Settings template | `views/partials/settings.html` |
| 3.5 | Brief template | `views/partials/brief.html` |
| 3.6 | Toggle switch JS | `static/js/toggles.js` |
| 3.7 | Toggle CSS | In custom.css |
| 3.8 | Danger zone | `localStorage.clear()` handler |
### Files to Create (Sprint 3)
```
internal/model/device.go
internal/model/config.go
internal/handler/settings.go
internal/handler/brief.go
views/partials/settings.html
views/partials/brief.html
static/js/toggles.js
```
---
## Sprint 4 — SFTP Dual-Pane
**Goal**: File browser dual-pane dengan navigasi folder, upload/download.
### Tasks
| # | Task | Files |
|---|------|-------|
| 4.1 | Model FileItem | `internal/model/file.go` |
| 4.2 | Model Transfer | `internal/model/transfer.go` |
| 4.3 | SFTP handler | `internal/handler/sftp.go` |
| 4.4 | SFTP template | `views/partials/sftp.html` |
| 4.5 | SFTP pane partial | `views/partials/sftp_pane.html` |
| 4.6 | Breadcrumb navigation | In sftp.html |
| 4.7 | Folder create + delete | Modal Alpine + POST |
| 4.8 | Toast notification | Alpine `x-data` |
### Files to Create (Sprint 4)
```
internal/model/file.go
internal/model/transfer.go
internal/handler/sftp.go
views/partials/sftp.html
views/partials/sftp_pane.html
```
---
## Sprint 5 — Terminal + xterm.js
**Goal**: SSH terminal via WebSocket + xterm.js dengan multi-tab.
### Tasks
| # | Task | Files |
|---|------|-------|
| 5.1 | Download xterm.js | `static/xterm/xterm.js`, `static/xterm/xterm.css` |
| 5.2 | Terminal handler | `internal/handler/terminal.go` |
| 5.3 | WebSocket handler | `internal/handler/terminal.go` (WS upgrade) |
| 5.4 | Terminal template | `views/partials/terminal.html` |
| 5.5 | Terminal JS | `static/js/terminal.js` (xterm init + WS) |
| 5.6 | Tab management | Alpine `x-data="terminalTabs()"` |
| 5.7 | SSH pipe (mock dulu) | Go backend → SSH via v1/pkg/ssh |
| 5.8 | Multi-tab per host | Alpine state + multiple xterm instances |
### Files to Create (Sprint 5)
```
internal/handler/terminal.go
views/partials/terminal.html
static/js/terminal.js
static/xterm/xterm.js
static/xterm/xterm.css
```
---
## Sprint 6 — Data Store Integration
**Goal**: Ganti mock data dengan real storage dari v1/pkg/. Integrasi SSH, SFTP, crypto.
### Tasks
| # | Task | Files |
|---|------|-------|
| 6.1 | replace directive go.mod | `app/backend/go.mod` |
| 6.2 | Integrate v1/pkg/storage | Semua handler |
| 6.3 | Integrate v1/pkg/config | `handler/settings.go` |
| 6.4 | Integrate v1/pkg/crypto | `handler/keychain.go` |
| 6.5 | Integrate v1/pkg/knownhosts | `handler/terminal.go` |
| 6.6 | Real SSH via v1/pkg/ssh | `handler/terminal.go` WS |
| 6.7 | Real SFTP via v1/pkg/ssh | `handler/sftp.go` |
| 6.8 | Remove all mock data | Semua handler |
---
## Total Files
| Sprint | Go Files | HTML Templates | JS Files | CSS Files |
|--------|----------|---------------|----------|-----------|
| Sprint 0 | 4 | 3 | 2 | 2 |
| Sprint 1 | 2 | 5 | 1 | 0 |
| Sprint 2 | 4 | 6 | 2 | 0 |
| Sprint 3 | 4 | 2 | 1 | 0 |
| Sprint 4 | 3 | 2 | 0 | 0 |
| Sprint 5 | 1 | 1 | 1 | 0 |
| Sprint 6 | (modify existing) | 0 | 0 | 0 |
| **Total** | **18** | **19** | **7** | **2** |