kandown 0.22.0 → 0.23.0

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.23.0 — 2026-07-20 — "Theme Switcher & Auto Update"
4
+
5
+ - **Added**: **Animated Theme Switcher** — added a shadcn-style three-option theme switcher under `src/components/ui/theme-switcher-1.tsx` with system, light, and dark choices, lucide icons, and Motion layout animation.
6
+ - **Changed**: **Theme Mode UX** — replaced the Appearance settings theme dropdown and the old header two-state toggle with the new animated switcher. The component keeps Kandown's existing `auto`, `light`, and `dark` config values so existing `.kandown/kandown.json` files remain compatible.
7
+ - **Changed**: **Framework Integration** — adapted the provided `next-themes` component to Kandown's Vite/Zustand theme pipeline instead of adding a Next.js-only provider. Theme changes continue to persist through the existing project config and apply via the local CSS token engine.
8
+ - **Fixed**: **Auto-Updater Retry Blocking** — failed auto-update installs are no longer cached as a successful update check. Previously, if npm answered the registry but the install failed, Kandown could suppress retries for 24 hours, making auto-update feel blocked across open projects.
9
+ - **Fixed**: **Multi-Project Auto-Update Refresh** — after a successful in-place global update, Kandown now scans active local daemons and refreshes `kandown.html` for every open project, so two simultaneously open boards do not remain stuck on different web UI bundles.
10
+ - **Fixed**: **Installed Version Verification** — the auto-updater now verifies the installed Kandown CLI version instead of only checking the npm registry version, making successful restart decisions based on the actual local binary.
11
+
3
12
  ## 0.22.0 — 2026-07-20 — "Sectioned List View"
4
13
 
5
14
  - **Added**: **Sectioned List View** — rebuilt the web List view so board columns such as Backlog, Todo, In Progress, Review, and Done are displayed as horizontal task sections stacked vertically. This keeps the dense list/table workflow while making the List view match the mental model of the Kanban board in the opposite orientation.
package/bin/kandown.js CHANGED
@@ -145,6 +145,32 @@ function resolveKandownBin() {
145
145
  return null;
146
146
  }
147
147
 
148
+ async function readInstalledKandownVersion(targetVersion) {
149
+ const localVersion = getCurrentVersion();
150
+ if (localVersion && semverGt(localVersion, targetVersion) >= 0) return localVersion;
151
+
152
+ const bin = resolveKandownBin();
153
+ if (!bin) return localVersion;
154
+
155
+ return await new Promise((resolveVersion) => {
156
+ const child = spawn(bin, ['--version'], {
157
+ timeout: 5000,
158
+ stdio: ['pipe', 'pipe', 'pipe'],
159
+ env: { ...process.env, KANDOWN_NO_UPDATE: '1' },
160
+ detached: false,
161
+ });
162
+ let stdout = '';
163
+ child.stdout.on('data', (d) => { stdout += d; });
164
+ child.stderr.on('data', () => {});
165
+ child.on('error', () => resolveVersion(localVersion));
166
+ child.on('close', (code) => {
167
+ if (code !== 0) return resolveVersion(localVersion);
168
+ const match = stdout.trim().match(/v?(\d+\.\d+\.\d+(?:-[\w.-]+)?)/);
169
+ resolveVersion(match ? match[1] : localVersion);
170
+ });
171
+ });
172
+ }
173
+
148
174
  /**
149
175
  * 📖 Compares two semver strings (major.minor.patch, optional -prerelease).
150
176
  * Prerelease-safe: "0.18.0-beta.1" no longer parses as NaN — the numeric
@@ -260,11 +286,15 @@ async function checkForUpdate(argv = process.argv) {
260
286
  });
261
287
  });
262
288
 
263
- // 📖 A successful registry answer (even "up to date") arms the 24h throttle.
264
- // Offline / registry-down does NOT — we retry on the next interactive run.
265
- if (latest) rememberUpdateCheck();
289
+ if (!latest) return; // offline / registry-down retry on the next interactive run
266
290
 
267
- if (!latest || semverGt(current, latest) >= 0) return; // up to date or offline
291
+ if (semverGt(current, latest) >= 0) {
292
+ // 📖 Up-to-date registry answers can be throttled. Failed update attempts
293
+ // below are intentionally NOT cached, otherwise one broken install would
294
+ // silence auto-update retries for 24h across every open project.
295
+ rememberUpdateCheck();
296
+ return;
297
+ }
268
298
 
269
299
  tuiDone('⚡', `Update available: ${c.dim}kandown ${current}${c.reset} → ${c.green}${latest}${c.reset}`);
270
300
 
@@ -305,22 +335,9 @@ async function checkForUpdate(argv = process.argv) {
305
335
  return;
306
336
  }
307
337
 
308
- // 📖 Step 4: Verify the update actually landed.
309
- const postVersion = await new Promise((resolve) => {
310
- const child = spawn('npm', ['view', 'kandown', 'version'], {
311
- timeout: 5000,
312
- stdio: ['pipe', 'pipe', 'pipe'],
313
- detached: false,
314
- });
315
- let stdout = '';
316
- child.stdout.on('data', (d) => { stdout += d; });
317
- child.stderr.on('data', () => {});
318
- child.on('error', () => resolve(null));
319
- child.on('close', (code) => {
320
- if (code !== 0) return resolve(null);
321
- resolve(stdout.trim().replace(/^"|"$/g, '') || null);
322
- });
323
- });
338
+ // 📖 Step 4: Verify the installed CLI, not just the npm registry, because
339
+ // `npm view kandown version` only proves a version exists remotely.
340
+ const postVersion = await readInstalledKandownVersion(latest);
324
341
 
325
342
  if (!postVersion || semverGt(postVersion, latest) < 0) {
326
343
  tuiDone('✗', `${c.yellow}Update did not apply${c.reset} — continuing with current version`);
@@ -329,6 +346,18 @@ async function checkForUpdate(argv = process.argv) {
329
346
  return;
330
347
  }
331
348
 
349
+ rememberUpdateCheck();
350
+
351
+ // 📖 If the running package directory has been replaced in-place (normal
352
+ // global install), refresh every currently open project daemon, not only the
353
+ // project that triggered the update. This keeps two simultaneously open
354
+ // Kandown boards from getting stuck on different kandown.html versions.
355
+ const localVersionAfterInstall = getCurrentVersion();
356
+ if (localVersionAfterInstall && semverGt(localVersionAfterInstall, latest) >= 0) {
357
+ const refreshed = await refreshRunningProjectHtml();
358
+ if (refreshed > 0) info(`Refreshed ${refreshed} open project${refreshed === 1 ? '' : 's'}`);
359
+ }
360
+
332
361
  tuiDone('✓', `${c.green}Updated to v${postVersion}${c.reset} — restarting…`);
333
362
  printVersionChangelog(postVersion);
334
363
  printBreakingChangeNotices(current, postVersion);
@@ -1785,6 +1814,28 @@ function refreshKandownHtml(kandownDir) {
1785
1814
  return false;
1786
1815
  }
1787
1816
 
1817
+ async function refreshRunningProjectHtml() {
1818
+ const ports = [];
1819
+ for (let port = START_PORT_RANGE; port <= END_PORT_RANGE; port++) {
1820
+ if (!isBrowserUnsafePort(port)) ports.push(port);
1821
+ }
1822
+
1823
+ const daemons = await Promise.all(ports.map(port => fetchDaemonInfo(port)));
1824
+ const kandownDirs = new Set(
1825
+ daemons
1826
+ .map(daemon => daemon?.kandownDir)
1827
+ .filter(dir => typeof dir === 'string' && dir.length > 0)
1828
+ );
1829
+
1830
+ let refreshed = 0;
1831
+ for (const kandownDir of kandownDirs) {
1832
+ try {
1833
+ if (refreshKandownHtml(kandownDir)) refreshed++;
1834
+ } catch { /* best-effort: one locked project must not block restart */ }
1835
+ }
1836
+ return refreshed;
1837
+ }
1838
+
1788
1839
  async function waitForDaemon(kandownDir, timeoutMs = 8000) {
1789
1840
  // 📖 Detect daemon startup via TCP probe + process liveness rather than HTTP
1790
1841
  // fetch. The freshly spawned child is known to be ours, so once its process