#!/usr/bin/env python3 """Render the pql ticket board as a self-contained interactive HTML snapshot. Companion to clide's two-column board view, for sessions running in the Claude desktop app: the output is deployed as a claude.ai Artifact (stable URL across redeploys), giving always-visible pql board + ticket-detail reference without clide. The page is fully client-side over embedded JSON — artifacts are network-sandboxed, so this is a *snapshot*; regenerate + redeploy to refresh. Data comes from pql's native JSON output only (no output parsing): - `pql ticket list --full` → every ticket, whole rows - `pql ticket show --with-blockers` → dep edges, batched, for non-terminal tickets only (native list output carries no dep edges — if pql ever grows `list --with-blockers`, drop the batching here; candidate upstream feature request per the no-ad-hoc-wrappers rule) - `pql plan status` → header counts Usage: tooling/pql-board-html [OUTPUT.html] (default output: /tmp/pql-board.html) The emitted file is an Artifact-ready fragment: no doctype/html/head/body wrapper (the Artifact pipeline adds those); it starts with + <style>. """ import html import json import subprocess import sys from datetime import datetime, timezone NON_TERMINAL = ["in_progress", "review", "ready", "backlog"] SHOW_CHUNK = 40 def pql(*args): out = subprocess.run( ["pql", *args], capture_output=True, text=True, check=True ).stdout return json.loads(out) def fetch(): tickets = pql("ticket", "list", "--full") plan = pql("plan", "status") ids = [t["id"] for t in tickets if t.get("status") in NON_TERMINAL] blockers = {} for i in range(0, len(ids), SHOW_CHUNK): chunk = ids[i : i + SHOW_CHUNK] shown = pql("ticket", "show", ",".join(chunk), "--with-blockers") if isinstance(shown, dict): shown = [shown] for row in shown: edges = row.get("blockers") or row.get("blocked_by") or [] blockers[row["id"]] = [ e["id"] if isinstance(e, dict) else e for e in edges ] return tickets, blockers, plan PAGE = """<title>Settled Reach — pql board

Settled Reach · pql

snapshot __STAMP__
Select a ticket.
""" def main(): out_path = sys.argv[1] if len(sys.argv) > 1 else "/tmp/pql-board.html" tickets, blockers, plan = fetch() stamp = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M") data = json.dumps( {"tickets": tickets, "blockers": blockers, "plan": plan}, ensure_ascii=False, separators=(",", ":"), ).replace("