scenri 0.4.4 → 0.4.5

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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.5](https://github.com/tonygorb/Scenri/compare/v0.4.4...v0.4.5) (2026-08-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * check npm every six hours for real, and catch up after sleep ([9efb633](https://github.com/tonygorb/Scenri/commit/9efb6338b8a3ffbcb0c0a0f167492510f33d8bca))
9
+ * keep the update float off a source checkout ([35d5269](https://github.com/tonygorb/Scenri/commit/35d5269f46d20044921878c2068fdc7495e6c27d))
10
+ * keep the update float off a source checkout ([d2c129e](https://github.com/tonygorb/Scenri/commit/d2c129e4a567ea4220f503118872087f9e2516cc))
11
+ * let an idle tab hear about a ready update within the half hour ([ea0088d](https://github.com/tonygorb/Scenri/commit/ea0088d363d8d709ddb01549717c4fed1aa06aab))
12
+ * say every six hours everywhere the update check is disclosed ([78204c8](https://github.com/tonygorb/Scenri/commit/78204c8cfc26c3a9d856d399808f25396fe6084c))
13
+
3
14
  ## [0.4.4](https://github.com/tonygorb/Scenri/compare/v0.4.3...v0.4.4) (2026-08-23)
4
15
 
5
16
 
package/README.md CHANGED
@@ -95,7 +95,7 @@ pnpm dev # starts the server on 127.0.0.1:4747
95
95
  - **Iteration is the product.** A version tree, not a prompt box. Branch, compare, keep the winners.
96
96
  - **Your brands are files, not hostages.** `.brand` is an open, documented format under a permissive license. Email one to a client. Any tool can adopt it.
97
97
  - **Your AI, your cost.** Bring your own Codex CLI session or an API key. Experiments cost raw API price, or nothing at all on a local session. No credits that burn on a miss.
98
- - **Local first, and it means it.** No account, no telemetry, no upload. The server binds to your machine only. Scenri makes exactly two requests on its own behalf: a daily version-number check against npm so updates can announce themselves (and, when one is found, the download of that release from npm, staged locally until you choose to restart), and a one-time download of the library imagery archive, cached locally forever after. Nothing about you or your work is ever sent, and both turn off: in Settings or `SCENRI_NO_UPDATE_CHECK=1` for the first, `SCENRI_NO_CONTENT_FETCH=1` for the second ([how updates work](https://github.com/tonygorb/scenri/blob/main/docs/updates.md)).
98
+ - **Local first, and it means it.** No account, no telemetry, no upload. The server binds to your machine only. Scenri makes exactly two requests on its own behalf: a version-number check against npm every six hours so updates can announce themselves (and, when one is found, the download of that release from npm, staged locally until you choose to restart), and a one-time download of the library imagery archive, cached locally forever after. Nothing about you or your work is ever sent, and both turn off: in Settings or `SCENRI_NO_UPDATE_CHECK=1` for the first, `SCENRI_NO_CONTENT_FETCH=1` for the second ([how updates work](https://github.com/tonygorb/scenri/blob/main/docs/updates.md)).
99
99
 
100
100
  ## Engines
101
101
 
@@ -4,9 +4,13 @@ import { existsSync, rmSync, mkdirSync, readFileSync, renameSync } from 'fs';
4
4
  import { join, dirname } from 'path';
5
5
 
6
6
  // src/update/check.ts
7
- var CACHE_MS = 24 * 60 * 60 * 1e3;
7
+ var CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
8
+ var TICK_MS = 15 * 60 * 1e3;
9
+ var JITTER_RATIO = 0.2;
8
10
  var FORCE_COOLDOWN_MS = 60 * 1e3;
9
11
  var TIMEOUT_MS = 5e3;
12
+ var MIN_INTERVAL_MS = 60 * 60 * 1e3;
13
+ var MIN_TICK_MS = 60 * 1e3;
10
14
  function resolveRegistry(env = process.env, override) {
11
15
  return (override ?? env.SCENRI_REGISTRY ?? "https://registry.npmjs.org").replace(/\/+$/, "");
12
16
  }
@@ -32,6 +36,7 @@ async function fetchDistTagLatest(name, opts = {}) {
32
36
  return { latest: null, error: String(err?.message ?? err) };
33
37
  }
34
38
  }
39
+ var isReleaseTriplet = (v) => /^\d+\.\d+\.\d+$/.test(v);
35
40
  function triplet(v) {
36
41
  const m = v.match(/^(\d+)\.(\d+)\.(\d+)$/);
37
42
  return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
@@ -50,7 +55,18 @@ function createUpdateChecker(deps) {
50
55
  const env = deps.env ?? process.env;
51
56
  const now = deps.now ?? Date.now;
52
57
  const log = deps.log ?? console.log;
58
+ const random = deps.random ?? Math.random;
53
59
  const registry = resolveRegistry(env, deps.registry);
60
+ const onNpm = registry.startsWith("https://registry.npmjs.org");
61
+ const envMs = (key) => {
62
+ const n = Number(env[key]);
63
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
64
+ };
65
+ const rawInterval = envMs("SCENRI_UPDATE_INTERVAL_MS") ?? CHECK_INTERVAL_MS;
66
+ const intervalMs = onNpm ? Math.max(rawInterval, MIN_INTERVAL_MS) : rawInterval;
67
+ const rawTick = envMs("SCENRI_UPDATE_TICK_MS") ?? TICK_MS;
68
+ const tickMs = onNpm ? Math.max(rawTick, MIN_TICK_MS) : rawTick;
69
+ let jitterMs = random() * JITTER_RATIO * intervalMs;
54
70
  const cached = () => {
55
71
  const at = deps.store.getSetting("update.checkedAt");
56
72
  return {
@@ -72,6 +88,7 @@ function createUpdateChecker(deps) {
72
88
  if (res.error) return { ...cached(), error: res.error };
73
89
  deps.store.setSetting("update.latest", res.latest ?? "");
74
90
  deps.store.setSetting("update.checkedAt", String(now()));
91
+ jitterMs = random() * JITTER_RATIO * intervalMs;
75
92
  const result = cached();
76
93
  onResultFn?.(result);
77
94
  return result;
@@ -80,7 +97,7 @@ function createUpdateChecker(deps) {
80
97
  if (!enabled() && !force) return cached();
81
98
  if (inflight) return inflight;
82
99
  const prior = cached();
83
- const fresh = prior.checkedAt !== null && now() - prior.checkedAt < CACHE_MS;
100
+ const fresh = prior.checkedAt !== null && now() - prior.checkedAt < intervalMs + jitterMs;
84
101
  if (force) {
85
102
  if (now() - lastForceAt < FORCE_COOLDOWN_MS) return prior;
86
103
  lastForceAt = now();
@@ -94,7 +111,7 @@ function createUpdateChecker(deps) {
94
111
  }
95
112
  function schedule() {
96
113
  setTimeout(() => void check(), 1e4).unref();
97
- setInterval(() => void check(), CACHE_MS).unref();
114
+ setInterval(() => void check(), tickMs).unref();
98
115
  }
99
116
  return {
100
117
  enabled,
@@ -229,6 +246,6 @@ async function stageVersion(deps) {
229
246
  return { ok: true, version, entry: entryOf(deps.home, deps.pkg, version) };
230
247
  }
231
248
 
232
- export { classify, createUpdateChecker, fetchDistTagLatest, findNpm, stageVersion };
233
- //# sourceMappingURL=chunk-WIIQZN3K.js.map
234
- //# sourceMappingURL=chunk-WIIQZN3K.js.map
249
+ export { classify, createUpdateChecker, fetchDistTagLatest, findNpm, isReleaseTriplet, stageVersion };
250
+ //# sourceMappingURL=chunk-FYQ5BAFA.js.map
251
+ //# sourceMappingURL=chunk-FYQ5BAFA.js.map
@@ -1,4 +1,4 @@
1
- import { stageVersion, fetchDistTagLatest, findNpm } from './chunk-WIIQZN3K.js';
1
+ import { stageVersion, fetchDistTagLatest, findNpm } from './chunk-FYQ5BAFA.js';
2
2
  import { readMeta } from './chunk-Y3ZPBPLP.js';
3
3
  import { defaultHome, newestStaged, compareSemver } from './chunk-4MAFHYAD.js';
4
4
 
@@ -63,5 +63,5 @@ async function runUpdateCommand(opts) {
63
63
  }
64
64
 
65
65
  export { runUpdateCommand };
66
- //# sourceMappingURL=cli-PBDPZQ4G.js.map
67
- //# sourceMappingURL=cli-PBDPZQ4G.js.map
66
+ //# sourceMappingURL=cli-KSKENGIR.js.map
67
+ //# sourceMappingURL=cli-KSKENGIR.js.map
package/dist/index.js CHANGED
@@ -70,7 +70,7 @@ try {
70
70
  await (await import('./serve.js')).serve();
71
71
  break;
72
72
  case "update": {
73
- const { runUpdateCommand } = await import('./cli-PBDPZQ4G.js');
73
+ const { runUpdateCommand } = await import('./cli-KSKENGIR.js');
74
74
  process.exit(await runUpdateCommand(command));
75
75
  break;
76
76
  }
package/dist/serve.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { portBusyLines } from './chunk-WGIZJXNE.js';
2
- import { createUpdateChecker, classify, findNpm, stageVersion } from './chunk-WIIQZN3K.js';
2
+ import { createUpdateChecker, classify, isReleaseTriplet, findNpm, stageVersion } from './chunk-FYQ5BAFA.js';
3
3
  import { readMeta, repoSlug } from './chunk-Y3ZPBPLP.js';
4
4
  import { compareSemver, newestStaged } from './chunk-4MAFHYAD.js';
5
5
  import { networkInterfaces, homedir, tmpdir } from 'os';
@@ -7022,6 +7022,21 @@ function registerImageRoutes(app, deps) {
7022
7022
 
7023
7023
  // src/release/notes.data.ts
7024
7024
  var RELEASES = [
7025
+ {
7026
+ version: "0.4.5",
7027
+ date: "2026-08-23",
7028
+ title: "New versions find you while Scenri runs.",
7029
+ sections: [
7030
+ {
7031
+ heading: "Updates",
7032
+ body: "A running Scenri now checks for new versions every six hours instead of relying on the next launch, and a laptop that slept through a check catches up within minutes of waking. Open tabs hear about a downloaded update within the half hour, or the moment you return to them."
7033
+ },
7034
+ {
7035
+ heading: "Fixes",
7036
+ body: "Running from a source checkout no longer shows a floating update notice whose button had nothing to do. The checkout keeps its quiet badge and the pull-and-rebuild note in Settings."
7037
+ }
7038
+ ]
7039
+ },
7025
7040
  {
7026
7041
  version: "0.4.4",
7027
7042
  date: "2026-08-23",
@@ -7339,7 +7354,9 @@ function registerUpdateRoutes(app, deps) {
7339
7354
  // (bump-minor-pre-major), so only a real major asks for attention.
7340
7355
  attention: kind === "major",
7341
7356
  checkedAt: r.checkedAt,
7342
- notesUrl: r.latest && ghSlug ? `https://github.com/${ghSlug}/releases/tag/v${r.latest}` : null,
7357
+ // Only a clean release triple becomes a URL: a stray prerelease on the
7358
+ // latest tag must not leak into link surfaces any more than into staging.
7359
+ notesUrl: r.latest && ghSlug && isReleaseTriplet(r.latest) ? `https://github.com/${ghSlug}/releases/tag/v${r.latest}` : null,
7343
7360
  error: apply.phase === "error" ? apply.error : r.error,
7344
7361
  canApply: blockReason() === null,
7345
7362
  blockReason: blockReason(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenri",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "author": "Tony Gorb <hello@scenri.co>",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",