From c1c96df19f5d5e9d2ac61d874ffad16484ec937d Mon Sep 17 00:00:00 2001 From: jennyf19 <19942418+jennyf19@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:15:14 -0700 Subject: [PATCH] address review: validate task ids, seeded counter status, carry-on-done, live region - Validate task ids (^[A-Za-z0-9_-]+$) and dedupe before interpolating into data-* attributes, so a malformed configured/persisted id can't break out of an attribute (template + sample). - Seeded counters (current value >0 and below goal) now render as 'in progress', not 'to do'. - Hide the 'not today' carryover control on done tasks; it was flipping a completed task to carried and dropping it from the recap's Done tally. - #eodmsg is now role=status aria-live=polite so screen-reader users hear save/copy results. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 07e720ee-ca02-419e-9adb-300738b6fc76 --- skills/daily-focus-board/assets/board.template.html | 9 +++++---- skills/daily-focus-board/examples/sample-board.html | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/skills/daily-focus-board/assets/board.template.html b/skills/daily-focus-board/assets/board.template.html index 747f8509..ddffae7b 100644 --- a/skills/daily-focus-board/assets/board.template.html +++ b/skills/daily-focus-board/assets/board.template.html @@ -250,7 +250,7 @@ -

+

progress saves automatically in this browser · 🔥 built with Ember

@@ -275,7 +275,7 @@ let state={counters:(raw.counters||{}),t:(raw.t||{}),day:(raw.day||[]),brain:(ra let dragId=null, renderedIds=[]; function ensureTaskState(){tasks.forEach(t=>{ if(typeof t.goal==="number"){if(state.counters[t.id]===undefined)state.counters[t.id]=(t.start||0);} else if(!state.t[t.id])state.t[t.id]={status:"todo",notes:[],carried:false}; else if(state.t[t.id].carried===undefined)state.t[t.id].carried=false; });} -function rebuildTasks(){tasks=baseTasks.concat(state.added||[]);ensureTaskState();if(state.focus&&!tasks.some(t=>t.id===state.focus))state.focus=null;} +function rebuildTasks(){const seen={};tasks=baseTasks.concat(state.added||[]).filter(t=>{const id=t&&t.id;if(!validId(id)||seen[id])return false;seen[id]=1;return true;});ensureTaskState();if(state.focus&&!tasks.some(t=>t.id===state.focus))state.focus=null;} rebuildTasks(); const cardsEl=document.getElementById("cards"),feedEl=document.getElementById("feed"),brainEl=document.getElementById("brain"); @@ -303,7 +303,7 @@ function fmt(ms){return new Date(ms).toLocaleTimeString(undefined,{hour:"numeric function esc(s){return (s||"").replace(/[&<>]/g,m=>({"&":"&","<":"<",">":">"}[m]));} const LABEL={todo:"to do",doing:"in progress",done:"done"}; function isCounter(t){return typeof t.goal==="number";} -function statusOf(t){if(isCounter(t)){const v=state.counters[t.id];return v>=t.goal?"done":(v>(t.start||0)?"doing":"todo");}return state.t[t.id].status;} +function statusOf(t){if(isCounter(t)){const v=state.counters[t.id];return v>=t.goal?"done":(v>0?"doing":"todo");}return state.t[t.id].status;} function carriedOf(t){return !isCounter(t)&&state.t[t.id].carried;} function dueStr(t){ @@ -355,7 +355,7 @@ function render(){ ${rmHtml} ${notes.length?`
${notes.map((n,i)=>`
${fmt(n.t)}${esc(n.txt)}
`).join("")}
`:""} -
+
${st!=="done"?``:""}
${metaHtml}`; cardsEl.appendChild(c); } @@ -419,6 +419,7 @@ 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 validId(id){return typeof id==="string"&&/^[A-Za-z0-9_-]+$/.test(id);} 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 54b3f82c..0c32546f 100644 --- a/skills/daily-focus-board/examples/sample-board.html +++ b/skills/daily-focus-board/examples/sample-board.html @@ -260,7 +260,7 @@ -

+

progress saves automatically in this browser · 🔥 built with Ember

@@ -285,7 +285,7 @@ let state={counters:(raw.counters||{}),t:(raw.t||{}),day:(raw.day||[]),brain:(ra let dragId=null, renderedIds=[]; function ensureTaskState(){tasks.forEach(t=>{ if(typeof t.goal==="number"){if(state.counters[t.id]===undefined)state.counters[t.id]=(t.start||0);} else if(!state.t[t.id])state.t[t.id]={status:"todo",notes:[],carried:false}; else if(state.t[t.id].carried===undefined)state.t[t.id].carried=false; });} -function rebuildTasks(){tasks=baseTasks.concat(state.added||[]);ensureTaskState();if(state.focus&&!tasks.some(t=>t.id===state.focus))state.focus=null;} +function rebuildTasks(){const seen={};tasks=baseTasks.concat(state.added||[]).filter(t=>{const id=t&&t.id;if(!validId(id)||seen[id])return false;seen[id]=1;return true;});ensureTaskState();if(state.focus&&!tasks.some(t=>t.id===state.focus))state.focus=null;} rebuildTasks(); const cardsEl=document.getElementById("cards"),feedEl=document.getElementById("feed"),brainEl=document.getElementById("brain"); @@ -313,7 +313,7 @@ function fmt(ms){return new Date(ms).toLocaleTimeString(undefined,{hour:"numeric function esc(s){return (s||"").replace(/[&<>]/g,m=>({"&":"&","<":"<",">":">"}[m]));} const LABEL={todo:"to do",doing:"in progress",done:"done"}; function isCounter(t){return typeof t.goal==="number";} -function statusOf(t){if(isCounter(t)){const v=state.counters[t.id];return v>=t.goal?"done":(v>(t.start||0)?"doing":"todo");}return state.t[t.id].status;} +function statusOf(t){if(isCounter(t)){const v=state.counters[t.id];return v>=t.goal?"done":(v>0?"doing":"todo");}return state.t[t.id].status;} function carriedOf(t){return !isCounter(t)&&state.t[t.id].carried;} function dueStr(t){ @@ -365,7 +365,7 @@ function render(){ ${rmHtml} ${notes.length?`
${notes.map((n,i)=>`
${fmt(n.t)}${esc(n.txt)}
`).join("")}
`:""} -
+
${st!=="done"?``:""}
${metaHtml}`; cardsEl.appendChild(c); } @@ -429,6 +429,7 @@ 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 validId(id){return typeof id==="string"&&/^[A-Za-z0-9_-]+$/.test(id);} 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";}}