Comprehensively update README.md, in-app help, and copilot-instructions.md to document all 9 recently added features. README.md: - Add new feature highlights: TOTP/2FA, proxy, auto-reconnect, login scripts, broadcast, snippets, search, context menu, quick connect - Add Power Features section covering broadcast, snippets, search, context menu, auto-login scripts, auto-reconnect, and quick connect - Expand SSH section with TOTP, proxy, login scripts, auto-reconnect - Expand Telnet section with login scripts and auto-reconnect - Add new keyboard shortcuts: Cmd+Shift+B/S/Q - Add troubleshooting entries for proxy, TOTP, login scripts, auto-reconnect - Update architecture: 18 JS modules, sshclient with proxy/TOTP In-app help (settings.js): - Add new Power Features section documenting snippets, search, quick connect, login scripts, and auto-reconnect - Expand SSH section: TOTP/2FA, proxy, auto-login scripts, auto-reconnect - Expand Telnet section: login scripts, auto-reconnect - Expand Sessions & Tabs: broadcast mode, right-click context menu - Expand Security section: TOTP secret storage - Add new keyboard shortcuts to shortcuts table - Add new troubleshooting entries (proxy, TOTP, login scripts, reconnect) - Add broadcast mode to Fun Things section copilot-instructions.md: - Update JS module count and script load order - Document sshclient proxy/TOTP capabilities - Add SFTP, recording, export/import, updater packages - Add login script and TOTP profile editor patterns
7.6 KiB
Copilot Instructions — Nexus Terminal
Build & Test
make deps # go mod download + npm install
make build # production build → build/bin/nexus-terminal.app
make dev # wails dev with hot reload
make test # go test ./... -v -count=1
make lint # golangci-lint run ./...
# Single package test
go test ./internal/storage/ -v -count=1 -run TestProfileCRUD
The frontend has no separate build step — npm run build (called by Wails) copies xterm vendor libs from node_modules into frontend/src/js/lib/ and frontend/src/css/lib/. All frontend assets are embedded at compile time via frontend/embed.go.
Architecture
Wails v2 desktop app: Go backend + HTML/CSS/JS frontend rendered in a native webview (WKWebView on macOS).
Go backend (internal/):
app/— lifecycle bootstrap: opens SQLite, inits secret stores, wires everything togetherapi/— singleServicestruct bound to Wails; every public method is callable from JS aswindow.go.api.Service.MethodName()sessions/— manages concurrent protocol sessions; owns SSH/Telnet/RAW/GitHub client lifecycles; handles auto-reconnect, login scripts, broadcaststorage/— SQLite repository (WAL mode) with embedded sequential migrations instorage/migrations/secrets/— two-tier credential storage: OS keychain (preferred) → AES-256-GCM encrypted file vault (fallback)models/— shared domain types and defaults; protocol configs are JSON-encoded in the DBsshclient/— SSH client with agent, TOTP/2FA, and SOCKS5/HTTP proxy supporttelnetclient/,rawclient/,githubclient/— protocol implementationssftpclient/— SFTP file operations (browse, upload, download, rename, delete)sshconfig/— OpenSSH~/.ssh/configparser and importerrecording/— asciicast v2 session recording and playbackexportimport/— encrypted profile bundles (.nexus format)updater/— GitHub Releases update checkerknownhosts/— SSH host key managementvalidation/— profile and protocol validationaudit/— timestamped audit logging
Frontend (frontend/):
- Plain HTML/CSS/JS — no framework, no bundler, no ES modules
index.htmlis the app shell; loads CSS then JS in strict ordersrc/js/— 18 modules, each IIFE-wrapped, exporting towindow.NexusTerminalsrc/css/— modular stylesheets;tokens.cssdefines all design tokens- Vendor libs (xterm.js, addons) live in
src/js/lib/andsrc/css/lib/— auto-copied, gitignored
Go ↔ JS communication:
- Synchronous: Frontend calls
window.go.api.Service.*()(returns Promise) - Asynchronous: Backend pushes events via
wailsRuntime.EventsEmit(); frontend listens withwindow.runtime.EventsOn() - Bidirectional events for secret prompts and host key verification: backend emits a request event with a
requestID, frontend shows a modal, then emits a response event with the same ID. The Go side blocks on a channel keyed by that ID.
Key events: session:data, session:state, session:closed, session:raw:data, session:github:repos, session:secret:prompt/session:secret:response, session:hostkey:verify/session:hostkey:response.
WKWebView Constraints (Critical)
The macOS webview silently blocks or breaks several standard web APIs. These constraints affect every frontend change:
-
No
window.confirm(),window.prompt(),window.alert()— they either return false/null silently or do nothing. All user prompts must use the custom modal system (DOM-based backdrop + modal withis-visibleclass toggling). -
No ES modules —
<script type="module">fails silently. All JS files use plain<script>tags and IIFE wrappers:(function() { ... })(); -
Click swallowing on text-selectable elements — WKWebView initiates a text selection gesture on
mousedownfor<div>elements, which swallows the firstclickevent (appears to require double-click). Fix: addmousedownhandler withe.preventDefault()on any clickable non-input element (section headers, list items, etc.). -
Modal visibility pattern — both
.modal-backdropAND.modalelements start withopacity: 0; visibility: hiddenin CSS. You must add theis-visibleclass to both elements, not just the backdrop. -
Wails runtime injection —
window.goandwindow.runtimeare injected by Wails before scripts execute. Do NOT add explicit script tags for Wails scripts. The frontend polls for their availability at startup (max 5s).
Conventions
Language: All code comments in British English (colour, initialise, behaviour, etc.).
Go patterns:
- String-typed enums:
type Protocol stringwithconst ProtocolSSH Protocol = "ssh"etc. - Protocol configs are pointer fields on
Profile(SSH *SSHConfig,Telnet *TelnetConfig, etc.) — nil when not applicable NewProfile(protocol)factory indefaults.gopopulates sensible defaults per protocol- Storage uses
nullableJSON()helper to write nil for unused config columns - Tests use
t.TempDir()for isolation,t.Fatalf()for setup failures
Frontend patterns:
- Every JS file is IIFE-wrapped and exports to
window.NexusTerminalnamespace - Script load order matters (defined in
index.html) — utils → state → notifications → sidebar → tabs → themes → terminal → profiles → raw → github → settings → commands → secrets → recordings → sftp → splitpane → snippets → app - DOM helpers:
createElement(tag, className, attrs)from utils.js (supportstextContent,innerHTML,dataset,on*event handlers) - Profile editor uses
_renderSection(title, fields)pattern with_field(type, name, label, value, opts)helper for form generation - Login script editor uses
_renderLoginScriptSection(prefix, steps)for expect-style step lists - TOTP section uses
_renderTOTPSection(profile)with encrypted vault storage protocolColour()returns CSS class names likeproto-ssh; tab badges usebadge-sshclass naming (different pattern)
CSS patterns:
- Design tokens in
tokens.css:--bg-base,--bg-surface,--bg-elevated,--text-primary,--accent,--protocol-ssh, etc. - Dark charcoal palette — not Material Design literally, but Material-inspired layering
- Protocol colours: SSH=teal
#4ecdc4, Telnet=amber#ffa726, RAW=purple#ab47bc, GitHub=indigo#7c8aff
Adding a new protocol:
- Add
Protocolconstant + config struct inmodels/models.go - Add defaults in
models/defaults.goNewProfile()switch - Create client package in
internal/<proto>client/ - Add
connect<Proto>()insessions/manager.go+ case inConnect()switch - Add API methods in
api/api.goif needed - Add DB migration in
storage/migrations/(update CHECK constraint, add config column) - Update
storage/store.go: all SELECT/INSERT/UPDATE queries + both scan functions - Frontend: add DEFAULTS in
profiles.js, render sections, collect form values, protocol tab - Frontend: add viewer in new JS file (IIFE-wrapped), include in
index.html, wire inapp.js - Add CSS: protocol colour token, sidebar indicator, tab badge, viewer styles
Database migrations:
- Numbered SQL files:
001_initial.sql,002_github_protocol.sql, etc. - Each must
INSERT INTO schema_version (version, applied_at) VALUES (N, datetime('now'))at the end - SQLite has no
ALTER COLUMN— use the recreate-table pattern (CREATE new → INSERT INTO new SELECT FROM old → DROP old → ALTER TABLE RENAME) - Every query in
store.gothat touches the profiles table must be updated when columns change (there are ~8 SELECT column lists + 2 scan functions)