unsnooze 1.16.2 → 1.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/package.json +1 -1
- package/src/dashboard/run.js +46 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.16.3 — 2026-08-22
|
|
4
|
+
|
|
5
|
+
- **A dashboard left open overnight no longer runs the machine out of memory.**
|
|
6
|
+
([#16](https://github.com/saaranshM/unsnooze/issues/16)) `unsnooze status` in
|
|
7
|
+
a spare pane is exactly what it is for, and after nine or ten hours it was
|
|
8
|
+
dying on *"FATAL ERROR: Ineffective mark-compacts near heap limit — JavaScript
|
|
9
|
+
heap out of memory"*. The dashboard itself was innocent: React's development
|
|
10
|
+
build files a `performance.measure()` entry for every render, commit and
|
|
11
|
+
`setState` — the performance track it publishes for devtools — and Node
|
|
12
|
+
buffers user-timing entries forever with nothing to drain them. The status tab
|
|
13
|
+
repaints about three times a second (a one-second data tick plus the
|
|
14
|
+
450ms logo animation), each repaint left roughly 40 entries behind, and a
|
|
15
|
+
night of that walks into Node's 2GB heap ceiling. Measured at ~12kB retained
|
|
16
|
+
per render, surviving a forced garbage collection. The dashboard now drains
|
|
17
|
+
that buffer while it is mounted; heap use went from climbing without limit to
|
|
18
|
+
flat. Nothing else in unsnooze records user timing, and no other command
|
|
19
|
+
renders long enough to have been affected.
|
|
20
|
+
|
|
21
|
+
- **Every release now creates its GitHub release automatically.** They were
|
|
22
|
+
written by hand afterwards, so 1.16.2 shipped to npm and to the website while
|
|
23
|
+
the repository's Releases tab still showed 1.16.1. The publish workflow now
|
|
24
|
+
creates the release from that version's CHANGELOG section, and waits for the
|
|
25
|
+
npm registry to actually serve the new version before rebuilding the website —
|
|
26
|
+
`npm publish` returns before the registry does, which is why 1.16.2's own
|
|
27
|
+
changelog entry was briefly invisible on unsnooze.dev.
|
|
28
|
+
|
|
3
29
|
## 1.16.2 — 2026-08-22
|
|
4
30
|
|
|
5
31
|
- **A herdr revival now opens a tab in your project's workspace, not a new
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unsnooze",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.3",
|
|
4
4
|
"description": "Automatically resume Claude Code, Codex CLI, Grok, Qwen Code, Kimi CLI, OpenCode, and Antigravity sessions when 5-hour or weekly usage limits reset—on macOS, Linux and Windows, with or without a terminal multiplexer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/src/dashboard/run.js
CHANGED
|
@@ -62,6 +62,41 @@ export function installMouseCleanup(stdout = process.stdout) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// React's development build records a `performance.measure()` entry for every
|
|
66
|
+
// render, commit and setState — the React Performance Track it publishes for
|
|
67
|
+
// devtools. Node buffers user-timing entries and nothing ever drains that
|
|
68
|
+
// buffer, so on a long-lived dashboard it IS the leak: the status tab repaints
|
|
69
|
+
// roughly three times a second (a 1s data tick plus the 450ms logo animation),
|
|
70
|
+
// each repaint leaves ~40 entries behind, and an overnight `unsnooze status`
|
|
71
|
+
// walked into Node's 2GB heap ceiling and aborted (#16). Measured at ~12 kB
|
|
72
|
+
// retained per render, surviving a forced GC.
|
|
73
|
+
//
|
|
74
|
+
// The entries cannot be capped (Node caps resource timing, not user timing) and
|
|
75
|
+
// switching React to its production build would mean setting NODE_ENV for the
|
|
76
|
+
// whole CLI before anything imports React. Draining is the narrow fix: nothing
|
|
77
|
+
// in unsnooze reads user timing, and the sweep lives exactly as long as the
|
|
78
|
+
// render does.
|
|
79
|
+
export const TIMING_SWEEP_MS = 5_000;
|
|
80
|
+
|
|
81
|
+
export function sweepReactTiming(perf = globalThis.performance) {
|
|
82
|
+
try {
|
|
83
|
+
perf?.clearMeasures?.();
|
|
84
|
+
perf?.clearMarks?.();
|
|
85
|
+
} catch { /* a runtime with no user timing has nothing to leak */ }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Returns the stop function. The timer is unref'd: draining a buffer is never
|
|
89
|
+
// a reason to hold the process open.
|
|
90
|
+
export function startTimingSweep({ intervalMs = TIMING_SWEEP_MS, perf } = {}) {
|
|
91
|
+
sweepReactTiming(perf);
|
|
92
|
+
const timer = setInterval(() => sweepReactTiming(perf), intervalMs);
|
|
93
|
+
timer.unref?.();
|
|
94
|
+
return () => {
|
|
95
|
+
clearInterval(timer);
|
|
96
|
+
sweepReactTiming(perf);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
65
100
|
export function shouldUseDashboard({
|
|
66
101
|
force = null,
|
|
67
102
|
json = false,
|
|
@@ -87,13 +122,18 @@ export async function runDashboard({ tab = 'status' } = {}) {
|
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
installMouseCleanup(process.stdout);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
const stopTimingSweep = startTimingSweep();
|
|
126
|
+
try {
|
|
127
|
+
const instance = render(h(App, { initialTab: tab, mouseEnabled: getConfig('mouse') !== false }), {
|
|
128
|
+
exitOnCtrlC: true,
|
|
129
|
+
// Full-screen: separate buffer like vim / htop / less — original scrollback restored on quit
|
|
130
|
+
alternateScreen: true,
|
|
131
|
+
});
|
|
95
132
|
|
|
96
|
-
|
|
133
|
+
await instance.waitUntilExit();
|
|
134
|
+
} finally {
|
|
135
|
+
stopTimingSweep();
|
|
136
|
+
}
|
|
97
137
|
// Belt and braces: normal quit also clears modes (provider already did).
|
|
98
138
|
process.stdout.write(MOUSE_DISABLE_ALL);
|
|
99
139
|
return 0;
|