diff --git a/.claude/skills/pql-board/SKILL.md b/.claude/skills/pql-board/SKILL.md
index bf5aff017..4f05cbab3 100644
--- a/.claude/skills/pql-board/SKILL.md
+++ b/.claude/skills/pql-board/SKILL.md
@@ -63,25 +63,23 @@ Real one-click interactivity lives in the in-chat widget — render it when the
user asks for "the panel", "pick-up panel", or wants to act on tickets rather
than read them.
-1. Pull live data (small, native projections — never embed the whole store):
+1. Generate the widget code with the bundled script (template + live pql data
+ — do NOT hand-compose the HTML):
```bash
- pql ticket list --status in_progress --fields id,type,priority,title
- pql ticket list --under T-750 --unblocked --leaf --fields id,type,priority,title
+ .claude/skills/pql-board/support/make-panel # stdout = widget_code
```
- Panel contents: WIP + the unblocked ready work of the active phase (cap at
- ~10 rows; if more, take highest priority first and say so under the panel).
+ Flags: `--phase T-NNN` (default T-750, the active phase epic), `--cap N`
+ (default 10). Contents: actionable WIP + unblocked leaf work from the
+ active phase AND the maintenance initiative (T-1037), sorted
+ status→priority→recency, overflow noted in the panel. Template:
+ `support/panel-template.html` (edit that file to change the panel's look;
+ the pick-up/detail sendPrompt contract lives there).
-2. Render with `mcp__visualize__show_widget` (load its `read_me` first,
- `interactive` module). Panel shape — compact bordered rows, CDS tokens:
- - Each row: mono ticket id (link to the artifact deep-link `#T-NNN`),
- title, status/priority pill, and two buttons:
- - **Pick up ↗** → `sendPrompt('pick up the following ticket: T-NNN —
')`
- - **Detail ↗** → `sendPrompt('show me T-NNN in detail')`
- - Footer buttons: **Refresh board ↗** → `sendPrompt('/pql-board')`,
- **Next batch ↗** → `sendPrompt('/whats-next')`.
- - Widget rules: no emoji, sentence case, `↗` suffix on sendPrompt buttons.
+2. Render with `mcp__visualize__show_widget`: load its `read_me` first
+ (`interactive` module) if not yet loaded this conversation, then pass the
+ script's stdout verbatim as `widget_code`.
3. **On receiving a "pick up the following ticket:" prompt** (from the panel
or typed): treat it as single-ticket batch activation — the /whats-next
diff --git a/.claude/skills/pql-board/support/make-panel b/.claude/skills/pql-board/support/make-panel
new file mode 100755
index 000000000..4bde9f8c7
--- /dev/null
+++ b/.claude/skills/pql-board/support/make-panel
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+"""Fill panel-template.html with live pql data → widget_code for show_widget.
+
+Emits the interactive in-chat pql panel (see ../SKILL.md §Interactive panel):
+WIP + unblocked leaf work of the active phase, capped, with one-click
+Pick up ↗ / Detail ↗ sendPrompt actions. Claude runs this and pastes stdout
+verbatim as the show_widget widget_code.
+
+Usage: .claude/skills/pql-board/support/make-panel [--phase T-NNN] [--cap N]
+ (defaults: active phase T-750, cap 10; output on stdout)
+"""
+
+import argparse
+import json
+import subprocess
+from pathlib import Path
+
+ARTIFACT_URL = "https://claude.ai/code/artifact/0064e59d-84a9-4a31-a830-f7d677df9d92"
+FIELDS = "id,type,priority,status,title,updated_at"
+PRIORITY_RANK = {"critical": 0, "high": 1, "medium": 2, "low": 3}
+STATUS_RANK = {"in_progress": 0, "review": 1, "ready": 2, "backlog": 3}
+ACTIONABLE_TYPES = ("story", "task", "bug")
+MAINTENANCE = "T-1037"
+
+
+def pql(*args):
+ out = subprocess.run(
+ ["pql", *args], capture_output=True, text=True, check=True
+ ).stdout
+ return json.loads(out)
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("--phase", default="T-750")
+ ap.add_argument("--cap", type=int, default=10)
+ args = ap.parse_args()
+
+ wip = pql("ticket", "list", "--status", "in_progress", "--fields", FIELDS)
+ phase = pql(
+ "ticket", "list", "--under", args.phase, "--unblocked", "--leaf",
+ "--fields", FIELDS,
+ )
+ maint = pql(
+ "ticket", "list", "--under", MAINTENANCE, "--unblocked", "--leaf",
+ "--fields", FIELDS,
+ )
+
+ seen, rows = set(), []
+ for t in wip + phase + maint:
+ if t["id"] in seen or t["type"] not in ACTIONABLE_TYPES:
+ continue
+ if t["status"] in ("done", "cancelled"):
+ continue
+ seen.add(t["id"])
+ rows.append(t)
+
+ rows.sort(key=lambda t: t.get("updated_at") or "", reverse=True)
+ rows.sort(
+ key=lambda t: (
+ STATUS_RANK.get(t["status"], 3),
+ PRIORITY_RANK.get(t.get("priority"), 2),
+ )
+ )
+ overflow = max(0, len(rows) - args.cap)
+ rows = rows[: args.cap]
+
+ tpl = (Path(__file__).parent / "panel-template.html").read_text()
+ data = json.dumps(
+ {"rows": rows, "url": ARTIFACT_URL, "overflow": overflow},
+ ensure_ascii=False,
+ separators=(",", ":"),
+ ).replace("", "<\\/")
+ print(tpl.replace("__URL__", ARTIFACT_URL).replace("__DATA__", data))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.claude/skills/pql-board/support/panel-template.html b/.claude/skills/pql-board/support/panel-template.html
new file mode 100644
index 000000000..3278cecf5
--- /dev/null
+++ b/.claude/skills/pql-board/support/panel-template.html
@@ -0,0 +1,35 @@
+Interactive pql panel: work-in-progress and unblocked ready tickets with one-click pick-up and detail actions.
+
+
+
+
+