Commit graph

52 commits

Author SHA1 Message Date
7942bc3471 feat(i18n): make lib.js strings I18n source keys (strategy A); remove dead symbol exports; update tests; add i18n/ scaffolding + en.json placeholder
- getStrings().copied now returns template key "Copied %1 to clipboard" (interpolation at QML call site via .arg)
- Removed unused clearExitNode/setExitNode from getStrings (were dead code)
- Tests updated for new shape while preserving coverage
- New tailscalectl/i18n/ with README + en.json for future-proofing (per dms-plugin-dev best practice)
- Exact behavior preserved; no logic changes

Written by AI agent working for @jtmorris. Model: grok-build-0.1.
2026-05-25 03:00:33 +00:00
aacc027984 style: strictly enforce semicolon termination + curly braces on all JS statements
- 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.
2026-05-25 03:00:33 +00:00
7b41d7177f refactor: extract toggle decision logic to lib.js and scope one-shot coordination to statusCheck Process
- 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.
2026-05-25 03:00:33 +00:00
12ef07040c Code formatting cleanup. 2026-05-25 03:00:33 +00:00
6e4e5d51ec refactor(clipboard): replace object map + 4 helpers with simple array + getClipboardCommands; QML becomes dumb index driver
- 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.
2026-05-25 03:00:33 +00:00
e695db6650 security: validate hostname in makeExitNodeCommand (balanced paranoia, direct-argv defense-in-depth, TDD) 2026-05-25 03:00:33 +00:00
a40d8d49a4 refactor: centralize remaining error paths and popout strings via formatError + getStrings (final polish pass) 2026-05-25 03:00:33 +00:00
4a7803dc42 refactor: simplify copy path — drop caching and multi-tool retry state machine, keep simple dms→wl-copy fallback (first-principles plan) 2026-05-25 03:00:33 +00:00
5ace4095c6 feat: remove 5-second refreshTimer — first major step toward low-duty-cycle architecture (on-demand + post-action verification only) 2026-05-25 03:00:33 +00:00
619c988f56 chore: remove historical issue-chasing comments from QML and lib.js (per first-principles plan) 2026-05-25 03:00:33 +00:00
9dd73b6d55 refactor: add getStatusCommand pure helper + test (TDD step 2 — prepares low-duty-cycle status calls) 2026-05-25 03:00:33 +00:00
6b71197770 refactor: introduce formatError centralizer + remove first batch of historical comments (TDD step 1 of first-principles plan) 2026-05-25 03:00:33 +00:00
ee36ae5c72 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-25 03:00:33 +00:00
e1569dd870 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-25 03:00:33 +00:00
440e954099 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-25 03:00:33 +00:00
925233dfb9 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-25 03:00:33 +00:00
1a937a8249 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-25 03:00:33 +00:00
afdcba9d4c 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-25 03:00:33 +00:00
da4d566f0c 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-25 03:00:33 +00:00
2fa5694895 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-25 03:00:33 +00:00
b6ead317e1 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-25 03:00:33 +00:00
42d3bef5c6 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-25 03:00:33 +00:00
3d62e4c4b2 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-25 03:00:33 +00:00
6385f6b19c Professionalize the author name. 2026-05-25 02:49:59 +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