{"record":{"id":"278a09e3a81eaf0f","repo":"nexu-io/open-design","slug":"slide-plan-must-be-a-non-empty-json-array-of-slide","errorCode":null,"errorMessage":"slide_plan must be a non-empty JSON array of slides","messagePattern":"slide_plan must be a non-empty JSON array of slides","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"plugins/community/humanize-ppt/scripts/preview_outline_html.py","lineNumber":66,"sourceCode":"    \"takeaway\": \"收束\",\n}\n\nROLE_LEAVE_STATE = {\n    \"hook\": \"注意力被抓住，愿意继续听\",\n    \"context\": \"共享背景成立，知道为什么是现在\",\n    \"tension\": \"意识到旧理解有缺口，想要答案\",\n    \"method\": \"看到一条可执行的路径\",\n    \"proof\": \"相信路径真实有效，不是口号\",\n    \"takeaway\": \"带走一句可复述的判断，知道下一步\",\n}\n\n\ndef load_plan(path):\n    data = json.loads(Path(path).read_text(encoding=\"utf-8\"))\n    if isinstance(data, dict) and isinstance(data.get(\"slides\"), list):\n        data = data[\"slides\"]\n    if not isinstance(data, list) or not data:\n        raise ValueError(\"slide_plan must be a non-empty JSON array of slides\")\n    return data\n\n\ndef state_rows(plan):\n    \"\"\"Chain per-slide enter/leave audience states from the role sequence.\"\"\"\n    rows = []\n    enter = DECK_INITIAL_STATE\n    for slide in plan:\n        role = slide.get(\"role\", \"slide\")\n        leave = ROLE_LEAVE_STATE.get(role, enter)\n        rows.append({\n            \"slide_id\": slide.get(\"slide_id\", \"?\"),\n            \"role\": role,\n            \"role_label\": ROLE_LABELS.get(role, role),\n            \"title\": slide.get(\"title\", \"\"),\n            \"message\": slide.get(\"message\", \"\"),\n            \"speaker_intent\": slide.get(\"speaker_intent\", \"\"),\n            \"enter\": enter,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/plugins/community/humanize-ppt/scripts/preview_outline_html.py#L48-L84","documentation":"Raised by load_plan in preview_outline_html.py when the JSON file at the given path does not reduce to a non-empty list of slides. The function accepts either a bare JSON array of slides OR an object with a `slides` key (a common wrapper shape), but after unwrapping the result must be a list with at least one element. Anything else — an object without `slides`, an empty array, a scalar, null — raises ValueError.","triggerScenarios":"Passing a JSON file that is `{}` or `{\"meta\": ...}` without a slides array; an empty `[]`; a file containing a single slide object instead of an array; malformed/placeholder JSON produced by a half-finished pipeline stage; wrong file handed to the script (a config JSON instead of a plan JSON).","commonSituations":"Upstream slide-planning step wrote a status object instead of a plan; LLM produced a single slide dict rather than an array; user passed the deck spec where the outline plan was expected; empty plan from a filtering step that removed every slide.","solutions":["Open the JSON file and confirm its top-level shape: `python3 -c \"import json,sys; d=json.load(open(sys.argv[1])); print(type(d), len(d) if isinstance(d,(list,dict)) else '')\" plan.json`.","If the file is an object, ensure it has a non-empty `slides` array; if it has slides under a different key, restructure to `{\"slides\":[...]}` or a bare `[...]`.","Re-run the upstream planner so it emits a non-empty array of slide objects.","Validate the plan with a one-liner before invoking preview_outline_html: assert it is a list with len >= 1 and each entry is a dict."],"exampleFix":"// before (plan.json contains {})\npython3 preview_outline_html.py --plan plan.json\n# -> ValueError: slide_plan must be a non-empty JSON array of slides\n\n// after\n# plan.json:\n[{\"slide_id\":\"s1\",\"role\":\"hook\",\"title\":\"...\"}, {\"slide_id\":\"s2\",\"role\":\"tension\",\"title\":\"...\"}]\npython3 preview_outline_html.py --plan plan.json","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\n\ndef load_plan_checked(path: str):\n    data = json.loads(Path(path).read_text(encoding=\"utf-8\"))\n    if isinstance(data, dict) and isinstance(data.get(\"slides\"), list):\n        data = data[\"slides\"]\n    if not isinstance(data, list) or not data:\n        raise SystemExit(\n            f\"Plan must be a non-empty array of slides (got \"\n            f\"{type(data).__name__}, len={len(data) if hasattr(data,'__len__') else 'n/a'}).\"\n        )\n    if not all(isinstance(s, dict) for s in data):\n        raise SystemExit(\"Each slide must be a JSON object.\")\n    return data","typeGuard":"def is_slide_plan(data) -> bool:\n    if isinstance(data, dict):\n        data = data.get(\"slides\")\n    return isinstance(data, list) and len(data) > 0 and all(isinstance(s, dict) for s in data)","tryCatchPattern":"try:\n    plan = load_plan(plan_path)\nexcept ValueError as exc:\n    raise SystemExit(f\"Invalid slide plan: {exc}\") from exc","preventionTips":["Define and validate the plan JSON schema (jsonschema or pydantic) before rendering.","Ensure the upstream planner always emits a non-empty list (or {slides: [...]}).","Add a unit test on load_plan covering empty array, dict-without-slides, scalar, and null.","Refuse to proceed past plan loading until the shape is confirmed."],"tags":["json","validation","data-shape","humanize-ppt","preview"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}