Repository navigation
Releases: Ar3ss12/nanoclick
Release list
NanoClick v1.3.0 Updater Hardening & Portable In-Place
NanoClick v1.3.0
A hardening release: the app is quieter in the tray, harder to break, and
updates itself cleanly — including the zero-install portable binary.
Updates that cannot leave a mess behind
- NSIS updates now tear down before the installer takes over. The one-shot
download-and-install call is split:download()→prepare_nsis_update→
install(). Between them the backend stops the tray icon (no ghost icon),
the clicker, the keyboard/mouse hooks and the foreground patrol, and flushes
the final config — before the updater plugin exits the process. - Portable updates happen in place, with no installer. The running
.exe
swaps itself: download to<exe>.new→ minisign-verify with the same key
the NSIS path trusts → renameexe → .old→ rename.new → exe→ respawn
with--updated-from=<pid>→ exit. A failed signature discards the download
and leaves the running binary untouched;.oldstays beside the new binary
until the first healthy boot as a free rollback copy. - Key rotation is forbidden for routine releases. Old installs trust only
the pubkey baked into their binary — rotating it orphans every existing
install. Same key, re-sign, unless a full migration plan exists.
The app is actually idle in the tray
- The app-profile thread no longer reads
config.jsonevery 1500 ms with an
empty rule set: each tick starts with one file-timestamp check, the rules
are re-parsed only on a real change, and the golden snapshot is written once
per change (plus on save and on quit) instead of ~40 times a minute. - The global-keyboard hook thread parks in the Windows kernel and wakes on the
event itself instead of spinning on a 5 ms channel timeout with a
per-bindingGetAsyncKeyStatescan. Shutdown wakes it immediately. - The config watcher polls at 2 s, and the 30-minute frontend update check is
visibility-gated — a hidden page no longer talks to GitHub.
A passive eye on elevated windows (zero polling)
- A
SetWinEventHook(EVENT_SYSTEM_FOREGROUND)patrol reports every foreground
switch at 0% idle CPU. When automation is live and the new foreground window
runs elevated, you get the warning sound, a notification record, and
(visible window only) the window brought forward without a sticky
always-on-top. A window you put in the tray is never resurrected: tray gets
a balloon only. - Seamless elevation is opt-in: a Task Scheduler logon task (
/RL HIGHEST,
plainschtasks, no COM) registered only by your explicit click, plus the
existingrunasrestart and the AppCompatRUNASADMINtoggle.
Your settings, macros and presets cannot be lost
- No save before hydration: a save issued before the config finished
loading is refused (toast +warnin the log) instead of overwriting the
live profile with factory defaults. The previousconfig.jsonis copied to
config.json.bak-<epoch>before the first write of every session. - A finished recording is data, not a dialog result: the macro is
persisted the moment recording stops (empty captures are reported, not
stored); renaming is a separate non-destructive inline edit. - Preset writes are verified: the editor awaits the save and closes only
on success; the grid re-renders from disk. The library lives in its own
presets.json(own snapshot, quarantine, atomic writes) — a config save
cannot replace it, and "no presets" is a real visible state. - A bound key can never arm the guard that gates it: the key policy keeps
two bitmaps (bound keys vs. seed + your ignore list), so a config save
cannot un-exempt your toggle hotkey. - The typing guard sleeps in Work Mode. The hook loop skips both the
kill-switch and the arming when the mode is Work — a lockout that gates
nothing is waste, and it used to leak across the Work→Autoclicker switch as
aBlockedByTypingfor typing that happened in the other mode. Hotkey
dispatch itself (mode switch back, emergency stop, presets) keeps working;
only the guard sleeps. Returning to Autoclicker disarms the guard, so the
first toggle after the switch fires cleanly.
The window opens the way you closed it
- "Reopen the window as I closed it": restart replays the last state —
open if it was open, straight to the tray if it lived there. The state is
stamped by the backend, never by a page that never saw the hide. Explicit
"Start Minimized to Tray" still wins. - Satellites (HUD, ripple overlay) never outlive the main window; shutdown
destroys them first andmainlast, after the backend stopped. Shutdown is
re-entry safe (the page's unload fallback lands back in the same function
and becomes a no-op). - Focus return resyncs live backend state (no stale IDLE/RUNNING after a
hotkey start from a game); in-window hotkeys fire with zero latency while
typing fields and the hotkey recorder stay exempt; browser accelerators
(F5, Ctrl+R, …) and the Chromium context menu are suppressed.
Notifications and sound
- All alerts flow through a backend
NotificationStore(50-item ring, six
categories): tray balloons +MessageBeepwhile hidden or on critical
alerts, reconciled as toasts/modals when the window returns. Sound cues are
synthesized with the Web Audio API: zero audio assets. UIPI throttle is
1500 ms.
Housekeeping
- Bundle identifier is
com.nanoclick.desktop. Only the WebView2 cache
directory is created anew on first start; config, presets, macros and
hotkeys are untouched. - Silent usage telemetry: a backend thread pings the update proxy
(?v=<semver>, no identifiers) 60 s after cold start and every 6 h after.
Counting is Cloudflare's edge; failures are silent and updates always go
direct to GitHub, never through the proxy.
Verified
- Frontend grammar: 12/12 scripts parse cleanly (module and classic modes).
- Frontend lint: 0 errors / 0 warnings (
oxlint). - Rust suite: 358/358 passed (274 unit + 84 integration tests;
7physical_filtered out) — plus the uncommitted typing-guard sleep
(disarm, Work→Autoclicker clean entry,typing_guard_sleeps_in_work_mode).
Measured 2026-10-04, release profile.
Install
Download NanoClick_1.3.0_x64-setup.exe below (no admin rights required), or
NanoClick-portable.exe (+ .sig) for a zero-install run that now updates
itself in place. Existing installs update themselves through the built-in
signed updater.
License
PolyForm Noncommercial License 1.0.0 with NanoClick Additional Terms. Free
for personal gaming, streaming and research. See
LICENSE.md.
NanoClick v1.2.0 — Settings Safety, Undroppable Macros & a Separate Preset Store
NanoClick v1.2.0
The interface is now disposable, side mouse buttons are full citizens, presets are full working
profiles that can be bound to hotkeys and switched instantly on the fly, and the click engine gains
realistic biomechanics with dynamic fadeout from 1 to 160 CPS.
Presets became a selective snapshot
- Every engine setting is editable in a preset now. The editor was missing
technique,
jitter radius and hesitation entirely: a preset could never pin Butterfly/Drag, the spatial
dispersion was always silently 0 (the "Save config" button did not even copy it), and hesitation
was inherited from whatever the engine happened to hold at save time. - The editor's ranges now match the dashboard, so merely loading or importing a preset can no
longer rewrite it: CPS starts at 0.1 (not 1.0), jitter reaches 35 % (not 30 %), the click limit
is capped at 1,000,000 (was unbounded in one place and unbounded-then-truncated in another), and
hold pause floors at 10 ms like the dashboard. - Capture scope. A preset declares which groups it owns — Engine (always captured),
Guards (typing lockout, focus-loss pause, app scope and its list), Hotkeys (the six global
bindings, the record key, key-memory TTL and smart record, toggle response time) and
Appearance (theme, accent, language, always-on-top, ripple, HUD, notifications). An unchecked
group collapses to a dimmed preview of the value it would inherit, so "not captured" can never be
mistaken for "empty". The nine preset slots are never captured (a slot maps to a preset, so it
would be recursive) and tray/lifecycle settings are excluded on purpose — applying a preset must
not hide the window or arm an autostart entry. - GUI lock duration, the auto-stop unit and the multi-point sequence travel with the preset, and
a single normalizer now gates the config load, the editor, the import and the apply path, so no
entry point can silently drop a field. - A better Presets page: search, a category filter (Combat / Utility / Game-specific), a
Duplicate action (the copy drops its hotkey so two presets never fight over one bind), scope
badges on every card, and a full grouped Inspect table that also names the groups a preset
does not carry.
Side mouse buttons & global input (Mouse 4, Mouse 5, Mouse 3)
- Side buttons as global hotkeys:
Mouse 4(XBUTTON1),Mouse 5(XBUTTON2) andMouse 3
(middle wheel button) are now first-class hotkey triggers across the entire application. - Dedicated low-level mouse hook (
WH_MOUSE_LL): Implemented directly in Win32 with a zero-cost
fast path forWM_MOUSEMOVEto guarantee zero latency on 1,000–8,000 Hz gaming polling rates. - WebView2 navigation guard: Capturing side buttons in the hotkey recorder explicitly blocks
Chromium's built-in "Back / Forward" page navigation (e.preventDefault()). - Typing Guard exclusion: Mouse button virtual keys (
0x01..=0x06) are exempt from keyboard typing
lockout, preventing legitimate mouse clicking from suppressing hotkeys.
Preset hotkeys & Lock-Free Preset Preemption
- Direct preset hotkey triggers: Every preset in the library can be assigned its own dedicated
activation hotkey with an edit modal and visual badges on the preset cards. - Atomic preset preemption (Instant Switch): Pressing the hotkey of another preset while one is
already running instantly stops the current worker and spawns the new preset in < 1 ms without
freezing the Win32 message loop (.join()is never called on the hot path). - Work Mode protection: Preset preemption respects Work Mode and Typing Guard restrictions,
providing clear toast feedback if switching is blocked.
Realistic Biomechanical Click Physics & Dynamic Fadeout (1–160 CPS)
-
Dynamic Hold Time Safety Cap: Click hold duration is capped at
$\le 40%$ of the cycle interval
(with hardware floor$\ge 2\text{ ms}$ ), completely eliminating the infinite-hold microswitch lockup
bug at 25+ CPS. -
Single Click with Human Fatigue Model: Injects an asymmetrical biological hesitation tail
(15% probability of$+5..=18\text{ ms}$ stretch) and natural Bates B3 jitter. -
Butterfly Click Engine: Two-finger biomechanics alternating between Index (
$\text{Bates}(72 \pm 12)\text{ ms}$ )
and Middle finger ($\text{Bates}(84 \pm 15)\text{ ms}$ ) with 35% probability of microswitch bounce
($\text{Bates}(20 \pm 6)\text{ ms}$ ), producing a genuine bimodal latency distribution. -
Drag Click Engine (Debounce = 0ms burst + adaptive deadzone): Simulates microswitch bounce
at 0 ms debounce with instant 90–110 CPS bursts during the stroke (0.15–0.30 s), while adaptively
scaling the hand return deadzone to preserve the exact average CPS chosen on the slider. -
Dynamic Fadeout (Uncapped Overdrive to 160 CPS): As the CPS slider is increased towards 40,
60, 100 and 160 CPS, biomechanical delays seamlessly compress without artificial ceilings. Every
mode cleanly reaches full 160 CPS. -
RAII
MouseHoldGuard: All click dispatch paths utilize an RAII guard with aDropimplementation
that guaranteesmouse_upis called under all circumstances (normal loop exit, emergency break,
timer stop, or panic).
Native system tray (no tray crate)
- Closing the window now hides it to the tray ("Minimize to Tray on Close", on by default):
the clicker keeps running, the backend keeps its hooks, and only the tray menu can quit.
"Start Minimized to Tray" boots straight into the icon. - The icon is hand-rolled Win32 —
Shell_NotifyIconWon a hidden top-level window with its
own message pump and aTrackPopupMenuExmenu (Open / Start-Stop clicking / Show-Hide HUD /
Quit). It deliberately does not use thetray-iconfeature: that pullsmudaand
re-importscomctl32!TaskDialogIndirect, the symbol behind the0xC0000139load failure. - The icon re-registers itself after
explorer.exerestarts, and a second launch surfaces the
existing window instead of starting another process.
Deep sleep in the tray (opt-in)
- New "Deep Sleep in Tray" setting: instead of hiding the window, the whole interface is
released while the app sits in the tray. The window is rebuilt on the next tray click and
appears already rendered — no white rectangle, no flash. - Measured with
scripts/measure-ram.ps1: ~120 MB with the interface alive versus
5.3 MB in deep sleep (0% CPU at rest in tray, the six WebView2 helpers go from 6 processes to 0). - Guarded by design: never while clicking, while a macro plays or while a recording runs; the
page is asked to persist pending state first (bounded wait); and the page's own unload fallback
cannot kill the backend during the suspension.
Auto-stop timers
- Unified auto-stop mechanism owned solely by the Rust scheduler hot loop.
- "Stop after" takes ms / sec / min / hour with fractional values and unit persistence.
- "Stop at" accurately targets the next occurrence.
- Exactly one auto-stop mode is active at a time (
engine.stop_mode:durationorwallclock).
Included Hardening & Fixes (v1.2.0 Update)
All improvements below are shipped in the updated v1.2.0 binaries:
- Zero-idle tray suspension (0% CPU and 5.3 MB RAM): all production frontend
setInterval
timers are gated byeveryVisible()so DOM timers freeze while hidden;watcher.rsis relaxed
to a 5 s cadence; the app-profile poller is gated by a cheapconfig.jsonmtime check
(eliminating ~40 disk writes and JSON parses per minute in idle); and the global hotkey loop parks
in a true kernel sleep via Win32MsgWaitForMultipleObjects(QS_ALLINPUT). - A settings save can no longer wipe your profile, and the previous state is always kept.
A save issued before the config had finished loading wrote the page's factory defaults over the
real profile — language, engine, hotkeys and every custom preset. Saving is now refused until the
config is hydrated (with a toast and awarnlog line instead of silence), and the previous
config.jsonis copied toconfig.json.bak-<epoch>before the first write of each session, so
a wrong-but-valid write is recoverable by hand. - Recorded macros are never dropped. A recording was written to disk only if the WebView2 host
name dialog returned a non-empty answer: a suppressed dialog (or a plain cancel) discarded the
whole recording silently — no file, no log, no toast. The recording is now persisted the moment
it stops (empty ones excepted) and renamed in place afterwards. - Closing to the tray no longer pulls the window back. A UIPI-blocked click or a hidden page's
focus request could callshow()on a window the user had put in the tray; the alert is now a
tray balloon. Click start also stopped re-creating the ripple-overlay WebView while the interface
lives in the tray — that alone respawned the whole WebView2 tree (~6msedgewebview2.exe,
~120 MB). - The tray switch tells the truth. It displays and writes
minimize_to_trayand
deep_sleep_to_traytogether; a factory profile used to show it ON while deep sleep was off, so
closing freed nothing. A skipped deep sleep now logs its reason atwarn. - Quit destroys every window — satellites first,
mainlast — and is re-entry safe, instead of
hiding the main window and leaving its WebView2 helpers alive. - The window state is remembered: "Reopen the window as I closed it" replays the last state on
the next start, and an explicit "Start Minimized to Tray" still wins. - The preset library moved to its own file (
presets.json, with its own last-good snapshot and
quarantine). It used to be one array insideconfig.json, so saving a single preset rewrote the
whole config — and a stale copy of that config could replace the entire library. Migration is
one-way and one-time, and an existingpresets.jsonis never overwritten by the conf...
NanoClick v1.1.1
NanoClick v1.1.1
A stability release. It closes the failure class where the window renders while the
interface is completely dead, it makes a broken frontend impossible to ship, and it
finishes two guards that were not doing their job: Auto-Pause on Focus Loss and the
Typing Guard delay field.
Boot guard
- New
src/boot_guard.js- a classic script loaded first on every page, so it
survives a fatalSyntaxErrorinsidemain.js(a module cannot trap its own
parse failure). - Catches uncaught errors, unhandled rejections and 404s of scripts/CSS, and writes
them to%TEMP%\nanoclick_web.logat error level - written in release builds too.
The old traps were debug-gated and logged nothing at all in a release. - Boot watchdog: if a page never signals readiness within about 3 s, it logs
[BOOT] MODULE DID NOT EXECUTE (SyntaxError class)and shows a banner instead of
failing silently. - Rate-limited (dedupe plus a 20-report cap) so a repeating error cannot flood the log.
showToast()/ensureToastHost()were referenced by 13 call sites but never
defined: 10 were silent no-ops, 3 threwReferenceErrormid-flow. Now implemented.
Auto-Pause on Focus Loss - now actually enforces
- The setting was inert. Starting from the UI button armed the guard with no baseline, so
it never paused, and every periodic stats save disarmed the live session. - Our own window is now a real baseline: leaving NanoClick mid-run (Alt+Tab, click-away,
Win key, system toast) stops the clicker, returning to it never does, and an
unresolvable foreground (lock screen, elevated window) fails open. - The stop is immediate: a dedicated watcher thread reads the foreground directly every
~50 ms instead of waiting for the click loop to wake at the end of its interval, and
the toast names the process that took focus. A disabled guard starts no thread at all.
Typing Guard delay field
- The "ms" spinner wrote nothing to the config: number inputs emit
inputevents when
the arrows are clicked and onlychangewas handled. Editing the value and pressing
Start now persists it (coalesced write, the trailing value always lands on disk).
Fixed
- Windows test gate: the unittest binary imported
comctl32.dll!TaskDialogIndirect, a
symbol that exists only in comctl32 v6, which an application manifest must activate.
A test binary built from the library is not a binary target, so it carried no manifest
and Windows killed the process with0xC0000139 STATUS_ENTRYPOINT_NOT_FOUNDbefore
the first test ran. The dependency that pulled that symbol in is no longer enabled, so
the tests need no manifest at all - and the shipped app no longer imports it either. - Re-recorded macros refresh the macro list immediately (
reRecordMacroignored its
onChangecallback). - Every boot step reports its elapsed time in the log (
stage()measured it and
threw it away). scripts\check-js-syntax.ps1no longer swallows its ownSyntaxErroroutput on
Windows PowerShell 5.1.scripts/sign_release.ps1was unparseable (a tail frommake_release.ps1had been
pasted into it), which broke the SIGN stage of every release.
Frontend lint: 53 warnings to 0
oxlint (a Rust binary, no node_modules, about 20 ms for all of src/) is a hard
gate: 0 errors / 0 warnings. The baseline was fixed, not silenced. An unreachable
legacy stats fallback was removed, dead bindings were removed, shadowed identifiers
were renamed, and per-call closures were hoisted. 27 empty catch blocks are allowed
by config, and 3 onclick replacement slots carry a documented exception.
Release pipeline
release.ps1Stage 0 (PREFLIGHT) runs the grammar check, the lint gate and the Rust
tests beforecargo tauri build, which never runs tests by itself.- Release builds default to
-j 1: the profile is LTO pluscodegen-units = 1, and a
2-job build has already died withrustc-LLVM ERROR: out of memory. - Release notes and the GitHub API body are now sent as UTF-8, so non-ASCII characters
no longer arrive as?. scripts\set-version.ps1bumps the version insrc-tauri/Cargo.toml,
src-tauri/tauri.conf.json, the README badge and the doc headers, parses the new JSON
before writing it, and then verifies that both files tauri reads carry the new value.
The pipeline itself only reads the version, so a tag that does not match the files
stops the run.- Two new gate tripwires:
common-controls-v6must stay disabled in the tauri feature
list (it pulls the comctl32 v6-only import into the test host), andtauri.conf.json
must parse and agree withCargo.tomlon the version.
Verified
- Frontend grammar: 11/11 scripts parse (module and classic modes).
- Frontend lint: 0 errors / 0 warnings.
- Test suite: 207/207 passed (188 unit + 19 integration; 7
physical_filtered out).
Install
Download NanoClick_1.1.1_x64-setup.exe below (no admin rights required), or
NanoClick-portable.exe for a zero-install run. Existing installs update themselves
through the built-in signed updater.
The installer is not code-signed with a Windows certificate yet, so SmartScreen may
warn about an unknown publisher. The updater artifacts are Minisign-signed and
verified by the app before install.
License
PolyForm Noncommercial License 1.0.0 with NanoClick Additional Terms. Free for
personal gaming, streaming and research. See
LICENSE.md.
NanoClick v1.0.0-beta
? NanoClick v1.0.0-beta
Welcome to the first public beta release of NanoClick!
Key Highlights
- Ultra-lightweight & High Performance: Built with Tauri 2 and pure Rust. Production setup is only ~3.7 MB and runs with ~15 MB RAM usage.
- Smart Click Engine: Single, double, and hold click modes with human jitter (timing & cursor randomization) and CPS telemetry up to 160 CPS.
- Smart Guard & Typing Protection: Automatically stops clicking when you type text and handles Windows UIPI elevation seamlessly.
- Macro Recorder & Visual Editor: Real-time recording with RDP (Ramer-Douglas-Peucker) curve compression and manual block sequencer.
- Tri-Lingual Interface: Full Ukrainian, English, and Russian localization.
- Built-in Auto-Updater: Cryptographically verified updates via Minisign.
?? License
Released under the PolyForm Noncommercial License 1.0.0 with NanoClick Additional Terms. Free for personal gaming, streaming, and research. See LICENSE.md for complete details.
?? Installation
Download and run NanoClick_1.0.0-beta_x64-setup.exe below. No administrative privileges required.
Or use NanoClick-portable.exe for zero-install portable execution.