@@ -418,6 +418,7 @@ function tagOf(t){return state.tag[t.id]!==undefined?state.tag[t.id]:(t.tag||"")
function setQuad(id,v){state.quad[id]=v;save();render();}
function setTag(id,v){state.tag[id]=v;save();render();}
function escAttr(s){return esc(s).replace(/"/g,""");}
+function safeCls(s){return (s||"").replace(/[^a-zA-Z0-9_-]/g,"");}
function reorder(from,targetId){if(!from||from===targetId)return;let ids=renderedIds.slice();const fromI=ids.indexOf(from),tgtI=ids.indexOf(targetId);if(fromI<0||tgtI<0)return;ids.splice(fromI,1);let ins=ids.indexOf(targetId);if(fromI{const ca=carriedOf(a)?1:0,cb=carriedOf(b)?1:0;if(ca!==cb)return ca-cb;const ra=(rank[quadOf(a)]===undefined?4:rank[quadOf(a)]),rb=(rank[quadOf(b)]===undefined?4:rank[quadOf(b)]);if(ra!==rb)return ra-rb;return orderIndex(a.id)-orderIndex(b.id);}).map(t=>t.id);state.order=ids;save();render();}
function updateOverload(){const el=document.getElementById("overload");if(!el)return;const live=tasks.filter(t=>!carriedOf(t)&&statusOf(t)!=="done").length;if(live>9){el.style.display="block";el.textContent=`That's ${live} active tasks for today — more than a focus board loves. Consider carrying a few to tomorrow (⤳ not today), or use Focus mode to take one at a time. No pressure — this is a nudge, not a rule.`;}else{el.style.display="none";}}
diff --git a/skills/daily-focus-board/examples/sample-board.html b/skills/daily-focus-board/examples/sample-board.html
index 697c2a76..54b3f82c 100644
--- a/skills/daily-focus-board/examples/sample-board.html
+++ b/skills/daily-focus-board/examples/sample-board.html
@@ -341,7 +341,7 @@ function render(){
+``
- +`
@@ -428,6 +428,7 @@ function tagOf(t){return state.tag[t.id]!==undefined?state.tag[t.id]:(t.tag||"")
function setQuad(id,v){state.quad[id]=v;save();render();}
function setTag(id,v){state.tag[id]=v;save();render();}
function escAttr(s){return esc(s).replace(/"/g,""");}
+function safeCls(s){return (s||"").replace(/[^a-zA-Z0-9_-]/g,"");}
function reorder(from,targetId){if(!from||from===targetId)return;let ids=renderedIds.slice();const fromI=ids.indexOf(from),tgtI=ids.indexOf(targetId);if(fromI<0||tgtI<0)return;ids.splice(fromI,1);let ins=ids.indexOf(targetId);if(fromI{const ca=carriedOf(a)?1:0,cb=carriedOf(b)?1:0;if(ca!==cb)return ca-cb;const ra=(rank[quadOf(a)]===undefined?4:rank[quadOf(a)]),rb=(rank[quadOf(b)]===undefined?4:rank[quadOf(b)]);if(ra!==rb)return ra-rb;return orderIndex(a.id)-orderIndex(b.id);}).map(t=>t.id);state.order=ids;save();render();}
function updateOverload(){const el=document.getElementById("overload");if(!el)return;const live=tasks.filter(t=>!carriedOf(t)&&statusOf(t)!=="done").length;if(live>9){el.style.display="block";el.textContent=`That's ${live} active tasks for today — more than a focus board loves. Consider carrying a few to tomorrow (⤳ not today), or use Focus mode to take one at a time. No pressure — this is a nudge, not a rule.`;}else{el.style.display="none";}}
diff --git a/skills/daily-focus-board/references/customize.md b/skills/daily-focus-board/references/customize.md
index cb9d0dcd..3bcde66d 100644
--- a/skills/daily-focus-board/references/customize.md
+++ b/skills/daily-focus-board/references/customize.md
@@ -3,8 +3,8 @@
## Theming
Colors live in the `:root` CSS block of `assets/board.template.html`
(`--ember`, `--good`, `--urgent`, backgrounds). The look is warm/dark by default. Tag colors:
-`new` (green), `deadline` (red), `career` (purple) — add your own by copying a `.tag.`
-rule and passing that name as a task's `tagc`.
+`new` (green), `deadline` (red), `career` (purple) — add your own by copying a `.tagedit.`
+rule and passing that name as a task's `tagc` (use a plain class name: letters, digits, `-`, `_`).
## v2 — file-backed state (closes the agent loop)
v1 stores progress in the browser (`localStorage`), which the agent can't read back. To let
diff --git a/skills/daily-focus-board/scripts/serve-board.ps1 b/skills/daily-focus-board/scripts/serve-board.ps1
index fe5f0e85..04cb2fc6 100644
--- a/skills/daily-focus-board/scripts/serve-board.ps1
+++ b/skills/daily-focus-board/scripts/serve-board.ps1
@@ -7,8 +7,17 @@ param(
)
$full = Join-Path $Dir $File
if (-not (Test-Path $full)) { Write-Error "Board not found: $full"; exit 1 }
-Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden
+try {
+ if (Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue) {
+ Write-Error "Port $Port is already in use — choose another with -Port or stop the existing server."; exit 1
+ }
+} catch { } # Get-NetTCPConnection may be unavailable on some platforms; skip the pre-check there.
+$proc = Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden -PassThru
Start-Sleep -Seconds 2
+if ($proc.HasExited) {
+ Write-Error "The server exited on startup (code $($proc.ExitCode)) — is Python installed and the port free?"; exit 1
+}
$url = "http://localhost:$Port/$File"
Write-Host "Focus board: $url"
+Write-Host "Server PID $($proc.Id) — stop it with: Stop-Process -Id $($proc.Id)"
Start-Process $url # opens in default browser; in a Copilot-app session, open a browser canvas to this URL instead