Skip to content

ember: add daily-focus-board skill (EF/neurodivergent-friendly) - #2452

Open
jennyf19 wants to merge 22 commits into
github:mainfrom
jennyf19:add-daily-focus-board-skill
Open

ember: add daily-focus-board skill (EF/neurodivergent-friendly)#2452
jennyf19 wants to merge 22 commits into
github:mainfrom
jennyf19:add-daily-focus-board-skill

Conversation

@jennyf19

Copy link
Copy Markdown
Contributor

What

Adds daily-focus-board, a warm, executive-function-friendly daily focus board you run by talking to your AI partner (Ember), and registers it in the ember plugin.

Renders from a single self-contained HTML template (no build, no backend; progress persists in localStorage). Designed to be universal (works standalone for anyone) and non-medicalizing (focus-friendly for everyone; never diagnoses).

Features

  • Tasks: status (to-do -> in progress -> done) with timestamped progress notes, plus numeric-goal counters (pages, pomodoros, reps...).
  • Arrival check-in (above / in-between / below the line) that suggests a daily mantra keyed to how you arrived.
  • Add / reorder / relabel tasks live; Eisenhower priority labels (Do first / Schedule / Delegate / Later) with a legend + "sort by priority".
  • Focus mode, kind "not today" carryover (no overdue-shaming), a brain-dump box, a gentle overload nudge, reduced-motion toggle, live clock.
  • End-of-day recap: download or copy a Markdown summary (paste it back to your partner to journal / plan tomorrow).

The highest-leverage part is the executive-function-friendly behavior section in SKILL.md — how the partner shows up (body-double, shrink the first step, one next thing, celebrate starting, never shame an incomplete).

Files

  • skills/daily-focus-board/SKILL.md, assets/board.template.html, scripts/serve-board.ps1, examples/sample-board.html, references (tutorial.md, neurodivergent-design.md, customize.md).
  • plugins/ember/.github/plugin/plugin.json — registers the skill; version 1.0.0 -> 1.1.0.
  • plugins/ember/README.md — adds a Components row.

Notes

  • Design references are cited and link-checked (CHADD, Simply Psychology, MDN, W3C WCAG, Conscious Leadership, Asana/Todoist, self-compassion.org).
  • Verified with a headless DOM test (render + all interactions) and a JS syntax gate.

Opening as a draft for final review.

@github-actions github-actions Bot added new-submission PR adds at least one new contribution plugin PR touches plugins skills PR touches skills needs-review:MEDIUM Contributor reputation check flagged MEDIUM risk labels Jul 27, 2026
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🔒 PR Risk Scan Results

Scanned 16 changed file(s).

Severity Count
🔴 High 0
🟠 Medium 1
ℹ️ Info 1
Severity Rule File Line Match
🟠 unpinned-version-indicator extensions/daily-focus-board/package.json 8 "@​​github/copilot-sdk": "latest"
ℹ️ skill-script-touched skills/daily-focus-board/scripts/serve-board.ps1 1 skills/daily-focus-board/scripts/serve-board.ps1
Skipped non-text or missing files
  • extensions/daily-focus-board/assets/preview.png

This is an automated soft-gate report. Findings indicate review targets and do not block merge by themselves.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🔍 Vally Lint Results

✅ All checks passed

Scope Checked
Skills 1
Agents 0
Total 1
Severity Count
❌ Errors 0
⚠️ Warnings 0
ℹ️ Advisories 0

Summary

Level Finding
ℹ️ ✅ daily-focus-board (2/2 checks passed)
ℹ️ ✓ [spec-compliance] All 1 skill(s) are spec-compliant.
ℹ️ ✓ spec-compliance: All spec checks passed.
ℹ️ ✓ [valid-refs] All file references across 1 skill(s) are valid.
ℹ️ ✓ valid-refs: All file references resolve to existing files within the skill directory.
ℹ️ 1 skill(s) linted, 1 passed
Full linter output
### Linting skills/daily-focus-board
✅ daily-focus-board (2/2 checks passed)
    ✓ [spec-compliance] All 1 skill(s) are spec-compliant.
        ✓ spec-compliance: All spec checks passed.
    ✓ [valid-refs] All file references across 1 skill(s) are valid.
        ✓ valid-refs: All file references resolve to existing files within the skill directory.

1 skill(s) linted, 1 passed

@jennyf19
jennyf19 marked this pull request as ready for review July 27, 2026 20:50
@jennyf19
jennyf19 requested a review from aaronpowell as a code owner July 27, 2026 20:50
Copilot AI review requested due to automatic review settings July 27, 2026 20:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an executive-function-friendly daily focus board skill to Ember.

Changes:

  • Adds the self-contained interactive board and sample.
  • Documents usage, customization, and design principles.
  • Registers the skill in Ember 1.1.0.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
skills/daily-focus-board/SKILL.md Defines board behavior and usage.
skills/daily-focus-board/assets/board.template.html Implements the interactive board.
skills/daily-focus-board/examples/sample-board.html Provides a populated example.
skills/daily-focus-board/scripts/serve-board.ps1 Serves and opens boards locally.
skills/daily-focus-board/references/tutorial.md Documents the daily workflow.
skills/daily-focus-board/references/neurodivergent-design.md Explains design principles and sources.
skills/daily-focus-board/references/customize.md Covers customization and future integrations.
plugins/ember/README.md Lists the new component.
plugins/ember/.github/plugin/plugin.json Registers the skill and bumps Ember’s version.
Comments suppressed due to low confidence (8)

skills/daily-focus-board/assets/board.template.html:186

  • The arrival choices are clickable spans with no keyboard semantics, so keyboard and assistive-technology users cannot perform the check-in. Render them as buttons (and expose the selected state with aria-pressed when updating them).
      <span class="ci-opt" data-checkin="below">🌧️ below the line</span>
      <span class="ci-opt" data-checkin="mid">⛅ in between</span>
      <span class="ci-opt" data-checkin="above">☀️ above the line</span>

skills/daily-focus-board/assets/board.template.html:347

  • Both status controls are non-focusable elements wired only with onclick, making the core to-do → in-progress → done action unavailable from a keyboard. Use button type="button" elements and retain the existing data-cyc binding.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>
        <div class="emoji">${t.emoji||"•"}</div>
        <div class="body"><div class="title">${esc(t.title)}</div>${t.sub?`<div class="sub">${esc(t.sub)}</div>`:""}${dh?`<div class="due ${dh.cls}" data-duefor="${t.id}">${dh.txt}</div>`:""}</div>
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>

skills/daily-focus-board/examples/sample-board.html:355

  • The sample's status-card emoji is interpolated into innerHTML without escaping, matching the injection issue in the template. Treat it as text.
        <div class="emoji">${t.emoji||"•"}</div>

skills/daily-focus-board/examples/sample-board.html:186

  • These sample controls are click-only spans, so keyboard-only users cannot toggle Focus mode or reduced motion. Keep the example accessible and synchronized with the template by using semantic buttons.
        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

skills/daily-focus-board/examples/sample-board.html:196

  • The sample's arrival choices are also non-focusable clickable spans. Convert them to buttons and update aria-pressed from renderCheckin() so the selected state is available to assistive technology.
      <span class="ci-opt" data-checkin="below">🌧️ below the line</span>
      <span class="ci-opt" data-checkin="mid">⛅ in between</span>
      <span class="ci-opt" data-checkin="above">☀️ above the line</span>

skills/daily-focus-board/examples/sample-board.html:357

  • The sample duplicates the template's keyboard blocker for the primary status transition: neither clickable element can receive keyboard focus. Use semantic buttons and keep the runtime binding unchanged.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>
        <div class="emoji">${t.emoji||"•"}</div>
        <div class="body"><div class="title">${esc(t.title)}</div>${t.sub?`<div class="sub">${esc(t.sub)}</div>`:""}${dh?`<div class="due ${dh.cls}" data-duefor="${t.id}">${dh.txt}</div>`:""}</div>
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>

skills/daily-focus-board/examples/sample-board.html:442

  • The sample's fallback reports success even when execCommand("copy") returns false. Check that return value before invoking the copied-success callback.
function fallbackCopy(md,cb){try{const ta=document.createElement("textarea");ta.value=md;document.body.appendChild(ta);ta.select();document.execCommand("copy");ta.remove();cb&&cb();}catch(e){eodMsg("couldn't copy here — try download 💾");}}

skills/daily-focus-board/examples/sample-board.html:476

  • The sample continuously repaints a full-screen canvas even with no active particles, wasting CPU and battery for a page intended to remain open. Mirror the template fix: start animation on burst() and stop when the particle list empties.
function loop(){if(!cx)return;cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}
render(); loop();

Comment thread skills/daily-focus-board/SKILL.md
Comment thread plugins/ember/.github/plugin/plugin.json Outdated
Comment thread skills/daily-focus-board/SKILL.md Outdated
Comment thread skills/daily-focus-board/references/tutorial.md Outdated
Comment thread skills/daily-focus-board/scripts/serve-board.ps1 Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/SKILL.md
Copilot AI review requested due to automatic review settings July 27, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (5)

skills/daily-focus-board/assets/board.template.html:334

  • Config-derived values are interpolated into innerHTML without escaping: emoji is raw here, and id is raw in the surrounding attributes/selectors. A crafted task can inject markup/script or break rendering. Build these nodes with DOM APIs, or consistently escape text/attributes and validate/escape IDs (including selector use).
      c.innerHTML=`<div class="top"><div class="emoji">${t.emoji||"🎯"}</div>
        <div class="body"><div class="title">${esc(t.title)}</div>
          <div class="sub"><b style="color:var(--ink)">${v.toLocaleString()}</b> / ${t.goal.toLocaleString()} ${esc(unit)} · ${pct}%${st==="done"?" — done 🎉":""}</div>
          ${dh?`<div class="due ${dh.cls}" data-duefor="${t.id}">${dh.txt}</div>`:""}</div>
        <span class="pill ${st}">${LABEL[st]}</span>
        <button class="fbtn" data-focus="${t.id}" title="focus on this">🎯</button>${rmHtml}</div>

skills/daily-focus-board/examples/sample-board.html:344

  • This standalone example repeats the template's unsafe innerHTML interpolation of config-derived emoji and id values. Customized sample data can inject markup/script or invalidate selectors. Apply the same text/attribute escaping and ID validation as in the source template.
      c.innerHTML=`<div class="top"><div class="emoji">${t.emoji||"🎯"}</div>
        <div class="body"><div class="title">${esc(t.title)}</div>
          <div class="sub"><b style="color:var(--ink)">${v.toLocaleString()}</b> / ${t.goal.toLocaleString()} ${esc(unit)} · ${pct}%${st==="done"?" — done 🎉":""}</div>
          ${dh?`<div class="due ${dh.cls}" data-duefor="${t.id}">${dh.txt}</div>`:""}</div>
        <span class="pill ${st}">${LABEL[st]}</span>
        <button class="fbtn" data-focus="${t.id}" title="focus on this">🎯</button>${rmHtml}</div>

skills/daily-focus-board/assets/board.template.html:432

  • document.execCommand("copy") returns false when copying is denied without necessarily throwing, but this always calls the success callback and tells the user the recap was copied. Check the boolean result and show the failure message when it is false; also remove the temporary textarea in finally.
function fallbackCopy(md,cb){try{const ta=document.createElement("textarea");ta.value=md;document.body.appendChild(ta);ta.select();document.execCommand("copy");ta.remove();cb&&cb();}catch(e){eodMsg("couldn't copy here — try download 💾");}}

skills/daily-focus-board/SKILL.md:1

  • The new skill is absent from docs/README.skills.md, so it will not appear in the repository's generated skill catalog. Run npm run build and commit the generated documentation.
---

plugins/ember/.github/plugin/plugin.json:4

  • The checked-in .github/plugin/marketplace.json still advertises Ember version 1.0.0, so this 1.1.0 release is not reflected in the generated marketplace metadata. Run npm run build and commit the regenerated output.
  "version": "1.1.0",

Comment thread skills/daily-focus-board/SKILL.md Outdated
Comment thread skills/daily-focus-board/SKILL.md
Comment thread skills/daily-focus-board/SKILL.md Outdated
Comment thread skills/daily-focus-board/SKILL.md Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html
Comment thread skills/daily-focus-board/scripts/serve-board.ps1 Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 21:18
@jennyf19
jennyf19 force-pushed the add-daily-focus-board-skill branch from 6e1b5fc to c514d58 Compare July 27, 2026 21:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 10 comments.

Comments suppressed due to low confidence (6)

skills/daily-focus-board/SKILL.md:80

  • This command binds Python's HTTP server to every network interface by default, exposing the user's board and any other files in the served directory to the local network. Bind explicitly to loopback because this board may contain personal tasks and notes.
opening the file path directly:

skills/daily-focus-board/references/tutorial.md:22

  • The example server command listens on all interfaces by default, making the personal board reachable from other machines on the local network. Keep the documented default private by binding it to loopback.
1. Ask Ember to make the board. It serves the folder (e.g. `python -m http.server 8790 --bind 127.0.0.1`) and gives

skills/daily-focus-board/scripts/serve-board.ps1:12

  • http.server binds to every interface unless told otherwise, so this helper exposes the board directory to the local network despite only printing a localhost URL. Bind the server to loopback and use the matching URL.
Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden
Start-Sleep -Seconds 2
$url = "http://localhost:$Port/$File"

skills/daily-focus-board/assets/board.template.html:447

  • Turning on Reduce motion does not stop confetti already running in the canvas requestAnimationFrame loop; it only prevents future bursts and CSS animation. Cancel the active frame and clear particles/canvas when the toggle becomes enabled.
document.getElementById("rmchip").onclick=()=>{state.rm=!state.rm;document.body.classList.toggle("rm",state.rm);document.getElementById("rmchip").classList.toggle("on",state.rm);document.getElementById("rmchip").setAttribute("aria-pressed",state.rm?"true":"false");save();};

skills/daily-focus-board/assets/board.template.html:438

  • When dateKey is omitted, this filename uses a UTC date while the board's storage key and displayed day use local time. Around local midnight the downloaded recap can therefore be named for the previous or next day; reuse todayLocal() here.
function downloadRecap(){const md=dayRecap();try{const blob=new Blob([md],{type:"text/markdown"});const url=URL.createObjectURL(blob);const a=document.createElement("a");a.href=url;a.download="focus-"+(CFG.dateKey||new Date().toISOString().slice(0,10))+".md";document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);eodMsg("saved a recap to your downloads 💾");}catch(e){eodMsg("couldn't auto-download here — use copy instead 📋");}}

skills/daily-focus-board/examples/sample-board.html:448

  • When dateKey is omitted, this filename uses a UTC date while the sample's storage key and displayed day use local time. Around local midnight the recap can be named for the wrong day; reuse todayLocal() here.
function downloadRecap(){const md=dayRecap();try{const blob=new Blob([md],{type:"text/markdown"});const url=URL.createObjectURL(blob);const a=document.createElement("a");a.href=url;a.download="focus-"+(CFG.dateKey||new Date().toISOString().slice(0,10))+".md";document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);eodMsg("saved a recap to your downloads 💾");}catch(e){eodMsg("couldn't auto-download here — use copy instead 📋");}}

Comment thread skills/daily-focus-board/SKILL.md Outdated
Comment thread skills/daily-focus-board/assets/board.template.html
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 21:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 13 comments.

Comments suppressed due to low confidence (2)

skills/daily-focus-board/assets/board.template.html:328

  • Reordering is exposed only through a non-focusable HTML drag handle, so keyboard-only users cannot perform the advertised arbitrary reorder operation. Add keyboard-operable move controls (or a focusable grip with documented key handlers) and announce the updated position.
    const metaHtml=`<div class="cardmeta"><span class="grip" draggable="true" data-grip="${t.id}" title="drag to reorder">⠿</span>`

skills/daily-focus-board/examples/sample-board.html:338

  • The standalone sample likewise exposes reorder only through a non-focusable drag handle. Keyboard-only users need move controls or a focusable grip with key handlers and an announced result.
    const metaHtml=`<div class="cardmeta"><span class="grip" draggable="true" data-grip="${t.id}" title="drag to reorder">⠿</span>`

Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html
Comment thread skills/daily-focus-board/examples/sample-board.html
Comment thread skills/daily-focus-board/SKILL.md Outdated
Comment thread skills/daily-focus-board/references/neurodivergent-design.md Outdated
Comment thread skills/daily-focus-board/references/neurodivergent-design.md Outdated
Executive-function-friendly daily focus board (self-contained HTML) you run by
talking to Ember. Registers it in the ember plugin (1.1.0) and regenerates the
skills index + marketplace.json.

Review fixes: emoji XSS escaping; keyboard a11y (semantic buttons + aria, plus
up/down move controls); tagc label colors; local-date storage key + recap
date/filename; guarded execCommand and localStorage; confetti animates only
while active and stops on reduce-motion; loopback serve bind + quoted dir;
JSON config injection with '<' escaped; minute rounding; reorder-to-end; clear
stale focus; counter goal guard; and doc corrections (id charset, sample
reference, counter contract, option-b, frontmatter length).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07e720ee-ca02-419e-9adb-300738b6fc76
Copilot AI review requested due to automatic review settings July 27, 2026 22:13
@jennyf19
jennyf19 force-pushed the add-daily-focus-board-skill branch from 278c0bd to 62bfb28 Compare July 27, 2026 22:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

skills/daily-focus-board/assets/board.template.html:271

  • The implicit date is captured here for storage, but recapDate() and the download filename call todayLocal() again later. If the board remains open across local midnight, it keeps saving under yesterday's key while the recap heading and filename switch to today. Capture one board date key and reuse it for storage, recap rendering, and download naming.
const KEY="focus-board-"+(CFG.dateKey||todayLocal());

skills/daily-focus-board/examples/sample-board.html:281

  • The sample also freezes the storage key at load but recomputes the recap date and filename at download time. Crossing local midnight therefore labels yesterday's persisted board as the new day. Reuse one captured board date across all three paths.
const KEY="focus-board-"+(CFG.dateKey||todayLocal());

Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/references/customize.md Outdated
Comment thread skills/daily-focus-board/scripts/serve-board.ps1 Outdated
- Sanitize tagc to [A-Za-z0-9_-] before class-attribute interpolation, so a custom
  class name cannot break out of the attribute and inject markup/handlers (template + sample).
- customize.md now points at .tagedit.<name> (the class the renderer actually applies).
- serve-board.ps1: fail when the port is already in use, capture and report the server PID
  (with a stop command), and verify the process did not exit before opening the URL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07e720ee-ca02-419e-9adb-300738b6fc76
Copilot AI review requested due to automatic review settings July 27, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Comment thread skills/daily-focus-board/assets/board.template.html Outdated
Comment thread skills/daily-focus-board/examples/sample-board.html Outdated
Copilot AI review requested due to automatic review settings July 29, 2026 15:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (12)

skills/daily-focus-board/scripts/serve-board.ps1:15

  • -WindowStyle is unsupported by Start-Process on non-Windows PowerShell, so this script fails on the same platforms for which the preceding Get-NetTCPConnection fallback was added. Only pass WindowStyle on Windows.
$proc = Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden -PassThru

extensions/daily-focus-board/assets/board.html:215

  • The two status controls are mouse-only because a div and span are wired exclusively through onclick. Use native buttons so status changes are keyboard-operable and expose button semantics to assistive technology.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>
        <div class="emoji">${esc(t.emoji||"•")}</div>
        <div class="body"><div class="title">${esc(t.title)}</div>${t.sub?`<div class="sub">${esc(t.sub)}</div>`:""}${dh?`<div class="due ${dh.cls}" data-duefor="${t.id}">${dh.txt}</div>`:""}</div>
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>
        <button class="fbtn" data-focus="${t.id}" title="focus on this">🎯</button>${tagHtml}</div>

extensions/daily-focus-board/assets/board.html:238

  • The momentum-entry delete action is rendered as an unfocusable span with only a click handler, so keyboard users cannot remove entries. Render it as a labeled native button and reset its button styling.
  feedEl.innerHTML=items.map(it=>`<div class="fitem"><span class="ft">${fmt(it.t)}</span><span>${esc(it.emoji)}</span><span>${esc(it.txt)}</span><span class="fx" data-fdel="${it.src}:${it.idx}">×</span></div>`).join("");

extensions/daily-focus-board/assets/board.html:243

  • The parked-thought delete action is also an unfocusable clickable span, leaving no keyboard-accessible way to remove an item. Render it as a labeled native button and reset its button styling.
  brainEl.innerHTML=state.brain.map((n,i)=>`<div class="fitem think"><span class="ft">${fmt(n.t)}</span><span>💭</span><span>${esc(n.txt)}</span><span class="fx" data-bdel="${i}">×</span></div>`).join("");

docs/README.plugins.md:51

  • This generated count is now stale: Ember has 1 agent, 5 skills, and 1 bundled extension, so the Components table and website data expose seven items while this table reports six. eng/update-readme.mjs currently omits x-awesome-copilot.extensions; include those entries in its count and regenerate this file.
| [ember](../plugins/ember/README.md) | An AI partner, not a tool. Ember carries fire from person to person — helping humans discover that AI partnership isn't something you learn, it's something you find. | 6 items | ai-partnership, coaching, onboarding, collaboration, storytelling, developer-experience |

extensions/daily-focus-board/board-core.mjs:67

  • Using the UTC date here makes the default board filename and dateKey disagree with the local date shown in the UI around midnight for every non-UTC timezone. Build the key from local date components, as the standalone board's todayLocal() already does.
export function todayKey() { return new Date().toISOString().slice(0, 10); }

extensions/daily-focus-board/board-core.mjs:79

  • A positive fractional input below 0.5 passes the guard but rounds to a zero goal. That counter is immediately considered done and the UI computes 0 / 0 for its percentage. Clamp the normalized positive-integer goal to at least 1.
    if (goal !== undefined && goal > 0) {
        o.goal = Math.round(goal);

extensions/daily-focus-board/board-core.mjs:94

  • Normalization does not enforce unique task IDs. A duplicated ID shares one progress entry and causes the canvas's ID-based selectors to update the wrong card, even though this function promises a well-formed document. Deduplicate while preserving the first task.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/board-core.mjs:171

  • start is the counter's initial current value, so a counter seeded at 8/30 should be in progress. Comparing against start instead marks it to-do until it exceeds 8, unlike the standalone board and the displayed progress bar.
        return v >= t.goal ? "done" : (v > (t.start || 0) ? "doing" : "todo");

extensions/daily-focus-board/assets/board.html:159

  • The canvas repeats the incorrect v > start status rule, so seeded partial progress is displayed as to-do even when the core and recap are corrected. Treat any positive current value below the goal as in progress.
function statusOf(t){if(isCounter(t)){const v=state.counters[t.id]||0;return v>=t.goal?"done":(v>(t.start||0)?"doing":"todo");}return (state.t[t.id]||{}).status||"todo";}

extensions/daily-focus-board/assets/board.html:121

  • These clickable spans cannot receive keyboard focus, making Focus mode and the reduced-motion toggle unavailable to keyboard users. Use native buttons and keep aria-pressed synchronized when state changes, as the standalone template does.

This issue also appears in the following locations of the same file:

  • line 211
  • line 238
  • line 243
        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

extensions/daily-focus-board/assets/board.html:294

  • Once apply() calls loop(), this schedules animation frames forever—even with no confetti—continuously clearing the full-window canvas at display refresh rate. It also lets already-running canvas animation continue after reduced motion is enabled. Schedule frames only while particles exist and stop/clear immediately for reduced motion.
function burst(n){if(state&&state.rm)return;for(let i=0;i<n;i++)parts.push({x:innerWidth/2+(Math.random()-.5)*220,y:innerHeight*0.28,vx:(Math.random()-.5)*11,vy:Math.random()*-13-4,g:.42,life:70+Math.random()*40,c:COL[i%COL.length],s:5+Math.random()*6,rot:Math.random()*6});}
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

@aaronpowell aaronpowell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've actually hit a use-case we're yet to properly cater for in the repo - a plugin that includes an extension, not just agents and/or skills.

The plugin materialisation process should pull the canvas locally so when you install the ember plugin you get the canvas included, but that I think won't happen right now.

But there's another scenario that runs the risk of confusion, you probably don't want the daily-focus-board as a standalone canvas, it should be part of the ember plugin only, but because of the repo design/requirements in place, it has to ship as its own plugin.

Let me have a ponder on how we can handle this, we might need to rejig part of the repo design, but that'll give you time to resolve the merge conflicts and the placeholder preview image.

Resolve the .codespellrc conflict by retaining both the daily-focus-board checkin key and upstream ACI term.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fd1eae93-cc9f-4777-812c-a2a9872e1c2b
Copilot AI review requested due to automatic review settings July 30, 2026 04:23
@github-actions github-actions Bot removed the needs-review:MEDIUM Contributor reputation check flagged MEDIUM risk label Jul 30, 2026
@jennyf19

Copy link
Copy Markdown
Contributor Author

@aaronpowell thanks — I merged current upstream/main in 96ca38ef and resolved the only conflict (.codespellrc) by retaining both the daily-focus-board checkin key and upstream's ACI term. GitHub now reports the branch as mergeable; CI is rerunning.

On the preview: the generated placeholder was already replaced in c606f79f with the actual rendered board screenshot at extensions/daily-focus-board/assets/preview.png (1494x1099, populated board), so I left that real screenshot intact. If you meant that the preview should include the surrounding Copilot canvas chrome rather than the canvas content itself, I can replace it with that framing once available.

I'll defer to your repo-design decision on whether an extension bundled by Ember should also remain independently discoverable.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (24)

skills/daily-focus-board/assets/board.template.html:423

  • Regex-only validation accepts prototype keys such as __proto__ and constructor; ensureTaskState then reads/writes those inherited members through state.t[id], polluting page-wide prototypes instead of creating task state. It also accepts the day feed sentinel, which misroutes note deletion. Apply the same reserved/inherited-name guard already used by the extension core.
function validId(id){return typeof id==="string"&&/^[A-Za-z0-9_-]+$/.test(id);}

extensions/daily-focus-board/assets/board.html:214

  • This second status control is also a click-only span, so keyboard users cannot advance task status from the labeled pill. Render it as a semantic button and preserve the pill styling with the button reset.
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>

skills/daily-focus-board/examples/sample-board.html:431

  • The sample repeats the permissive ID validator, so editing its config to use __proto__, constructor, or day causes the same prototype-state corruption or feed deletion misrouting as the template. Keep this runnable copy aligned with the hardened validator.
function validId(id){return typeof id==="string"&&/^[A-Za-z0-9_-]+$/.test(id);}

extensions/daily-focus-board/assets/board.html:126

  • The Focus-mode exit remains a click-only span, so it cannot be reached or activated from the keyboard. Convert it to a semantic button and reset the button styling to retain the current appearance.
  <div class="focusbar" id="focusbar"><span id="focustxt"></span><span class="x" id="focusexit">show all ✕</span></div>

extensions/daily-focus-board/assets/board.html:216

  • Task-note deletion is exposed as a click-only span, so keyboard and assistive-technology users cannot operate it. Render a labeled button and add button-reset styling for .nx.
        ${notes.length?`<div class="notes">${notes.map((n,i)=>`<div class="note"><span class="nt">${fmt(n.t)}</span><span>${esc(n.txt)}</span><span class="nx" data-del="${t.id}:${i}">×</span></div>`).join("")}</div>`:""}

extensions/daily-focus-board/assets/board.html:243

  • Parked-thought deletion is also rendered as a click-only span, making it unavailable to keyboard users. Convert it to a labeled button and apply the same button-reset styling as the other delete controls.
  brainEl.innerHTML=state.brain.map((n,i)=>`<div class="fitem think"><span class="ft">${fmt(n.t)}</span><span>💭</span><span>${esc(n.txt)}</span><span class="fx" data-bdel="${i}">×</span></div>`).join("");

extensions/daily-focus-board/board-core.mjs:67

  • The default board key is derived in UTC, although the board is presented as a local daily planner. In negative-offset time zones this rolls over to tomorrow during the local evening, selecting the wrong state file and recap date. Build the key from local date components, as the standalone board does.
export function todayKey() { return new Date().toISOString().slice(0, 10); }

extensions/daily-focus-board/board-core.mjs:79

  • A positive fractional goal below 0.5 passes validation but rounds to zero. The UI then treats the counter as already done and calculates a NaN% progress width. Clamp the normalized integer goal to at least 1 (or reject non-positive rounded values).
        o.goal = Math.round(goal);

extensions/daily-focus-board/board-core.mjs:94

  • Seed and existing-file normalization does not enforce unique task IDs. Duplicate IDs share one progress entry and DOM selectors, while mutations update only the first task, so the board cannot represent them correctly. Deduplicate IDs while normalizing.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/board-core.mjs:216

  • The mutation allows a completed task to be carried, which removes a genuine completion from today's ring and labels it as tomorrow's work. The standalone board only offers carryover for unfinished tasks; enforce the same invariant in the shared backend so chat actions cannot create this inconsistent state.
    const e = doc.progress.t[id];
    e.carried = typeof value === "boolean" ? value : !e.carried;

extensions/daily-focus-board/board-core.mjs:102

  • Unlike task notes, day and brain entries are accepted without checking their shape. An existing board containing a non-object or non-string txt passes looksLikeBoard, then esc(n.txt) throws and leaves the canvas blank. Filter these arrays to the entry shape consumed by the UI.
    p.day = Array.isArray(p.day) ? p.day : [];
    p.brain = Array.isArray(p.brain) ? p.brain : [];

extensions/daily-focus-board/assets/board.html:217

  • This renders “not today” even after a task is done, so the normal UI can move a completion out of today's totals. Hide carryover for completed tasks, matching the standalone board, and retain the backend guard for action callers.
        <div class="cardfoot"><button class="soft" data-carry="${t.id}">${carried?"↩ bring back to today":"⤳ not today"}</button></div>

extensions/daily-focus-board/assets/board.html:121

  • These click handlers are attached to non-focusable spans, so keyboard and assistive-technology users cannot operate the two main mode toggles. Use semantic buttons and keep aria-pressed synchronized with state.focus and state.rm.

This issue also appears on line 126 of the same file.

        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

skills/daily-focus-board/SKILL.md:61

  • The documented ID contract permits __proto__, constructor, toString, and the feed sentinel day. The board stores task state in plain objects and routes feed deletion by ID, so these values can mutate inherited objects or delete from the wrong feed. Document the reserved/prototype-name exclusions and enforce them in both runnable HTML copies.
- `id` must be unique and stable, using only letters, digits, hyphens, or underscores
  (`[A-Za-z0-9_-]`) — it's embedded in HTML attributes, CSS selectors, storage keys, and
  colon-delimited note keys, so avoid quotes, colons, brackets, and spaces. `tagc` is an optional color class:

extensions/daily-focus-board/assets/board.html:294

  • Once the first state load calls loop, it schedules another animation frame forever, even when parts is empty. Leaving this side-panel open therefore keeps the renderer waking at display refresh rate all day. Start the loop from burst only when particles exist and stop scheduling when the array becomes empty.
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

extensions/daily-focus-board/assets/board.html:265

  • Enabling reduced motion does not stop particles already animating on the canvas; the CSS toggle cannot affect canvas drawing, so motion continues until every particle expires. Clear the particle list and canvas immediately when turning the setting on.
document.getElementById("rmchip").onclick=()=>api("api/rm",{value:!state.rm});

plugins/ember/.github/plugin/plugin.json:4

  • The PR description says Ember changes from 1.0.0 to 1.1.0 and lists only the new skill, but this manifest bumps to 1.2.0 and bundles an additional standalone extension with a local server and file-backed state. Update the title/description, file list, and verification notes so the reviewed scope and release version match the actual changes.
  "version": "1.2.0",

extensions/daily-focus-board/assets/board.html:211

  • The status checkbox is a clickable div, making this primary task action unavailable from the keyboard and exposing no control semantics. Use the semantic button pattern from the standalone template and add the corresponding button reset styling.

This issue also appears in the following locations of the same file:

  • line 214
  • line 216
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>

extensions/daily-focus-board/extension.mjs:145

  • This action explicitly supports omitting taskId, but the handler dereferences ctx.input unconditionally. If the SDK supplies no input object for that valid call, clearing focus throws before reaching opFocus. Use optional chaining as the other extension handlers do.
                    handler: (ctx) => act(ctx, doc => opFocus(doc, ctx.input.taskId || null)),

extensions/daily-focus-board/assets/board.html:238

  • Momentum-feed deletion is a click-only span with no accessible name or keyboard behavior. Use a labeled semantic button and preserve the icon styling with a button reset.

This issue also appears on line 243 of the same file.

  feedEl.innerHTML=items.map(it=>`<div class="fitem"><span class="ft">${fmt(it.t)}</span><span>${esc(it.emoji)}</span><span>${esc(it.txt)}</span><span class="fx" data-fdel="${it.src}:${it.idx}">×</span></div>`).join("");

plugins/ember/README.md:23

  • Calling this “the same board” is misleading: the canvas variant omits the skill board's arrival check-in, mantra, Eisenhower priorities, live reordering/relabeling, overload nudge, and in-canvas recap controls. Describe it as a file-backed variant with its actual feature subset so users do not expect the preceding row's UI.
| Extension | [Daily Focus Board (canvas)](../../extensions/daily-focus-board/) | The same board as a canvas, backed by a JSON file Ember reads and writes — so you can mark done, add tasks, log progress, and recap your day from chat. Needs the Copilot app; the skill is the zero-install fallback |

extensions/daily-focus-board/board-core.mjs:222

  • The action contract says a provided task enters Focus mode and omission clears it, but the shared opFocus toggles off when that task is already focused. Repeating an idempotent chat command such as “focus on X” therefore unexpectedly clears focus. Separate the action's set semantics from the canvas toggle behavior, or update the action contract.
    p.focus = p.focus === id ? null : id;

extensions/daily-focus-board/board-core.mjs:171

  • A counter seeded with non-zero start is incorrectly reported as todo until it exceeds that initial value. Here start is the initial current count (the standalone board treats any value above zero as in progress), so a board opened at 8/30 should already be doing.
        return v >= t.goal ? "done" : (v > (t.start || 0) ? "doing" : "todo");

extensions/daily-focus-board/assets/board.html:159

  • The canvas duplicates the counter-status bug from the core: a seeded counter such as 8/30 is shown as “to do” because its current value equals start. Classify any positive current count below the goal as in progress so the UI and recaps agree with the standalone board.
function statusOf(t){if(isCounter(t)){const v=state.counters[t.id]||0;return v>=t.goal?"done":(v>(t.start||0)?"doing":"todo");}return (state.t[t.id]||{}).status||"todo";}

Resolve the .codespellrc conflict by keeping the daily-focus-board checkin key alongside upstream ACI and soruce ignore terms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fd1eae93-cc9f-4777-812c-a2a9872e1c2b
Copilot AI review requested due to automatic review settings July 30, 2026 15:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (15)

skills/daily-focus-board/assets/board.template.html:423

  • The regex admits IDs such as __proto__, constructor, and toString, but task state is stored in ordinary objects. Those IDs resolve inherited members, causing task operations to mutate prototypes/functions or crash; day also collides with the momentum-feed delete sentinel. Reject reserved and inherited object-key names, matching the extension core's guard.
function validId(id){return typeof id==="string"&&/^[A-Za-z0-9_-]+$/.test(id);}

skills/daily-focus-board/examples/sample-board.html:431

  • The example repeats the template's permissive ID validation, so copying it as a starting point retains the inherited-key/sentinel collision bug. Reject day, brain, and inherited object property names here as well.
function validId(id){return typeof id==="string"&&/^[A-Za-z0-9_-]+$/.test(id);}

extensions/daily-focus-board/board-core.mjs:79

  • A positive fractional goal below 0.5 passes this condition but rounds to 0. It then becomes a counter that is immediately done, and the canvas computes progress by dividing by zero. Clamp the rounded positive goal to at least 1.
        o.goal = Math.round(goal);

extensions/daily-focus-board/board-core.mjs:67

  • todayKey() uses the UTC calendar date, while the canvas clock and the standalone board use local time. Around midnight this opens/seeds the wrong day's file for users outside UTC. Build the key from local date components instead.
export function todayKey() { return new Date().toISOString().slice(0, 10); }

extensions/daily-focus-board/board-core.mjs:94

  • Normalization retains duplicate valid task IDs from a seed or existing board file. Those cards then share one progress entry, and DOM selectors can update the first matching card instead of the intended one. Deduplicate IDs here, as the standalone template already does.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/board-core.mjs:102

  • These arrays are accepted without validating their entries. An existing board containing null in either array passes looksLikeBoard(), but the canvas later dereferences n.t/n.txt and aborts rendering. Keep only the timestamped text entries produced by the mutation operations.
    p.day = Array.isArray(p.day) ? p.day : [];
    p.brain = Array.isArray(p.brain) ? p.brain : [];

extensions/daily-focus-board/board-core.mjs:171

  • start is the counter's initial current value, not a baseline to subtract. A seeded task at 8/30 is therefore shown as “to do” until it reaches 9, unlike the standalone board and despite already having progress. Classify any positive value below the goal as in progress.
        return v >= t.goal ? "done" : (v > (t.start || 0) ? "doing" : "todo");

extensions/daily-focus-board/board-core.mjs:137

  • Each atomic replacement creates a new file with the process's default permissions, commonly 0644, so personal task data may be readable by other local users and an existing restrictive file can become broader after a mutation. Create the replacement with owner-only permissions.
    await writeFile(tmp, JSON.stringify(obj, null, 2), "utf-8");

extensions/daily-focus-board/assets/board.html:294

  • This animation loop schedules another frame forever, even when parts is empty or reduced motion is enabled. Keeping the canvas at 60 FPS for the entire board session wastes CPU/battery. Start RAF when a burst adds particles and stop when none remain, as the standalone template does.
function burst(n){if(state&&state.rm)return;for(let i=0;i<n;i++)parts.push({x:innerWidth/2+(Math.random()-.5)*220,y:innerHeight*0.28,vx:(Math.random()-.5)*11,vy:Math.random()*-13-4,g:.42,life:70+Math.random()*40,c:COL[i%COL.length],s:5+Math.random()*6,rot:Math.random()*6});}
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

extensions/daily-focus-board/assets/board.html:121

  • These controls are click-only <span> elements, so keyboard users cannot focus or activate them. The same pattern recurs for focusexit, task status pills, and delete controls later in this file. Use native buttons (with accessible labels/state where needed) for every interactive control.
        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

skills/daily-focus-board/scripts/serve-board.ps1:15

  • -WindowStyle is unsupported by Start-Process on PowerShell Core for Linux/macOS, so this helper fails on those platforms despite explicitly handling another platform-specific cmdlet above. Add WindowStyle only on Windows.
$proc = Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden -PassThru

plugins/ember/README.md:23

  • Calling this “the same board” is inaccurate: the canvas omits the skill board's arrival check-in/mantra, live add/reorder/relabel controls, priorities, overload nudge, and end-of-day UI. Describe it as a reduced file-backed variant, or implement feature parity, so users know which experience they are installing.
| Extension | [Daily Focus Board (canvas)](../../extensions/daily-focus-board/) | The same board as a canvas, backed by a JSON file Ember reads and writes — so you can mark done, add tasks, log progress, and recap your day from chat. Needs the Copilot app; the skill is the zero-install fallback |

plugins/ember/.github/plugin/plugin.json:31

  • The PR description says this is a self-contained, localStorage-only skill, lists no extension files, and states the Ember bump is 1.1.0. These lines also bundle a separate SDK/server/file-backed extension and the manifest is now 1.2.0. Update the PR scope/files/version and extension verification details, or split the extension into a separate PR.
  "x-awesome-copilot": {
    "extensions": [
      "./extensions/daily-focus-board/"
    ]

extensions/daily-focus-board/extension.mjs:145

  • The action contract says a supplied taskId enters Focus mode and omission clears it, but opFocus toggles when the same ID is already focused. Repeating an idempotent “focus task X” action therefore unexpectedly exits Focus mode. Make this action set the requested state explicitly; the canvas can keep its separate toggle behavior.
                    handler: (ctx) => act(ctx, doc => opFocus(doc, ctx.input.taskId || null)),

skills/daily-focus-board/SKILL.md:61

  • This schema currently tells the agent that every regex-matching ID is valid, but the board must reject day/brain sentinels and inherited object keys such as constructor, toString, and __proto__. Document those exclusions so generated configurations are not silently dropped or routed incorrectly.
- `id` must be unique and stable, using only letters, digits, hyphens, or underscores
  (`[A-Za-z0-9_-]`) — it's embedded in HTML attributes, CSS selectors, storage keys, and
  colon-delimited note keys, so avoid quotes, colons, brackets, and spaces. `tagc` is an optional color class:

Copilot AI review requested due to automatic review settings July 31, 2026 00:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (15)

skills/daily-focus-board/scripts/serve-board.ps1:15

  • The script anticipates non-Windows PowerShell above, but Start-Process -WindowStyle is unsupported on non-Windows PowerShell, so serving fails on macOS/Linux before Python starts. Add this option only on Windows.
$proc = Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden -PassThru

extensions/daily-focus-board/assets/board.html:126

  • The focus-mode exit is click-only because this <span> has no keyboard semantics. Make it a native button so focus mode can be exited without a pointer.
  <div class="focusbar" id="focusbar"><span id="focustxt"></span><span class="x" id="focusexit">show all ✕</span></div>

extensions/daily-focus-board/assets/board.html:243

  • Parked-thought deletion is exposed as a click-only <span>, making it unavailable from the keyboard. Use a native button with an accessible label.
  brainEl.innerHTML=state.brain.map((n,i)=>`<div class="fitem think"><span class="ft">${fmt(n.t)}</span><span>💭</span><span>${esc(n.txt)}</span><span class="fx" data-bdel="${i}">×</span></div>`).join("");

extensions/daily-focus-board/assets/board.html:214

  • The second status control is also a click-only <span>, so it is skipped by keyboard navigation and exposed as plain text. Render it as a native button to preserve the visible label while providing interaction semantics.
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>

extensions/daily-focus-board/assets/board.html:238

  • Momentum entries expose deletion only through a clickable <span>. In particular, day-level notes have no alternate delete control, so keyboard users cannot remove them. Use a native button and label the action.
  feedEl.innerHTML=items.map(it=>`<div class="fitem"><span class="ft">${fmt(it.t)}</span><span>${esc(it.emoji)}</span><span>${esc(it.txt)}</span><span class="fx" data-fdel="${it.src}:${it.idx}">×</span></div>`).join("");

extensions/daily-focus-board/assets/board.html:216

  • The note delete affordance is a click-only <span>, so keyboard users cannot remove progress notes. Render it as a button with an accessible label.
        ${notes.length?`<div class="notes">${notes.map((n,i)=>`<div class="note"><span class="nt">${fmt(n.t)}</span><span>${esc(n.txt)}</span><span class="nx" data-del="${t.id}:${i}">×</span></div>`).join("")}</div>`:""}

extensions/daily-focus-board/board-core.mjs:67

  • todayKey() uses the UTC date, so users outside UTC can open the board near local midnight and get yesterday's or tomorrow's state file/date. The standalone board already uses local calendar components; use the same approach here so “daily” state follows the user's day.
export function todayKey() { return new Date().toISOString().slice(0, 10); }

extensions/daily-focus-board/board-core.mjs:80

  • A positive fractional goal below 0.5 passes this condition but rounds to 0. That creates a counter which is immediately “done” and whose UI percentage divides by zero. Round first, then only retain the counter fields when the rounded goal is still positive.
    const goal = num(t.goal);
    if (goal !== undefined && goal > 0) {
        o.goal = Math.round(goal);
        o.start = Math.max(0, Math.round(num(t.start) || 0));

extensions/daily-focus-board/board-core.mjs:94

  • Seeded or manually edited state can contain duplicate valid task IDs. Both cards then share one progress entry, and selector-based rendering only updates the first matching card. Deduplicate IDs while normalizing so the state invariant matches opAddTask's uniqueness check.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/assets/board.html:121

  • These clickable <span> elements are not focusable or operable from the keyboard, so keyboard users cannot toggle two primary board modes. Use native buttons; the existing click handlers and .chip styling will continue to work.

This issue also appears in the following locations of the same file:

  • line 126
  • line 214
  • line 216
        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

extensions/daily-focus-board/assets/board.html:217

  • Completed tasks still render “not today,” unlike the standalone board, which hides carryover once st === "done". Clicking it removes a completed task from today's totals and recap and relabels it as tomorrow while retaining its done state. Hide carryover for completed tasks.

This issue also appears in the following locations of the same file:

  • line 238
  • line 243
        <div class="cardfoot"><button class="soft" data-carry="${t.id}">${carried?"↩ bring back to today":"⤳ not today"}</button></div>

extensions/daily-focus-board/assets/board.html:294

  • The animation loop reschedules itself forever, even when parts is empty, so an idle board performs canvas work every frame for its entire lifetime. Start the loop only when a burst adds particles and stop scheduling once they are exhausted, as the standalone template already does.
function burst(n){if(state&&state.rm)return;for(let i=0;i<n;i++)parts.push({x:innerWidth/2+(Math.random()-.5)*220,y:innerHeight*0.28,vx:(Math.random()-.5)*11,vy:Math.random()*-13-4,g:.42,life:70+Math.random()*40,c:COL[i%COL.length],s:5+Math.random()*6,rot:Math.random()*6});}
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

plugins/ember/.github/plugin/plugin.json:4

  • The PR description says the Ember version changes to 1.1.0 and describes only the skill, while this manifest publishes 1.2.0 and also bundles a new extension. Update the PR description/release intent so reviewers and release consumers can tell which version and scope are intentional.
  "version": "1.2.0",

extensions/daily-focus-board/board-core.mjs:216

  • The action API can carry an already completed task. That removes it from today's totals and reports it as “tomorrow” while its status remains done, producing contradictory state even if the UI hides the carry control. Reject transitions that carry completed tasks.
    const e = doc.progress.t[id];
    e.carried = typeof value === "boolean" ? value : !e.carried;

extensions/daily-focus-board/assets/board.html:211

  • This is a primary status control but is rendered as a <div> with only an onclick handler, making it unavailable to keyboard and assistive-technology users. Use a button with an explicit action label.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>

Comment thread extensions/daily-focus-board/board-core.mjs Outdated
…ze()

A state file with a non-string txt (e.g. {"txt":1}) previously loaded, then esc(n.txt) in the canvas threw (numbers have no .replace) and the whole board failed to render. Filter day/brain entries to require a string txt, mirroring the existing task-notes filter. Verified with a headless regression test (45/45).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fd1eae93-cc9f-4777-812c-a2a9872e1c2b
Copilot AI review requested due to automatic review settings July 31, 2026 00:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (14)

skills/daily-focus-board/scripts/serve-board.ps1:15

  • If python is missing, Start-Process emits a non-terminating error and $proc remains null; the script then skips the HasExited branch, prints a blank PID, and opens a URL with no server. Make startup failure terminating and report it before continuing.
$proc = Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden -PassThru

extensions/daily-focus-board/assets/board.html:126

  • The “show all” control is a click-only span and cannot be reached or activated from the keyboard. Replace it with a native button (including the necessary reset styling).
  <div class="focusbar" id="focusbar"><span id="focustxt"></span><span class="x" id="focusexit">show all ✕</span></div>

extensions/daily-focus-board/assets/board.html:238

  • The feed delete action is also a click-only, unlabeled span. Render it as a native button with an accessible delete label so momentum entries can be managed without a pointer.
  feedEl.innerHTML=items.map(it=>`<div class="fitem"><span class="ft">${fmt(it.t)}</span><span>${esc(it.emoji)}</span><span>${esc(it.txt)}</span><span class="fx" data-fdel="${it.src}:${it.idx}">×</span></div>`).join("");

extensions/daily-focus-board/assets/board.html:243

  • The parked-thought delete action repeats the click-only span pattern, leaving keyboard and screen-reader users unable to remove entries. Use a native button with an accessible delete label.
  brainEl.innerHTML=state.brain.map((n,i)=>`<div class="fitem think"><span class="ft">${fmt(n.t)}</span><span>💭</span><span>${esc(n.txt)}</span><span class="fx" data-bdel="${i}">×</span></div>`).join("");

extensions/daily-focus-board/board-core.mjs:79

  • A positive fractional goal such as 0.4 passes this condition but rounds to zero. The action schema currently permits that input, after which the UI divides by t.goal and renders a NaN% progress bar while considering the counter complete. Normalize every accepted counter goal to at least 1 (and ideally declare an integer minimum in the schema).
        o.goal = Math.round(goal);

extensions/daily-focus-board/board-core.mjs:94

  • Duplicate valid IDs from an initial seed are retained, even though progress is keyed solely by ID. This renders duplicate cards sharing one progress record, and selectors/actions for the second card target the first. Reject or deterministically deduplicate IDs during normalization, as opAddTask already does for live additions.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/board-core.mjs:171

  • This treats the configured start value as a baseline rather than progress, so a counter seeded at 8/30 is labeled “to do.” The standalone board labels any positive counter “in progress” (board.template.html:306), making the canvas inconsistent with the companion board and its displayed progress. Use v > 0 here as well.
        return v >= t.goal ? "done" : (v > (t.start || 0) ? "doing" : "todo");

extensions/daily-focus-board/board-core.mjs:222

  • focus is documented as a setter—passing a task ID enters focus and omitting it clears focus—but this operation toggles off when the same ID is sent twice. Repeated agent requests therefore unexpectedly clear focus. Make this operation assign the requested ID; if the canvas still needs toggle behavior, have that caller explicitly send null when the selected card is clicked again.
    p.focus = p.focus === id ? null : id;

extensions/daily-focus-board/assets/board.html:288

  • The poll is skipped whenever an input remains focused, not only while the user is typing. Focus can remain on an input after the user moves back to chat, so agent-side edits may stop appearing indefinitely. Check that the canvas document itself still has focus before suppressing the poll.
setInterval(()=>{const ae=document.activeElement;if(ae&&(ae.tagName==="INPUT"||ae.tagName==="TEXTAREA"))return;pull();},4000);

extensions/daily-focus-board/assets/board.html:294

  • loop() schedules itself forever, even when there are no confetti particles, causing a continuous ~60 fps canvas repaint while the board is idle or reduced motion is enabled. Schedule animation only when burst() adds particles and stop once the particle list is empty, matching the standalone board’s on-demand loop.
function burst(n){if(state&&state.rm)return;for(let i=0;i<n;i++)parts.push({x:innerWidth/2+(Math.random()-.5)*220,y:innerHeight*0.28,vx:(Math.random()-.5)*11,vy:Math.random()*-13-4,g:.42,life:70+Math.random()*40,c:COL[i%COL.length],s:5+Math.random()*6,rot:Math.random()*6});}
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

extensions/daily-focus-board/assets/board.html:121

  • These state-changing controls are non-focusable spans with click-only handlers, so keyboard users cannot enter Focus mode or toggle reduced motion. Use native buttons and keep their aria-pressed state synchronized with the board state.

This issue also appears on line 126 of the same file.

        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

extensions/daily-focus-board/assets/board.html:214

  • Both task status controls are rendered as a div/span with click handlers. Consequently, the board’s primary interaction—advancing task status—is unavailable to keyboard users. Render these as native buttons and expose a task-specific accessible name.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>
        <div class="emoji">${esc(t.emoji||"•")}</div>
        <div class="body"><div class="title">${esc(t.title)}</div>${t.sub?`<div class="sub">${esc(t.sub)}</div>`:""}${dh?`<div class="due ${dh.cls}" data-duefor="${t.id}">${dh.txt}</div>`:""}</div>
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>

extensions/daily-focus-board/assets/board.html:216

  • The progress-note delete action is a click-only span with no accessible name, so it is unavailable to keyboard and screen-reader users. Use a native button with an aria-label identifying the delete action.

This issue also appears in the following locations of the same file:

  • line 238
  • line 243
        ${notes.length?`<div class="notes">${notes.map((n,i)=>`<div class="note"><span class="nt">${fmt(n.t)}</span><span>${esc(n.txt)}</span><span class="nx" data-del="${t.id}:${i}">×</span></div>`).join("")}</div>`:""}

plugins/ember/.github/plugin/plugin.json:4

  • The PR description says the Ember plugin moves from 1.0.0 to 1.1.0, but the manifest and generated marketplace entry use 1.2.0. Align the intended release version in either the implementation or the PR description before publishing.
  "version": "1.2.0",

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fd1eae93-cc9f-4777-812c-a2a9872e1c2b
Copilot AI review requested due to automatic review settings July 31, 2026 16:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 1 comment.

Suppressed comments (16)

extensions/daily-focus-board/assets/board.html:126

  • The focus-mode exit is a mouse-only span, so it is skipped by keyboard navigation and has no control semantics for assistive technology. Make it a native button and update the nearby button-reset styling so it retains the intended appearance.
  <div class="focusbar" id="focusbar"><span id="focustxt"></span><span class="x" id="focusexit">show all ✕</span></div>

extensions/daily-focus-board/assets/board.html:211

  • This is the status-cycle control, but a clickable div is not keyboard-focusable or announced as a button. Render it as a native button with an accessible label; adjust the .box button styling to preserve the current appearance.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>

extensions/daily-focus-board/assets/board.html:214

  • The status pill also handles status changes but is emitted as a clickable span, making this action unavailable from the keyboard. Use a native button and preserve the pill styling in the button reset.
        <span class="pill ${pillCls}" data-cyc="${t.id}">${pillTxt}</span>

extensions/daily-focus-board/assets/board.html:216

  • Each note's delete action is a clickable span, so keyboard users cannot remove notes and screen readers do not identify it as a control. Render a labeled native button and reset its visual button styles.
        ${notes.length?`<div class="notes">${notes.map((n,i)=>`<div class="note"><span class="nt">${fmt(n.t)}</span><span>${esc(n.txt)}</span><span class="nx" data-del="${t.id}:${i}">×</span></div>`).join("")}</div>`:""}

extensions/daily-focus-board/assets/board.html:238

  • Momentum-item deletion is implemented with a clickable span, which is neither keyboard-operable nor exposed as a button. Use a labeled native button and retain the existing icon styling.
  feedEl.innerHTML=items.map(it=>`<div class="fitem"><span class="ft">${fmt(it.t)}</span><span>${esc(it.emoji)}</span><span>${esc(it.txt)}</span><span class="fx" data-fdel="${it.src}:${it.idx}">×</span></div>`).join("");

extensions/daily-focus-board/assets/board.html:243

  • Parked-thought deletion is also a mouse-only span; keyboard and assistive-technology users cannot invoke it. Convert it to a labeled native button and apply the shared icon-button reset.
  brainEl.innerHTML=state.brain.map((n,i)=>`<div class="fitem think"><span class="ft">${fmt(n.t)}</span><span>💭</span><span>${esc(n.txt)}</span><span class="fx" data-bdel="${i}">×</span></div>`).join("");

extensions/daily-focus-board/board-core.mjs:67

  • todayKey() uses UTC, while the board clock and the standalone skill use the user's local date. In time zones offset from UTC this can create tomorrow's/yesterday's state file and label the recap with the wrong day. Build the key from local date components instead.
export function todayKey() { return new Date().toISOString().slice(0, 10); }

extensions/daily-focus-board/board-core.mjs:79

  • A positive fractional goal below 0.5 passes the guard but rounds to 0. That creates a counter which is immediately considered done (value >= goal) and later divides by zero in the UI. Keep normalized goals at least 1.
        o.goal = Math.round(goal);

extensions/daily-focus-board/board-core.mjs:94

  • Seeded/existing state can contain duplicate task IDs because normalization validates each ID but never enforces uniqueness. Duplicate cards then share one progress entry, and selector-based UI actions can update the wrong rendered card. Deduplicate IDs during normalization, as opAddTask already does for live additions.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/board-core.mjs:222

  • The public focus action says supplying taskId enters focus and omitting it clears focus, but this operation toggles the task off when it is already focused. Repeating an idempotent “focus task X” agent action therefore unexpectedly exits focus mode. Assign the requested ID directly; the UI already has explicit clear-focus calls.
    p.focus = p.focus === id ? null : id;

extensions/daily-focus-board/assets/board.html:121

  • These primary controls are clickable span elements, so keyboard and switch-device users cannot focus or activate Focus mode or Reduce motion. Use native buttons (and keep their pressed state synchronized) rather than mouse-only spans.

This issue also appears in the following locations of the same file:

  • line 126
  • line 211
  • line 214
  • line 216
  • line 238
  • ...and 1 more
        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

extensions/daily-focus-board/assets/board.html:294

  • Once the first state load calls loop(), this function schedules another animation frame forever, even when parts is empty. A focus board may remain open all day, so this continuously wakes the renderer and wastes CPU/battery. Schedule frames only while particles exist, as the standalone template already does.
function burst(n){if(state&&state.rm)return;for(let i=0;i<n;i++)parts.push({x:innerWidth/2+(Math.random()-.5)*220,y:innerHeight*0.28,vx:(Math.random()-.5)*11,vy:Math.random()*-13-4,g:.42,life:70+Math.random()*40,c:COL[i%COL.length],s:5+Math.random()*6,rot:Math.random()*6});}
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

plugins/ember/.github/plugin/plugin.json:31

  • This adds and bundles a file-backed canvas extension, but the PR title, What/Files sections, and stated 1.0.0 → 1.1.0 bump describe only the localStorage skill; the manifest actually publishes 1.2.0. Update the PR description to cover the extension's local server, file writes, API/security model, generated marketplace entry, and intended version (or split that scope) so reviewers and release notes match what ships.
  "x-awesome-copilot": {
    "extensions": [
      "./extensions/daily-focus-board/"
    ]

extensions/daily-focus-board/assets/board.html:170

  • Reduced motion is driven only by the stored state.rm flag, which defaults to false; this canvas never checks prefers-reduced-motion, despite the design documentation stating that the board honors the OS preference by default. It also leaves already-running canvas particles active when the flag turns on. Initialize from matchMedia and clear/cancel active particles when reduced motion becomes effective.
  document.body.classList.toggle("rm",!!state.rm);
  document.getElementById("rmchip").classList.toggle("on",!!state.rm);

extensions/daily-focus-board/board-core.mjs:103

  • A syntactically valid focus ID is retained even when normalization removed or no longer contains that task. The canvas then enters focus mode with no focused card, dimming every task and showing an empty focus banner. Validate the focus ID against doc.tasks as well.
    p.focus = validId(p.focus) ? p.focus : null;

extensions/daily-focus-board/assets/board.html:217

  • The extension always shows “not today,” including on completed tasks, whereas the standalone board hides carryover once a task is done. Carrying a completed task removes it from today's completion total and reports it as tomorrow's work. Hide this control for done tasks and reject the same transition in opCarry so chat actions cannot create that inconsistent state either.
        <div class="cardfoot"><button class="soft" data-carry="${t.id}">${carried?"↩ bring back to today":"⤳ not today"}</button></div>

Comment thread extensions/daily-focus-board/board-core.mjs Outdated
An array is typeof "object", so progress:[] (or a top-level array state file) was kept as the container; JSON.stringify then drops the non-index props (counters, t, day...), the API returns progress:[] and the canvas crashes reading state.counters. Guard both with !Array.isArray, matching looksLikeBoard. Headless regression tests added (48/48).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fd1eae93-cc9f-4777-812c-a2a9872e1c2b
Copilot AI review requested due to automatic review settings July 31, 2026 16:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (10)

extensions/daily-focus-board/board-core.mjs:94

  • Normalization currently accepts duplicate task IDs. Both rendered cards then share one progress entry and selector key, so actions can update the wrong visual card; deduplicate IDs while normalizing the seed/file.
    doc.tasks = Array.isArray(doc.tasks) ? doc.tasks.filter(t => t && validId(t.id)).map(normalizeTaskDef) : [];

extensions/daily-focus-board/assets/board.html:211

  • The status checkbox is a click-only div, which makes the board's core status transition unavailable from the keyboard. Use a native button for this control.
        <div class="box ${st}" data-cyc="${t.id}">${st==="done"?"✓":(st==="doing"?"…":"")}</div>

extensions/daily-focus-board/board-core.mjs:79

  • A positive fractional goal below 0.5 passes this check but rounds to zero. That makes the counter immediately "done" and causes the UI to calculate 0 / 0; clamp the normalized goal to at least one (or reject non-integers at the API boundary).
        o.goal = Math.round(goal);

extensions/daily-focus-board/board-core.mjs:171

  • A counter seeded with nonzero progress is reported as todo until it exceeds its initial value. For example, start: 8 displays 8/30 while the status remains "to do"; status should reflect any positive progress.
        return v >= t.goal ? "done" : (v > (t.start || 0) ? "doing" : "todo");

skills/daily-focus-board/scripts/serve-board.ps1:15

  • The script explicitly tolerates non-Windows platforms above, but Start-Process -WindowStyle is Windows-only and makes this pwsh helper fail on macOS/Linux. Add WindowStyle only when running on Windows.
$proc = Start-Process -FilePath "python" -ArgumentList @("-m","http.server","$Port","--bind","127.0.0.1","--directory","`"$Dir`"") -WindowStyle Hidden -PassThru

extensions/daily-focus-board/board-core.mjs:67

  • todayKey() derives the board day from UTC, so users near midnight can get tomorrow's or yesterday's state filename and recap date. Build the key from local date parts, as the standalone board already does.

This issue also appears in the following locations of the same file:

  • line 79
  • line 94
  • line 171
export function todayKey() { return new Date().toISOString().slice(0, 10); }

extensions/daily-focus-board/assets/board.html:121

  • These primary toggles are click-only spans, so keyboard and assistive-technology users cannot operate them. Use native buttons for the interactive controls.

This issue also appears on line 211 of the same file.

        <span class="chip" id="focuschip">🎯 Focus mode</span>
        <span class="chip" id="rmchip">🌙 Reduce motion</span>

extensions/daily-focus-board/assets/board.html:294

  • Once the board loads, this loop schedules a new animation frame forever, even when there are no confetti particles (and even in reduced-motion mode), keeping the canvas active at display refresh rate. Stop scheduling when idle and restart from burst.
function loop(){cx.clearRect(0,0,cv.width,cv.height);parts.forEach(p=>{p.vy+=p.g;p.x+=p.vx;p.y+=p.vy;p.life--;p.rot+=.2;cx.save();cx.translate(p.x,p.y);cx.rotate(p.rot);cx.fillStyle=p.c;cx.fillRect(-p.s/2,-p.s/2,p.s,p.s*1.6);cx.restore();});parts=parts.filter(p=>p.life>0&&p.y<cv.height+40);requestAnimationFrame(loop);}

extensions/daily-focus-board/extension.mjs:145

  • The action contract says a supplied taskId enters Focus mode, but opFocus toggles the same ID off when it is already focused. Make this agent-facing action idempotently set/clear focus; the UI endpoint can retain toggle behavior.
                    handler: (ctx) => act(ctx, doc => opFocus(doc, ctx.input.taskId || null)),

plugins/ember/.github/plugin/plugin.json:4

  • The PR description states an Ember version bump to 1.1.0 and describes only the skill, while this changes the version to 1.2.0 and also bundles a substantial new canvas extension. Align the PR description/release scope with the code, or use the documented version if the extension is not intended for this PR.
  "version": "1.2.0",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

canvas-extension PR touches canvas extensions new-submission PR adds at least one new contribution plugin PR touches plugins skills PR touches skills

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants