import html, json
from pathlib import Path
def page(title, sections, path, review=None):
"""Write standalone accessible HTML. Review data is downloadable and edited as JSON."""
blocks = []
for heading, rows in sections:
if not isinstance(rows, list):
rows = [rows]
keys = list(dict.fromkeys((k for r in rows if isinstance(r, dict) for k in r)))
table = (
""
+ "".join(
('' + html.escape(str(k)) + " " for k in keys)
)
+ "
"
)
for row in rows:
table += (
""
+ "".join(
(
""
+ html.escape(
json.dumps(row.get(k), ensure_ascii=False)
if isinstance(row.get(k), (dict, list))
else str(row.get(k, ""))
)
+ " "
for k in keys
)
)
+ " "
)
blocks.append(
""
+ html.escape(heading)
+ ' "
)
panel = ""
if review is not None:
panel = (
'Review decisions Edit this JSON, then download it. Re-run the CLI with --decisions decisions.json to validate and consume your review.
Decisions JSON Download decisions.json '
)
js = "document.querySelector('#filter').addEventListener('input',e=>document.querySelectorAll('tbody tr').forEach(r=>r.hidden=!r.textContent.toLowerCase().includes(e.target.value.toLowerCase())));const b=document.querySelector('#download');if(b)b.addEventListener('click',()=>{try{const raw=document.querySelector('#decisions').value;JSON.parse(raw);const a=document.createElement('a');const u=URL.createObjectURL(new Blob([raw],{type:'application/json'}));a.href=u;a.download='decisions.json';a.click();setTimeout(()=>URL.revokeObjectURL(u),1000);document.querySelector('#status').textContent='Downloaded. Re-run the CLI to validate these decisions.';}catch(e){document.querySelector('#status').textContent=e.message;}});"
css = ":root{color-scheme:light dark;font:17px/1.55 system-ui}body{max-width:1100px;margin:0 auto;padding:28px;background:light-dark(#f7f8fb,#15191f);color:light-dark(#202837,#e7edf5)}h1{font-size:clamp(1.8rem,5vw,3rem);line-height:1.1}section{margin:28px 0;padding:20px;border:1px solid #7c879a;border-radius:12px}.scroll{overflow:auto;max-width:100%}table{border-collapse:collapse;width:100%;min-width:640px}td,th{min-width:8rem;text-align:left;padding:10px;border-bottom:1px solid #7c879a;vertical-align:top}td{max-width:420px;overflow-wrap:anywhere}textarea{box-sizing:border-box;width:100%;font:14px/1.5 monospace}input,button{font:inherit;padding:10px}button{cursor:pointer;margin:10px 0}output{display:block}svg{display:block;min-width:640px;max-width:100%;height:auto}"
Path(path).write_text(
''
+ html.escape(title)
+ " '
+ "".join(blocks)
+ panel
+ "",
encoding="utf-8",
)