- lib.js, test/lib.test.js, and all executable JS in TailscaleWidget.qml (onExited handlers, custom methods, onClicked blocks) now terminate every statement with ;
- All if/for/while blocks use {} even for single statements (per repo code style rules)
- No behavior change; all tests continue to pass
- This was the missing follow-up to the previous refactor (the changes existed in the working tree but were never committed on the feature branch)
Written by AI agent working for @jtmorris. Model: grok-build-0.1.
- Add PendingAction constant and commandForPendingAction pure function (fully tested)
- Remove root _pendingToggle property
- Use dynamic _pendingAction on the statusCheck Process for the fresh-status hand-off
- Introduce refreshStatus() helper and deduplicate the two post-action refresh sites
- All JS statements now terminate with semicolons; all blocks use braces
- Behavior unchanged; coordination token no longer leaks to widget root
- Enables future post-status actions without adding more root properties
Written by AI agent working for @jtmorris. Model: grok-build-0.1.
- lib.js: single clipboardTools array + getClipboardCommands(text) returning argv lists
- Delete validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, safeClipboardTools, old clipboardTools object
- Remove noClipboardTool / invalidClipboardCommand from getStrings (dead)
- TailscaleWidget.qml: remove _copyCurrentTool + _executeCopy; add _copyIndex + _runNextCopy
- copyProcess.onExited and copyToClipboard now purely index-driven, no tool names
- Update tests: remove 4 old clipboard test blocks + dead string assertions; add focused getClipboardCommands tests
- All 34 tests pass
This is the minimal honest design given Process is a QML type.
- Existing toasts + truncated stderr provide the actionable information for users and errors
- Adding persistent debug logs would introduce state, rotation, and maintenance concerns
- with no demonstrable improvement to UX, decisions, or security for normal operation
- Explicit no-op commit closes the planning issue
Written by AI agent working for @jtmorris. Model: Grok build-0.1.
- toggleTailscale() now forces a fresh statusCheck instead of reading possibly-stale root.isConnected
- The actual up/down decision happens in statusCheck.onExited using the just-received value
- _pendingToggle is a transient one-shot flag (reset immediately after use) — not a persistent 'desired state' cache
- Complies with AGENTS.md rule 1 / #11
Written by AI agent working for @jtmorris. Model: Grok build-0.1.
- Two small pure UI predicates moved out of QML delegate
- QML now calls TailscaleLib.* (thinner glue)
- New tests + all existing tests pass
- No new state, no architecture change
Written by AI agent working for @jtmorris. Model: Grok build-0.1.
- safeClipboardTools now only dms + wl-copy (clipmanctl removed as niche optional history manager)
- buildCopyCommand now returns pure argv arrays (no sh -c, no escaping, eliminates future injection risk)
- Added getStrings() for centralised user-facing strings (prep for real i18n)
- Updated all tests (TDD) and README
- All 29 tests pass
Written by AI agent working for @jtmorris. Model: Grok build-0.1.
- Revert binaryAvailable property: reintroduced stale-state anti-pattern
already rejected in #11. All error paths already handled by Process
onExited handlers.
- Move statusCheck parsing from onStreamFinished to onExited (#38):
parser now only runs after exit code validation, preventing stale
data from being applied on failure.
- Add refreshTimer guard (#44): timer no longer spawns new statusCheck
while one is already running.
Closes#38, #44. Reverts #52 partial fix.
Store _pendingToggleAction before starting toggleProcess.
Use it in onExited instead of live root.isConnected.
Prevents 'Failed to disconnect' when user actually clicked connect.
Fixes#14
- Remove detectClipboard Process and clipboardCmd property
- Add try-fallback-cached copy logic: attempt cached tool first,
then cycle through hardcoded safe tools (dms, wl-copy, clipmanctl)
- Add stderr StdioCollector to all Process components for error details
- Update lib.js: replace parseClipboardDetection with validateClipboardTool,
buildCopyCommand, nextClipboardTool, allClipboardTools
- Update tests to match new lib.js API
Benefits:
- No startup delay from clipboard detection process
- Robust fallback if clipboard tool becomes unavailable
- Better error messages with stderr details from failed commands
The previous &&/|| chain evaluated all commands, producing multi-line
output when multiple tools were installed. parseClipboardDetection
correctly rejected this as invalid, falling back to 'none'. The
if/elif/else structure ensures only the first match is echoed.
Replace inline shell command building in QML with calls to
parseClipboardDetection, parseStatusResult, buildToggleCommand, and
buildCopyCommand. This centralizes sanitization logic and removes
duplicated try/catch and ternary blocks from the UI layer.
- Add errorMessage() utility for user-friendly toast messages
- Show toast on tailscale up/down/set/status failures
- Detect missing tailscale binary (exit code 127)
- Guard toggleTailscale and setExitNode when binary unavailable
- Show 'Tailscale not available' in popout when binary missing
QML's JS engine does not support ES module export/import syntax.
Replace bare export with a CommonJS guard that only runs under Node.js,
so the plugin loads in QML while tests still work.
- Extract peer parsing and exit-node command logic into lib.js
- Add ↗ button on peers that are ExitNodeOption candidates
- Highlight active exit node in primary color
- Add clear (×) button next to current exit node label
- Add unit tests for parsePeers and makeExitNodeCommand
- Parse Peers from tailscale status --json into a model
- Add ListView with hostname/IP rows below status row
- Click hostname or IP to copy via wl-copy + toast confirmation
- Increase popout height to 400 to accommodate peer list
- Online peers shown in primary color, offline in surfaceVariantText
Add propagateComposedEvents and left-button handling to both
horizontal and vertical MouseArea blocks. Left-click is forwarded
to PluginComponent (mouse.accepted = false) so it opens popoutContent.
Right-click still toggles Tailscale connection.
Closes#10
Add Timer (5s interval) that runs tailscale status --json, parsing
BackendState, Self.TailscaleIPs[0], and CurrentExitNode.HostName into
isConnected, tailscaleIP, and currentExitNode properties. Parse errors
surface via ToastService.showError. Initial check runs on load.
Closes#4
Implement connected/disconnected icon (vpn_key/vpn_key_off) in
both horizontal and vertical bar orientations. Right-click triggers
tailscale up / tailscale down via Process. Icon color follows
DMS theme (primary when connected, surfaceText when disconnected).
Closes#3
Minimal loadable DMS widget skeleton with plugin.json manifest
and TailscaleWidget.qml component. Symlinked, enabled, and loaded
cleanly in Dank Material Shell.
References #1, #2