Commit graph

45 commits

Author SHA1 Message Date
740bc81cea refactor: centralize remaining error paths and popout strings via formatError + getStrings (final polish pass) 2026-05-22 10:20:47 +00:00
2b3b45ccfa refactor: simplify copy path — drop caching and multi-tool retry state machine, keep simple dms→wl-copy fallback (first-principles plan) 2026-05-22 10:20:27 +00:00
aa3ade1777 feat: remove 5-second refreshTimer — first major step toward low-duty-cycle architecture (on-demand + post-action verification only) 2026-05-22 10:19:56 +00:00
4db80f7f1e chore: remove historical issue-chasing comments from QML and lib.js (per first-principles plan) 2026-05-22 10:19:48 +00:00
25e56c33ea refactor: add getStatusCommand pure helper + test (TDD step 2 — prepares low-duty-cycle status calls) 2026-05-22 10:19:39 +00:00
d6b704b884 refactor: introduce formatError centralizer + remove first batch of historical comments (TDD step 1 of first-principles plan) 2026-05-22 10:19:25 +00:00
0283ce6136 chore: evaluate structured logging per AGENTS.md rule 1 — no addition justified (#47)
- 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.
2026-05-22 06:42:11 +00:00
5219271be3 fix: eliminate toggle decision staleness via poll-then-act (no cached desired state) (#42)
- 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.
2026-05-22 06:42:11 +00:00
40a408e029 refactor: extract shouldShowClearExitNode and isActiveExitNode to lib.js (TDD) — light progress on #43
- 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.
2026-05-22 06:42:11 +00:00
06925ed3fc feat: extract UI strings to lib.js; harden clipboard to direct argv, drop clipmanctl (#34, #49)
- 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.
2026-05-22 06:42:11 +00:00
d2e9069ce5 fix: remove binaryAvailable, fix statusCheck parser location, add timer guard
- 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.
2026-05-22 03:13:33 +00:00
0d381751bf fix: remove propagateComposedEvents and fix plugin author (#50, #51)
- TailscaleWidget.qml: remove propagateComposedEvents from right-click MouseArea
- plugin.json: clean up author string
2026-05-22 03:13:24 +00:00
dca1039c51 fix: lib.js security and correctness fixes (#40, #41)
- parsePeers: add hasOwnProperty guard and filter null entries
- findActiveExitNode: add hasOwnProperty guard to for..in loop
- makeExitNodeCommand: handle empty hostname correctly
2026-05-22 03:13:24 +00:00
40e1e18a7f fix: remove unused settings_read/settings_write permissions from plugin.json
Only process permission is required for tailscale + clipboard commands.
Least-privilege fix. Fixes #16
2026-05-21 07:05:02 +00:00
f20837ada9 fix: add running guards to prevent concurrent toggleProcess/exitNodeProcess
Rapid clicks or slow ops no longer spawn duplicate tailscale invocations.
Matches existing pattern used for copyProcess safety.
Fixes #15
2026-05-21 07:04:45 +00:00
79e33eccc0 fix: capture intended toggle action at click time to avoid stale isConnected in error toast
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
2026-05-21 07:04:29 +00:00
1afc8a88f1 fix: remove redundant binaryAvailable guard from TailscaleWidget.qml
Removes stale one-way property, guards, and dead UI branch.
Error handling already covered by Process onExited handlers.
Fixes #11
2026-05-21 07:02:48 +00:00
701daa9a39 refactor: replace detectClipboard with try-fallback-cached clipboard strategy
- 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
2026-05-21 06:37:14 +00:00
2ece3cc103 fix: use if/elif/else in clipboard detection script
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.
2026-05-21 02:36:40 +00:00
6ef933211c fix: expand clipboard detection to xclip and xsel
Add xclip and xsel to the whitelist and detection script so users
on systems without wl-copy or clipmanctl can still copy peer info.
2026-05-21 02:29:13 +00:00
93b257ba5d refactor: delegate shell command construction to lib.js functions
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.
2026-05-20 19:37:08 +00:00
b521af3364 feat: add parseStatusResult for safe JSON parsing of tailscale status
Wraps JSON.parse in try/catch, returns safe defaults on failure,
and delegates to existing findActiveExitNode and parsePeers.
2026-05-20 19:29:02 +00:00
ce8707c618 feat: add buildToggleCommand for safe toggle command construction
Replaces inline ternary in QML with a pure function that returns
the correct tailscale up/down command based on connection state.
2026-05-20 19:21:17 +00:00
dddd9218f6 feat: add parseClipboardDetection for safe clipboard tool parsing
Trims output and validates against whitelist, falling back to 'none'
for empty, multi-line, or unexpected values.
2026-05-20 10:16:02 +00:00
3c31209ff1 feat: add buildCopyCommand for safe clipboard command construction
Uses validateClipboardCmd to reject untrusted input and properly escapes
single quotes in text to prevent shell injection.
2026-05-20 10:11:57 +00:00
a86d5e279d feat: add validateClipboardCmd with whitelist-based input validation
Prevents shell injection by only allowing known-safe clipboard commands
(dms cl copy, wl-copy, clipmanctl copy, none).
2026-05-20 10:08:59 +00:00
0083fe7dc2 docs: document intentional propagateComposedEvents: true on right-click MouseArea 2026-05-19 04:40:49 +00:00
bd9df03b5b fix: find active exit node from Peer map instead of CurrentExitNode 2026-05-18 22:10:37 +00:00
303256d646 Write README with install instructions and update author 2026-05-18 21:17:54 +00:00
6129b09943 feat: add error handling and toast notifications (#8)
- 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
2026-05-18 20:53:32 +00:00
c9d1baf7c4 fix: remove ESM export from lib.js for QML compatibility
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.
2026-05-18 18:39:39 +00:00
0c2376b3bb feat: add exit node selection to Tailscale widget
- 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
2026-05-18 18:27:41 +00:00
2d438415ee fix: use single-arg ToastService.showInfo for copy toast 2026-05-18 05:33:42 +00:00
d8bd3c6c30 feat: auto-detect clipboard tool at startup (#6)\n\n- Priority: dms cl copy → wl-copy → clipmanctl copy → none\n- Show error toast if no clipboard tool is found\n- statusCheck runs after clipboard detection completes 2026-05-18 05:08:15 +00:00
2c29fcc0b6 feat: add click-to-copy for peer hostname and IP (#6)\n\n- Use Process (copyProcess) instead of unimported Quickshell.execDetached\n- MouseArea on hostname and IP in peer list delegate\n- Toast confirmation on copy 2026-05-17 23:00:22 +00:00
3a8fa8abd1 fix: parse Peer (singular) key, remove clipboard for now 2026-05-17 22:36:10 +00:00
0d3f500a2c feat: add peer list with click-to-copy (#6)
- 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
2026-05-17 21:59:59 +00:00
f980f4da21 fix(tailscalectl): apply all three click/layout fixes from working config 2026-05-17 21:32:46 +00:00
0743f5450e fix(tailscalectl): remove MouseArea from pills, add root-level right-click handler 2026-05-17 21:23:01 +00:00
821ede2bea fix(tailscalectl): let PluginComponent handle left-click for popout 2026-05-17 21:18:02 +00:00
c3f63873ac feat(tailscalectl): add popout skeleton with header row 2026-05-17 21:11:14 +00:00
77430ec0d4 fix(tailscalectl): propagate left-click through MouseArea to open popout
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
2026-05-15 19:28:37 -07:00
8dd7562d35 feat(tailscalectl): add periodic status polling and reactive state
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
2026-05-15 19:19:01 -07:00
9c691ccc64 feat(tailscalectl): add bar pill with icon and right-click toggle
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
2026-05-15 19:14:49 -07:00
778f1969e0 feat(tailscalectl): add initial DMS widget plugin scaffolding
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
2026-05-15 19:10:50 -07:00