kandown 0.28.1 → 0.30.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,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0 — 2026-07-21 — "Fable UI Themes"
4
+
5
+ - **Added**: **Customizable JSON Theme Engine (`KandownTheme`)** — upgraded Kandown's skin system from 5 hardcoded HSL presets to an open, JSON-driven theme engine with dynamic inheritance (`base`), fallback safety, and runtime custom theme registration (`registerCustomThemes`).
6
+ - **Added**: **8 Curated Built-in Presets** — introduced 8 design presets in `src/lib/theme.ts`:
7
+ - ⚡ **Vercel**: Monochrome contrast, tight typography, 6px radius, sharp 1px borders, zero shadows.
8
+ - 📐 **Linear**: Dark-first (`#08090A`), electric violet (`#5E6AD2`), 8px radius, glass header, soft popover shadows.
9
+ - 🧡 **Claude**: Editorial warmth, oat crème (`#F5F1EA`), terracotta accent (`#D97757`), Charter serif display font, 12px radius, soft diffuse shadows.
10
+ - 🍎 **Apple**: Translucent materials (`rgba(255,255,255,0.72)` + backdrop blur), SF system font, 14px squircle radius, multi-layer elevated shadows.
11
+ - 💳 **Stripe**: Indigo night (`#0A0A23`), blurple accent (`#635BFF`), 8px radius, soft colored glow shadows.
12
+ - 📄 **Paper**: Notion-style calm studio, `#F7F6F3` light, `#191919` dark, pastel tags, 4px radius, no shadows.
13
+ - 🧛 **Catppuccin**: Official Mocha (`#1E1E2E` mauve `#CBA6F7`) and Latte palettes.
14
+ - 🖥️ **Terminal**: CRT homage to TUI, `#0C0C0C` background, `#33FF66` phosphor green text, SF Mono everywhere, 0px radius.
15
+ - **Added**: **Theme Gallery with Live Mini-Board Previews** — replaced static color swatches in Settings with an interactive gallery of `ThemePreviewCard` components rendering isolated live 3-column kanban board previews using each theme's unique HSL tokens, radius, fonts, and shadows.
16
+ - **Added**: **Visual Theme Editor Modal (`ThemeCustomizerModal`)** — added a visual customization drawer for creating and tweaking custom JSON themes with sliders for border radius (0–24px), shadow elevation levels (`none`, `soft`, `elevated`, `dramatic`), density, motion scale, glassmorphism, and live HSL token pickers.
17
+ - **Added**: **Live WCAG 2.1 Contrast Ratio Calculator** — added real-time relative luminance calculation in the theme editor showing an active WCAG AA contrast ratio badge (warning when ratio < 4.5:1).
18
+ - **Added**: **JSON Import & Export** — added direct theme JSON view/edit panel with 1-click clipboard copy for sharing and storing custom themes.
19
+ - **Added**: **`ui.customThemes` Config Support** — custom user themes are automatically persisted in `kandown.json` under `ui.customThemes` and loaded seamlessly on startup.
20
+ - **Changed**: **Appearance CSS Tokens & Design Refinements** — introduced `--radius`, `--shadow-*`, `--font-display`, `--motion-scale`, `--card-blur` variables into `:root` and mapped them to `tailwind.config.js` (`borderRadius`, `fontFamily.display`). Refined base body typography scale to 15px, unified focus rings (`:focus-visible`), and added smooth light/dark root transitions.
21
+
22
+ ## 0.29.0 — 2026-07-20 — "Global Daemon Refresh"
23
+
24
+ - **Added**: **Global daemon refresh command** — added `kandown daemon refresh-all`, which scans every active local Kandown daemon across the configured port range, refreshes each open project's `.kandown/kandown.html`, and restarts any daemon running an older CLI version.
25
+ - **Added**: **Forced daemon reboot aliases** — added `kandown daemon restart-all` and `kandown daemon refresh-all --force` for cases where every open Kandown daemon should be restarted, even when versions already match.
26
+ - **Changed**: **Safer post-update refresh flow** — refactored the existing auto-update refresh path to reuse the same daemon scan/restart helper, keeping automatic package-update behavior aligned with the new explicit global command.
27
+ - **Changed**: **Daemon CLI documentation** — updated CLI help and the README command table to surface `daemon refresh-all` as the recommended fix when old project sessions remain open after a global update.
28
+ - **Fixed**: **Outdated open project daemons after global updates** — users no longer need to manually find and restart each project when a global Kandown update succeeds but already-running daemons continue serving older code in memory.
29
+
3
30
  ## 0.28.1 — 2026-07-20 — "Robust Global Auto-Updater"
4
31
 
5
32
  - **Fixed**: **Global package manager auto-update commands** — updated `checkForUpdate()` and `cmdUpdate()` to run `pnpm add -g kandown@latest` (instead of `pnpm install -g kandown`), with fallback chain across `pnpm`, `npm`, `yarn`, and `bun`.
package/README.md CHANGED
@@ -91,6 +91,7 @@ The web daemon stays alive after you quit the TUI so the browser keeps working.
91
91
  | `kandown daemon status` | Show this project's web daemon status |
92
92
  | `kandown daemon start` | Start/reconnect this project's web daemon |
93
93
  | `kandown daemon stop` | Stop this project's web daemon |
94
+ | `kandown daemon refresh-all` | Refresh open projects and restart outdated daemons with the current CLI version |
94
95
  | `kandown update` | Update `kandown.html` to latest |
95
96
  | `kandown list` \| `show` \| `create` \| `move` \| `assign` \| `commit` | One-shot task commands (see below) |
96
97
  | `kandown tasks` | Full help for the one-shot task commands |
package/bin/kandown.js CHANGED
@@ -574,7 +574,7 @@ ${c.bold}Commands:${c.reset}
574
574
  ${c.cyan}board${c.reset} Open the interactive kanban board in the terminal
575
575
  ${c.cyan}init${c.reset} Initialize .kandown/ in the current directory
576
576
  ${c.cyan}settings${c.reset} Open the settings TUI
577
- ${c.cyan}daemon${c.reset} Manage the per-project web daemon (start|stop|status)
577
+ ${c.cyan}daemon${c.reset} Manage web daemons (start|stop|status|refresh-all)
578
578
  ${c.cyan}update${c.reset} Update kandown.html to the latest version
579
579
  ${c.cyan}list${c.reset} List tasks ${c.dim}(-s status, -a assignee, -t tag, -p priority, --json)${c.reset}
580
580
  ${c.cyan}show${c.reset} Print a task's raw file content
@@ -594,6 +594,7 @@ ${c.bold}Examples:${c.reset}
594
594
  ${c.dim}$${c.reset} npx kandown --port 3000 ${c.dim}# use a specific web UI port${c.reset}
595
595
  ${c.dim}$${c.reset} npx kandown board ${c.dim}# board TUI only${c.reset}
596
596
  ${c.dim}$${c.reset} npx kandown daemon stop ${c.dim}# stop this project's web daemon${c.reset}
597
+ ${c.dim}$${c.reset} npx kandown daemon refresh-all ${c.dim}# refresh/restart outdated open daemons${c.reset}
597
598
  ${c.dim}$${c.reset} npx kandown init
598
599
  ${c.dim}$${c.reset} npx kandown init --path docs/kanban
599
600
  ${c.dim}$${c.reset} npx kandown init --no-agents
@@ -1946,35 +1947,62 @@ function refreshKandownHtml(kandownDir) {
1946
1947
  return false;
1947
1948
  }
1948
1949
 
1949
- async function refreshRunningProjectHtml() {
1950
+ function daemonScanPorts() {
1950
1951
  const ports = [];
1951
1952
  for (let port = START_PORT_RANGE; port <= END_PORT_RANGE; port++) {
1952
1953
  if (!isBrowserUnsafePort(port)) ports.push(port);
1953
1954
  }
1955
+ return ports;
1956
+ }
1954
1957
 
1955
- const daemons = await Promise.all(ports.map(port => fetchDaemonInfo(port)));
1956
- const kandownDirs = new Set(
1957
- daemons
1958
- .map(daemon => daemon?.kandownDir)
1959
- .filter(dir => typeof dir === 'string' && dir.length > 0)
1960
- );
1958
+ async function listRunningDaemons() {
1959
+ const daemons = await Promise.all(daemonScanPorts().map(port => fetchDaemonInfo(port)));
1960
+ const byDir = new Map();
1961
+ for (const daemon of daemons) {
1962
+ if (!daemon || typeof daemon.kandownDir !== 'string' || daemon.kandownDir.length === 0) continue;
1963
+ if (!byDir.has(daemon.kandownDir)) byDir.set(daemon.kandownDir, daemon);
1964
+ }
1965
+ return [...byDir.values()];
1966
+ }
1961
1967
 
1968
+ async function refreshRunningDaemons({ forceRestart = false } = {}) {
1969
+ const daemons = await listRunningDaemons();
1962
1970
  const currentVersion = getCurrentVersion();
1963
- let refreshed = 0;
1964
- for (const kandownDir of kandownDirs) {
1971
+ const results = {
1972
+ found: daemons.length,
1973
+ refreshed: 0,
1974
+ restarted: 0,
1975
+ skipped: 0,
1976
+ failed: [],
1977
+ };
1978
+
1979
+ for (const daemon of daemons) {
1980
+ const kandownDir = daemon.kandownDir;
1965
1981
  try {
1966
- if (refreshKandownHtml(kandownDir)) refreshed++;
1982
+ if (refreshKandownHtml(kandownDir)) results.refreshed++;
1967
1983
  const current = await getDaemonStatus(kandownDir);
1968
- if (current.running) {
1969
- const daemonVersion = current.metadata?.version || null;
1970
- if (!daemonVersion || (currentVersion && semverGt(currentVersion, daemonVersion) > 0)) {
1971
- await stopDaemon(kandownDir);
1972
- await startDaemon(kandownDir, current.metadata?.port || null);
1973
- }
1984
+ const daemonVersion = daemon.version || current.metadata?.version || null;
1985
+ const shouldRestart = forceRestart || !daemonVersion || (currentVersion && semverGt(currentVersion, daemonVersion) > 0);
1986
+ if (!shouldRestart) {
1987
+ results.skipped++;
1988
+ continue;
1974
1989
  }
1975
- } catch { /* best-effort: one locked project must not block restart */ }
1990
+ const preferredPort = daemon.port || current.metadata?.port || null;
1991
+ await stopDaemon(kandownDir);
1992
+ const restarted = await startDaemon(kandownDir, preferredPort);
1993
+ if (!restarted.running) throw new Error('daemon failed to restart');
1994
+ results.restarted++;
1995
+ } catch (e) {
1996
+ results.failed.push({ kandownDir, error: e.message });
1997
+ }
1976
1998
  }
1977
- return refreshed;
1999
+
2000
+ return results;
2001
+ }
2002
+
2003
+ async function refreshRunningProjectHtml() {
2004
+ const results = await refreshRunningDaemons({ forceRestart: false });
2005
+ return results.refreshed;
1978
2006
  }
1979
2007
 
1980
2008
  async function waitForDaemon(kandownDir, timeoutMs = 8000) {
@@ -2978,6 +3006,21 @@ function detectStaleKandown(port, currentKandownDir) {
2978
3006
 
2979
3007
  async function cmdDaemon(rawArgs) {
2980
3008
  const [subcommand = 'status', ...rest] = rawArgs;
3009
+
3010
+ if (subcommand === 'refresh-all' || subcommand === 'restart-all') {
3011
+ const forceRestart = rest.includes('--force') || subcommand === 'restart-all';
3012
+ const results = await refreshRunningDaemons({ forceRestart });
3013
+ success(`Scanned ${results.found} running daemon${results.found === 1 ? '' : 's'}`);
3014
+ info(`Refreshed ${results.refreshed} kandown.html bundle${results.refreshed === 1 ? '' : 's'}`);
3015
+ info(`${forceRestart ? 'Restarted' : 'Restarted outdated'} ${results.restarted} daemon${results.restarted === 1 ? '' : 's'}`);
3016
+ if (!forceRestart && results.skipped > 0) info(`Skipped ${results.skipped} already-current daemon${results.skipped === 1 ? '' : 's'}`);
3017
+ if (results.failed.length > 0) {
3018
+ for (const failure of results.failed) warn(`${failure.kandownDir}: ${failure.error}`);
3019
+ process.exit(1);
3020
+ }
3021
+ return;
3022
+ }
3023
+
2981
3024
  const { kandownDir } = ensureKandownDir(rest);
2982
3025
  const preferredPort = parsePort(parseArgs(rest).port);
2983
3026
 
@@ -3039,7 +3082,7 @@ async function cmdDaemon(rawArgs) {
3039
3082
  }
3040
3083
 
3041
3084
  err(`Unknown daemon command: ${subcommand}`);
3042
- log(` Use ${c.cyan}kandown daemon start|stop|status${c.reset}`);
3085
+ log(` Use ${c.cyan}kandown daemon start|stop|status|refresh-all${c.reset}`);
3043
3086
  process.exit(1);
3044
3087
  }
3045
3088