- hx-put="" (empty) was causing HTMX to send wrong requests
- Now use single hx-post="/hosts" with hidden hostId field
- Server checks hostId: empty → create, present → update
- UpdateHost reads ID from both URL param and form data
- Add UpdateHost handler + PUT /hosts/:id route
- CreateHost now accepts all fields (port, username, auth, notes)
- host_modal.html: full form with port, username, auth type toggle, edit mode
- host_list.html: edit button dispatches edit-host event with host data
- Connect button links to /terminal?host={id} for pre-selection
- Terminal page reads selected host from query param
- Toast notification system via Alpine.js events
- Add default template function for fallback values
- Server-side {{if eq $.View .ID}} only works on initial page load
- HTMX swaps #main-content only, nav never re-renders → stale highlight
- Add x-data page state + @after-settle.window listener to track current route
- Nav items use :class binding to apply active style based on Alpine state
- Create terminal_content.html, sftp_content.html, settings_content.html, brief_content.html
- Update full page templates to use partial templates via {{template}}
- Handlers already rendered these templates on HX-Request, but files were missing → 500
- All pages now return 200 for both full page and HTMX nav requests
- go.mod replace directive for git.tukangketik.id/swanadiva/hostkeeper → ../../v1
- store/ package: JSON file persistence for hosts, snippets, keys, settings
(~/Library/Application Support/hostkeeper/v2/)
- model SSH fields: Hostname, Port, Username, AuthType, Password, KeyID
- sshconn/ package: real SSH client via golang.org/x/crypto/ssh
- Terminal WS: real SSH connection with fallback to mock shell
- Dynamic host list in terminal (server-rendered JSON script tag)
- All mock data defaults removed from model packages
- Settings persist via store/settings.go
- 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
- git mv docs/plans/ (V1 planning docs) into v1/docs/plans/
- Create AGENTS.md as AI session checkpoint for seamless continuation
- Root now clean: only V2-related files remain
- Add Theme struct with 3 predefined themes (dark/light/druntime)
- Add Profile struct for named configuration profiles
- Add ErrorBanner with severity levels and auto-dismiss
- Add unit tests for theme, error banner, and models
- Update CHANGELOG.md and PROJECT_STATE.md
- Storage.IsDataEncrypted() checks if hosts.json is encrypted
- TUI auto-detects encrypted files, prompts password automatically
- Root command: --password flag for all CLI commands
- newStorage() helper applies password flag to storage
- add/list/edit/delete commands now support encrypted storage
- passwordSetMsg loads hosts after password is set
- M key enters rename mode with current name pre-filled
- Type new name, Enter to confirm, Esc to cancel
- renameItem: client.Rename() for remote, os.Rename() for local
- Clear cache + refresh after rename
- Footer updated: M:rename
- Filter mode allows M to pass through
- Clear dirCache before refresh after transfer/delete/mkdir
- Cache was stale: old entries returned instead of fresh data
- Filter mode: allow commands (c,d,n,r) and nav (↑↓,Tab) to pass through
- Filter mode no longer blocks transfer/delete/mkdir/pane-switch
- Delete: clear cache + program.Send() for re-render
- Mkdir: clear cache + program.Send() for re-render
- Added program *tea.Program to SFTPBrowserTab and Model
- SetProgram() allows goroutines to send messages to Bubble Tea
- After transfer: program.Send(sftpRefreshMsg{}) triggers re-render
- Model stores program reference, passes to SFTP tab on creation
- Progress bar: [████████░░░░] 67% 12.3MB/18.5MB
- progressWriter wraps io.Writer, reports bytes via callback
- Footer shows live progress during upload/download
- Auto-refresh destination pane after transfer completes
- Success message shown for 1 second before clearing
- Added dirCache map to sftpPane for caching directory listings
- Cache hit: instant navigation (no disk/network I/O)
- Cache miss: read from disk/SFTP, store in cache per path
- R key clears current directory cache and forces fresh read
- Significant speedup when navigating back to previously visited folders
- Default active pane changed from Remote to Local on SFTP init
- renderPane now takes maxH parameter for fixed border height
- paneStyle.Height(maxH) prevents border from shrinking/growing on scroll
- Stacked mode: maxH = t.height - 6; Side-by-side: t.height - 5
Stacked mode (<50 cols) now shows only the active pane (Local/Remote)
with a visual indicator bar. Tab switches between panes. Side-by-side
mode (>=50 cols) unchanged.
Root cause: lipgloss Width(maxW) = content area inside border, border adds 2 chars extra.
- halfW was t.width/2, should be t.width/2-2 (reserve border per pane)
- innerW was maxW-4, should be maxW-2 (padding only, not border)
- Each pane total = maxW + 2 (content + border)
- NEW responsive.go: wrapFooter, adaptiveSidePad, clampWidth, truncateStr
- Footer auto-wraps to multi-line on narrow terminals (full labels preserved)
- Box width clamped to terminal width across all tabs
- Host/key/snippet rows truncated with ellipsis; compact format <35 cols
- SFTP panes stack vertically when terminal < 50 cols
- Tab bar truncates names on overflow
- Form contentW minimum lowered 50->30 for mobile
- Fixed: key_list_tab & snippet_list_tab dropped last data row (off-by-one bug)