Renames the product across the codebase: Go module path, config/data paths, keychain service name, export file extension, build tooling, docs, and UI text. Sidebar brand mark now reads ">_ AlchementaryLink" with "Alchementary" in the connection-group gold accent colour, and the window title is "Alchementary Link". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019T8bJS8BCtY5GeSG2HBj1n
7.6 KiB
Copilot Instructions — Alchementary Link
Build & Test
make deps # go mod download + npm install
make build # production build → build/bin/alchementary-link.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 (.link 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.AlchementaryLinksrc/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.AlchementaryLinknamespace - 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)