MicroSim Utilities Overview This meta-skill provides utility functions for managing and maintaining MicroSims in intelligent textbook projects. It consolidates four utility skills into a single entry point with on-demand loading of specific utility guides. When to Use This Skill Use this skill when users request: - Validating MicroSim quality and standards - Capturing screenshots for preview images - Adding or managing icons for MicroSims - Generating index pages for MicroSim directories - Quality scoring and standardization checks - Synchronizing iframe heights from JS source files - Setting…

, frontmatter, re.MULTILINE)\n title = title_match.group(1).strip() if title_match else item\n \n # get description\n desc_match = re.search(r'^description:\\s*(.+)

MicroSim Utilities Overview This meta-skill provides utility functions for managing and maintaining MicroSims in intelligent textbook projects. It consolidates four utility skills into a single entry point with on-demand loading of specific utility guides. When to Use This Skill Use this skill when users request: - Validating MicroSim quality and standards - Capturing screenshots for preview images - Adding or managing icons for MicroSims - Generating index pages for MicroSim directories - Quality scoring and standardization checks - Synchronizing iframe heights from JS source files - Setting…

, frontmatter, re.MULTILINE)\n description = desc_match.group(1).strip() if desc_match else f\"Interactive MicroSim for {title.lower()}.\"\n \n # rewrite frontmatter if description missing\n if not desc_match:\n new_frontmatter = frontmatter.rstrip() + f\"\\ndescription: {description}\\n\"\n new_content = parts[0] + \"---\" + new_frontmatter + \"---\" + parts[2]\n with open(index_file, 'w') as f:\n f.write(new_content)\n \n sims.append({\n \"name\": item,\n \"title\": title,\n \"description\": description\n })\n \n # check screenshot\n screenshot_file = os.path.join(sim_dir, f\"{item}.png\")\n if not os.path.exists(screenshot_file):\n missing_screenshots.append(item)\n\n# Sort by title\nsims.sort(key=lambda x: x[\"title\"].lower())\n\n# Generate index.md\nindex_content = \"\"\"---\ntitle: List of MicroSims for Theory of Knowledge\ndescription: A list of all the MicroSims used in the Theory of Knowledge course\nimage: /sims/index-screen-image.png\nog:image: /sims/index-screen-image.png\nhide:\n toc\n---\n\n# List of MicroSims for Theory of Knowledge\n\nInteractive Micro Simulations to help students learn Theory of Knowledge fundamentals.\n\n\u003cdiv class=\"grid cards\" markdown>\n\n\"\"\"\n\nfor sim in sims:\n index_content += f\"\"\"- **[{sim['title']}](./{sim['name']}/index.md)**\\n\\n\"\"\"\n index_content += f\"\"\" ![{sim['title']}](./{sim['name']}/{sim['name']}.png)\\n\\n\"\"\"\n index_content += f\"\"\" {sim['description']}\\n\\n\"\"\"\n\nindex_content += \"\u003c/div>\\n\"\n\nwith open(os.path.join(base_dir, \"index.md\"), 'w') as f:\n f.write(index_content)\n\n# write TODO.md\ntodo_path = os.path.join(base_dir, \"TODO.md\")\nif missing_screenshots:\n todo_content = \"# MicroSim Screenshot TODO\\n\\nThis file tracks MicroSims that need screenshots captured.\\n\\n## Missing Screenshots\\n\\nRun the following commands to capture missing screenshots:\\n\\n\"\n for item in missing_screenshots:\n todo_content += f\"### {item}\\n```bash\\n~/.local/bin/bk-capture-screenshot docs/sims/{item}\\n```\\n\\n\"\n with open(todo_path, 'w') as f:\n f.write(todo_content)\n\nprint(f\"Processed {len(sims)} MicroSims.\")\nprint(f\"Missing screenshots logged: {len(missing_screenshots)}\")\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3507,"content_sha256":"d0ff31a8f92cbd201a4fc91dbf7d2ef2a4502e6cc48b1e1e84469927d5e01280"},{"filename":"scripts/scaffold-microsims-from-todo.py","content":"#!/usr/bin/env python3\n\"\"\"\nScaffold MicroSim directories from TODO JSON specification files.\n\nFor each docs/sims/TODO/\u003csim-id>.json that does NOT already have a\ndocs/sims/\u003csim-id>/main.html, create a new directory with stub files:\n - main.html (placeholder canvas + spec embedded as a comment)\n - index.md (frontmatter, learning objective, iframe embed, spec)\n - metadata.json (mapped from the TODO JSON)\n\nUsage:\n python scaffold-microsims-from-todo.py [--project-dir /path/to/project] [--force]\n\n--force overwrites existing scaffold files (but never main.html if it\nalready exists, since that may contain a real implementation).\n\"\"\"\n\nimport argparse\nimport glob\nimport json\nimport os\nimport sys\nfrom datetime import date\n\n\ndef find_project_root(start_path):\n current = os.path.abspath(start_path)\n while True:\n if os.path.isfile(os.path.join(current, \"mkdocs.yml\")):\n return current\n parent = os.path.dirname(current)\n if parent == current:\n return None\n current = parent\n\n\nHTML_TEMPLATE = \"\"\"\u003c!DOCTYPE html>\n\u003chtml lang=\"en\">\n\u003chead>\n \u003cmeta charset=\"UTF-8\">\n \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003ctitle>{title}\u003c/title>\n \u003cstyle>\n * {{ margin: 0; padding: 0; box-sizing: border-box; }}\n body {{\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n background: #f8f9fa;\n color: #212529;\n }}\n #container {{\n width: 100%;\n max-width: 900px;\n margin: 0 auto;\n padding: 20px;\n background: white;\n }}\n h1 {{\n font-size: 22px;\n margin-bottom: 10px;\n color: #1a3a6c;\n }}\n .placeholder {{\n border: 2px dashed #adb5bd;\n border-radius: 8px;\n padding: 40px 20px;\n text-align: center;\n color: #6c757d;\n margin: 20px 0;\n min-height: 400px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n }}\n .placeholder .badge {{\n display: inline-block;\n background: #ffc107;\n color: #212529;\n padding: 4px 12px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n margin-bottom: 12px;\n }}\n .placeholder h2 {{\n font-size: 18px;\n color: #495057;\n margin-bottom: 8px;\n }}\n .placeholder p {{\n font-size: 14px;\n max-width: 480px;\n }}\n .meta {{\n margin-top: 20px;\n font-size: 13px;\n color: #6c757d;\n border-top: 1px solid #dee2e6;\n padding-top: 12px;\n }}\n .meta strong {{ color: #495057; }}\n \u003c/style>\n\u003c/head>\n\u003cbody>\n \u003cdiv id=\"container\">\n \u003ch1>{title}\u003c/h1>\n \u003cdiv class=\"placeholder\">\n \u003cspan class=\"badge\">SCAFFOLD\u003c/span>\n \u003ch2>MicroSim Not Yet Implemented\u003c/h2>\n \u003cp>This MicroSim has been scaffolded from its specification but the\n interactive content has not been built yet. See the chapter source\n and the \u003ccode>index.md\u003c/code> in this directory for the full spec.\u003c/p>\n \u003c/div>\n \u003cdiv class=\"meta\">\n \u003cp>\u003cstrong>Sim ID:\u003c/strong> {sim_id}\u003c/p>\n \u003cp>\u003cstrong>Library:\u003c/strong> {library}\u003c/p>\n \u003cp>\u003cstrong>Bloom Level:\u003c/strong> {bloom_level}\u003c/p>\n \u003cp>\u003cstrong>Learning Objective:\u003c/strong> {learning_objective}\u003c/p>\n \u003c/div>\n \u003c/div>\n\n \u003c!--\n SPECIFICATION (from chapter index.md):\n\n{spec_block}\n -->\n\u003c/body>\n\u003c/html>\n\"\"\"\n\n\nINDEX_MD_TEMPLATE = \"\"\"---\ntitle: {title}\ndescription: {description}\nstatus: scaffold\nlibrary: {library}\nbloom_level: {bloom_level}\n---\n\n# {title}\n\n\n\n\u003ciframe src=\"main.html\" width=\"100%\" height=\"600\">\u003c/iframe>\n\n[Run MicroSim in Fullscreen](main.html){{ .md-button .md-button--primary }}\n\n## Specification\n\nThe full specification below is extracted from\n[Chapter {chapter_number}: {chapter_title}](../../chapters/{chapter_dir}/index.md).\n\n```text\n{specification}\n```\n\n## Related Resources\n\n- [Chapter {chapter_number}: {chapter_title}](../../chapters/{chapter_dir}/index.md)\n\"\"\"\n\n\ndef make_metadata(spec):\n return {\n \"title\": spec[\"diagram_name\"],\n \"description\": (spec.get(\"learning_objective\") or spec[\"diagram_name\"]),\n \"creator\": \"Dementia Education Project\",\n \"date\": str(date.today()),\n \"subject\": [\"dementia\"],\n \"type\": \"Interactive Simulation\",\n \"format\": \"text/html\",\n \"language\": \"en-US\",\n \"rights\": \"CC BY 4.0\",\n \"identifier\": spec[\"sim_id\"],\n \"library\": spec.get(\"library\") or \"TBD\",\n \"bloomLevel\": spec.get(\"bloom_level\") or \"TBD\",\n \"bloomVerb\": spec.get(\"bloom_verb\") or \"TBD\",\n \"completion_status\": \"scaffold\",\n \"chapter_number\": spec.get(\"chapter_number\"),\n \"chapter_title\": spec.get(\"chapter_title\"),\n \"chapter_dir\": spec.get(\"chapter_dir\"),\n }\n\n\ndef indent_block(text, indent=\" \"):\n return \"\\n\".join(indent + line for line in text.splitlines())\n\n\ndef scaffold_one(spec, sims_dir, force=False):\n sim_id = spec[\"sim_id\"]\n sim_dir = os.path.join(sims_dir, sim_id)\n main_html = os.path.join(sim_dir, \"main.html\")\n index_md = os.path.join(sim_dir, \"index.md\")\n meta_json = os.path.join(sim_dir, \"metadata.json\")\n\n # If main.html already exists, the sim is implemented; skip entirely.\n if os.path.isfile(main_html):\n return \"skipped-implemented\"\n\n os.makedirs(sim_dir, exist_ok=True)\n\n title = spec[\"diagram_name\"]\n library = spec.get(\"library\") or \"TBD\"\n bloom_level = spec.get(\"bloom_level\") or \"TBD\"\n bloom_verb = spec.get(\"bloom_verb\") or \"TBD\"\n learning_objective = spec.get(\"learning_objective\") or \"TBD\"\n description = (spec.get(\"learning_objective\") or title).split(\"\\n\")[0]\n specification = spec.get(\"specification\") or \"\"\n\n html_out = HTML_TEMPLATE.format(\n title=title,\n sim_id=sim_id,\n library=library,\n bloom_level=bloom_level,\n learning_objective=learning_objective,\n spec_block=indent_block(specification, \" \"),\n )\n\n md_out = INDEX_MD_TEMPLATE.format(\n title=title,\n description=description,\n library=library,\n bloom_level=bloom_level,\n bloom_verb=bloom_verb,\n learning_objective=learning_objective,\n chapter_number=spec.get(\"chapter_number\") or \"?\",\n chapter_title=spec.get(\"chapter_title\") or \"\",\n chapter_dir=spec.get(\"chapter_dir\") or \"\",\n specification=specification,\n )\n\n # Write main.html (we know it doesn't exist - early return above)\n with open(main_html, \"w\", encoding=\"utf-8\") as f:\n f.write(html_out)\n\n # index.md - write if missing or force\n if force or not os.path.isfile(index_md):\n with open(index_md, \"w\", encoding=\"utf-8\") as f:\n f.write(md_out)\n\n # metadata.json - write if missing or force\n if force or not os.path.isfile(meta_json):\n with open(meta_json, \"w\", encoding=\"utf-8\") as f:\n json.dump(make_metadata(spec), f, indent=2, ensure_ascii=False)\n\n return \"scaffolded\"\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Scaffold MicroSim directories from TODO JSON specs.\"\n )\n parser.add_argument(\n \"--project-dir\",\n help=\"Path to the project root (containing mkdocs.yml). Auto-detected if omitted.\",\n )\n parser.add_argument(\n \"--force\",\n action=\"store_true\",\n help=\"Overwrite existing index.md and metadata.json (never overwrites main.html).\",\n )\n args = parser.parse_args()\n\n if args.project_dir:\n project_root = os.path.abspath(args.project_dir)\n else:\n project_root = find_project_root(os.path.dirname(os.path.abspath(__file__)))\n\n if not project_root or not os.path.isfile(os.path.join(project_root, \"mkdocs.yml\")):\n print(\"ERROR: Could not find project root (no mkdocs.yml found).\", file=sys.stderr)\n sys.exit(1)\n\n sims_dir = os.path.join(project_root, \"docs\", \"sims\")\n todo_dir = os.path.join(sims_dir, \"TODO\")\n\n if not os.path.isdir(todo_dir):\n print(f\"ERROR: TODO directory not found: {todo_dir}\", file=sys.stderr)\n sys.exit(1)\n\n todo_files = sorted(glob.glob(os.path.join(todo_dir, \"*.json\")))\n\n scaffolded = 0\n skipped = 0\n for todo_path in todo_files:\n with open(todo_path, \"r\", encoding=\"utf-8\") as f:\n spec = json.load(f)\n result = scaffold_one(spec, sims_dir, force=args.force)\n if result == \"scaffolded\":\n scaffolded += 1\n print(f\" scaffolded: {spec['sim_id']}\")\n else:\n skipped += 1\n print(f\" skipped (already implemented): {spec['sim_id']}\")\n\n print()\n print(f\"Project root: {project_root}\")\n print(f\"TODO specs processed: {len(todo_files)}\")\n print(f\"Scaffolded: {scaffolded}\")\n print(f\"Skipped (already implemented): {skipped}\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9231,"content_sha256":"27de9f94231b443b3c570a1465efe4059271d5f463bf1b1a3be9bb1f830cf247"},{"filename":"scripts/sync-iframe-heights.py","content":"#!/usr/bin/env python3\n\"\"\"Synchronize iframe heights across MicroSim index.md files and chapter files.\n\nUses the CANVAS_HEIGHT comment in each sim's .js file as the single source of\ntruth. If CANVAS_HEIGHT is missing, computes it from drawHeight + controlHeight\n(or other named variables) and inserts the comment on line 2 of the .js file.\n\nThen updates iframe height attributes in:\n 1. The sim's own docs/sims/\u003csim-id>/index.md\n 2. Any chapter file (docs/chapters/*/index.md) that embeds the sim\n\nIframe height = CANVAS_HEIGHT + 2 (2px for the iframe border)\n\nUsage:\n python3 sync-iframe-heights.py --project-dir /path/to/project --verbose\n python3 sync-iframe-heights.py --project-dir /path/to/project --sim gradient-explorer\n python3 sync-iframe-heights.py --project-dir /path/to/project --dry-run --verbose\n\"\"\"\n\nimport argparse\nimport glob\nimport os\nimport re\nimport sys\n\n# ── ANSI helpers ─────────────────────────────────────────────────────\nGREEN = \"\\033[92m\"\nRED = \"\\033[91m\"\nYELLOW = \"\\033[93m\"\nCYAN = \"\\033[96m\"\nBOLD = \"\\033[1m\"\nDIM = \"\\033[2m\"\nRESET = \"\\033[0m\"\nCHECK = \"\\u2714\"\nARROW = \"\\u2192\"\n\n\n# ── Height detection ─────────────────────────────────────────────────\n\ndef read_canvas_height_comment(js_content):\n \"\"\"Extract CANVAS_HEIGHT from a // CANVAS_HEIGHT: \u003cint> comment.\n\n Returns the integer value or None if not found.\n Searches within the first 15 lines only.\n \"\"\"\n for line in js_content.splitlines()[:15]:\n m = re.match(r'^\\s*//\\s*CANVAS_HEIGHT\\s*[:=]\\s*(\\d+)', line)\n if m:\n return int(m.group(1))\n return None\n\n\ndef compute_canvas_height(js_content):\n \"\"\"Compute CANVAS_HEIGHT from named height variables in the JS file.\n\n Looks for patterns like:\n let drawHeight = 400;\n let controlHeight = 80;\n let graphHeight = 180; (optional, for sims with a graph panel)\n\n Returns (height, explanation) or (None, reason).\n \"\"\"\n draw_h = _extract_var(js_content, r'(?:drawHeight|draw_height)')\n ctrl_h = _extract_var(js_content, r'(?:controlHeight|control_height|controlAreaHeight)')\n graph_h = _extract_var(js_content, r'(?:graphHeight|graph_height)')\n\n if draw_h is not None and ctrl_h is not None:\n total = draw_h + ctrl_h + (graph_h or 0)\n parts = f\"drawHeight({draw_h}) + controlHeight({ctrl_h})\"\n if graph_h:\n parts += f\" + graphHeight({graph_h})\"\n return total, parts\n\n # Fallback: canvasHeight directly\n canvas_h = _extract_var(js_content, r'(?:canvasHeight|canvas_height|containerHeight)')\n if canvas_h is not None:\n return canvas_h, f\"canvasHeight({canvas_h})\"\n\n # Fallback: createCanvas(w, h)\n m = re.search(r'createCanvas\\s*\\(\\s*\\w+\\s*,\\s*(\\d+)\\s*\\)', js_content)\n if m:\n return int(m.group(1)), f\"createCanvas height({m.group(1)})\"\n\n return None, \"could not detect height variables\"\n\n\ndef _extract_var(js_content, pattern):\n \"\"\"Extract an integer value from a JS variable declaration matching pattern.\"\"\"\n m = re.search(\n rf'(?:let|const|var)\\s+{pattern}\\s*=\\s*(\\d+)',\n js_content\n )\n return int(m.group(1)) if m else None\n\n\ndef insert_canvas_height_comment(js_path, height, dry_run=False):\n \"\"\"Insert a // CANVAS_HEIGHT: \u003cheight> comment on line 2 of the JS file.\n\n Preserves the existing first line (typically a title comment).\n Returns True if the file was modified.\n \"\"\"\n with open(js_path, 'r') as f:\n lines = f.readlines()\n\n comment = f\"// CANVAS_HEIGHT: {height}\\n\"\n\n # Check if line 2 already has a CANVAS_HEIGHT comment\n if len(lines) > 1 and re.match(r'^\\s*//\\s*CANVAS_HEIGHT', lines[1]):\n # Update existing\n if lines[1].strip() == comment.strip():\n return False # already correct\n if not dry_run:\n lines[1] = comment\n with open(js_path, 'w') as f:\n f.writelines(lines)\n return True\n\n # Insert after first line\n if not dry_run:\n lines.insert(1, comment)\n with open(js_path, 'w') as f:\n f.writelines(lines)\n return True\n\n\n# ── Iframe updating ──────────────────────────────────────────────────\n\nIFRAME_HEIGHT_RE = re.compile(\n r'(\u003ciframe\\b[^>]*?)height\\s*=\\s*[\"\\']?(\\d+)\\s*(?:px)?[\"\\']?([^>]*>)',\n re.IGNORECASE\n)\n\n\ndef update_iframes_in_file(filepath, sim_id, target_height, dry_run=False):\n \"\"\"Update iframe height for a specific sim_id in a markdown file.\n\n Matches iframes whose src contains the sim_id.\n Returns list of (old_height, new_height) for each changed iframe.\n \"\"\"\n with open(filepath, 'r') as f:\n content = f.read()\n\n changes = []\n new_content = content\n\n for m in IFRAME_HEIGHT_RE.finditer(content):\n full_match = m.group(0)\n prefix = m.group(1)\n old_h = int(m.group(2))\n suffix = m.group(3)\n\n # Check if this iframe is for our sim\n if sim_id not in full_match:\n # For sim's own index.md, also match src=\"main.html\"\n if 'main.html' in full_match and filepath.endswith(\n os.path.join(sim_id, 'index.md')\n ):\n pass # This is the sim's own iframe\n else:\n continue\n\n if old_h == target_height:\n continue\n\n new_iframe = f'{prefix}height=\"{target_height}\"{suffix}'\n new_content = new_content.replace(full_match, new_iframe, 1)\n changes.append((old_h, target_height))\n\n if changes and not dry_run:\n with open(filepath, 'w') as f:\n f.write(new_content)\n\n return changes\n\n\n# ── Main logic ───────────────────────────────────────────────────────\n\ndef sync_sim(sim_dir, sim_name, chapters_dir, dry_run=False, verbose=False):\n \"\"\"Synchronize iframe heights for a single MicroSim.\n\n Returns a dict with sync results.\n \"\"\"\n js_path = os.path.join(sim_dir, f'{sim_name}.js')\n if not os.path.isfile(js_path):\n if verbose:\n print(f\" {DIM}SKIP {sim_name}: no JS file{RESET}\")\n return None\n\n with open(js_path, 'r') as f:\n js_content = f.read()\n\n # Step 1: Get or compute CANVAS_HEIGHT\n canvas_height = read_canvas_height_comment(js_content)\n comment_inserted = False\n\n if canvas_height is None:\n canvas_height, explanation = compute_canvas_height(js_content)\n if canvas_height is None:\n if verbose:\n print(f\" {YELLOW}WARN{RESET} {sim_name}: {explanation}\")\n return None\n\n # Insert the comment into the JS file\n comment_inserted = insert_canvas_height_comment(\n js_path, canvas_height, dry_run\n )\n label = \"WOULD INSERT\" if dry_run else \"INSERTED\"\n print(\n f\" {CYAN}{label}{RESET} {sim_name}: \"\n f\"// CANVAS_HEIGHT: {canvas_height} (from {explanation})\"\n )\n else:\n # Validate: does the comment match the actual variables?\n computed, explanation = compute_canvas_height(js_content)\n if computed is not None and computed != canvas_height:\n if verbose:\n print(\n f\" {YELLOW}NOTE{RESET} {sim_name}: CANVAS_HEIGHT comment \"\n f\"({canvas_height}) differs from computed ({computed} = {explanation})\"\n )\n\n target_iframe_height = canvas_height + 2\n total_changes = 0\n\n # Step 2: Update sim's own index.md\n sim_index = os.path.join(sim_dir, 'index.md')\n if os.path.isfile(sim_index):\n changes = update_iframes_in_file(\n sim_index, sim_name, target_iframe_height, dry_run\n )\n for old_h, new_h in changes:\n label = \"WOULD FIX\" if dry_run else \"FIXED\"\n print(\n f\" {GREEN}{label}{RESET} {sim_name}/index.md: \"\n f\"{old_h} {ARROW} {new_h}\"\n )\n total_changes += 1\n\n # Step 3: Update chapter files that embed this sim\n if os.path.isdir(chapters_dir):\n for ch_index in sorted(glob.glob(\n os.path.join(chapters_dir, '*/index.md')\n )):\n with open(ch_index, 'r') as f:\n ch_content = f.read()\n\n # Only process if this chapter references our sim\n if sim_name not in ch_content:\n continue\n\n changes = update_iframes_in_file(\n ch_index, sim_name, target_iframe_height, dry_run\n )\n ch_name = os.path.basename(os.path.dirname(ch_index))\n for old_h, new_h in changes:\n label = \"WOULD FIX\" if dry_run else \"FIXED\"\n print(\n f\" {GREEN}{label}{RESET} chapters/{ch_name}/index.md: \"\n f\"{old_h} {ARROW} {new_h}\"\n )\n total_changes += 1\n\n if total_changes == 0 and not comment_inserted and verbose:\n print(\n f\" {DIM}OK {sim_name}: \"\n f\"CANVAS_HEIGHT={canvas_height}, iframe={target_iframe_height}px{RESET}\"\n )\n\n return {\n 'sim': sim_name,\n 'canvas_height': canvas_height,\n 'iframe_height': target_iframe_height,\n 'comment_inserted': comment_inserted,\n 'iframes_updated': total_changes,\n }\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=(\n \"Synchronize iframe heights from CANVAS_HEIGHT in JS files \"\n \"to sim index.md and chapter index.md files.\"\n )\n )\n parser.add_argument(\n '--project-dir', default=None,\n help='Project root containing mkdocs.yml (auto-detect if omitted)'\n )\n parser.add_argument(\n '--sim', default=None,\n help='Sync a single sim by name (default: all sims)'\n )\n parser.add_argument(\n '--dry-run', action='store_true',\n help='Preview changes without writing files'\n )\n parser.add_argument(\n '--verbose', action='store_true',\n help='Show status for all sims, not just changes'\n )\n args = parser.parse_args()\n\n # Resolve project root\n if args.project_dir:\n project_dir = os.path.abspath(args.project_dir)\n else:\n d = os.path.abspath('.')\n while d != os.path.dirname(d):\n if os.path.isfile(os.path.join(d, 'mkdocs.yml')):\n project_dir = d\n break\n d = os.path.dirname(d)\n else:\n print(\"ERROR: mkdocs.yml not found. Use --project-dir.\", file=sys.stderr)\n sys.exit(1)\n\n sims_dir = os.path.join(project_dir, 'docs', 'sims')\n chapters_dir = os.path.join(project_dir, 'docs', 'chapters')\n\n if not os.path.isdir(sims_dir):\n print(f\"ERROR: {sims_dir} not found.\", file=sys.stderr)\n sys.exit(1)\n\n print(f\"{BOLD}Project root:{RESET} {project_dir}\")\n\n # Collect sim directories\n if args.sim:\n sim_dirs = [os.path.join(sims_dir, args.sim)]\n if not os.path.isdir(sim_dirs[0]):\n print(f\"ERROR: sim directory {sim_dirs[0]} not found.\", file=sys.stderr)\n sys.exit(1)\n else:\n sim_dirs = sorted(\n d for d in glob.glob(os.path.join(sims_dir, '*/'))\n if os.path.basename(d.rstrip('/')) != 'TODO'\n )\n\n # Process each sim\n stats = {\n 'total': 0,\n 'comments_inserted': 0,\n 'iframes_updated': 0,\n 'skipped': 0,\n }\n\n for sim_dir in sim_dirs:\n sim_name = os.path.basename(sim_dir.rstrip('/'))\n result = sync_sim(\n sim_dir, sim_name, chapters_dir,\n dry_run=args.dry_run, verbose=args.verbose\n )\n\n if result is None:\n stats['skipped'] += 1\n else:\n stats['total'] += 1\n stats['comments_inserted'] += 1 if result['comment_inserted'] else 0\n stats['iframes_updated'] += result['iframes_updated']\n\n # Summary\n print()\n verb = \"Would sync\" if args.dry_run else \"Synced\"\n print(f\"{GREEN}{CHECK}{RESET} {verb} {stats['total']} sims\")\n if stats['comments_inserted']:\n verb2 = \"Would insert\" if args.dry_run else \"Inserted\"\n print(f\" {verb2} CANVAS_HEIGHT comment in {stats['comments_inserted']} JS files\")\n if stats['iframes_updated']:\n verb3 = \"Would update\" if args.dry_run else \"Updated\"\n print(f\" {verb3} {stats['iframes_updated']} iframe heights\")\n if stats['skipped']:\n print(f\" Skipped {stats['skipped']} (no JS file)\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12850,"content_sha256":"529f49ed57a3e6df344de363e2be48b8e49dbfe402f4c289d00f8a52a4c9cd62"},{"filename":"TODO.md","content":"# MicroSim Utils - TODO\n\n## Pending Enhancements\n\n### Add iframe Example Path Validation to Standardization Check\n\n**Priority:** Medium\n**Added:** 2026-01-01\n\n**Description:**\nAdd a new standardization check that validates sample iframe example URLs in `index.md` files to ensure they reference the correct GitHub Pages deployment path.\n\n**Problem Discovered:**\nDuring a routine check of `docs/sims/*/index.md` files, several MicroSims had incorrect iframe example paths:\n- Wrong repository name (e.g., `microsims` instead of `intro-to-physics-course`)\n- Typos in repository name (e.g., `intro-to-physics` missing `-course`)\n- Wrong directory name (e.g., `basic-fft` instead of `fft-basic`)\n\n**Proposed Implementation:**\n\n1. Add to `references/standardization.md` checklist:\n ```markdown\n #### 13. Sample iframe URL Validation\n - Check that the sample iframe example URL matches the project's GitHub Pages URL\n - Expected format: `https://\u003cusername>.github.io/\u003crepo-name>/sims/\u003cmicrosim-name>/main.html`\n - The `\u003cmicrosim-name>` in the URL must match the directory name\n - The `\u003crepo-name>` should be extracted from `mkdocs.yml` or detected from git remote\n - If mismatch found: Add TODO to fix iframe example URL\n ```\n\n2. Add scoring to the rubric table:\n ```markdown\n |iframe URL correct|The sample iframe URL matches the project's GitHub Pages path|3|\n ```\n\n3. Implementation details:\n - Extract `site_url` from `mkdocs.yml` to determine the correct base URL\n - Parse the iframe src attribute from the code block\n - Verify the URL contains the correct repository name\n - Verify the microsim directory name in the URL matches the actual directory\n\n**Detection Command:**\n```bash\n# Find incorrect paths (adjust exclusions as needed)\ngrep -r \"github.io\" docs/sims/*/index.md | grep -v \"\u003cexpected-repo-name>\" | grep -v \"external-references\"\n```\n\n**Reference:**\nSee `/logs/check-iframe-example-path.md` in the intro-to-physics-course project for the full analysis that identified this issue.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2020,"content_sha256":"5046cda64f45e5c64f57258e6d869feb4054a4cf7d14a1bb3dfc0a5b1d1f8e98"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"MicroSim Utilities","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"This meta-skill provides utility functions for managing and maintaining MicroSims in intelligent textbook projects. It consolidates four utility skills into a single entry point with on-demand loading of specific utility guides.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill when users request:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validating MicroSim quality and standards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Capturing screenshots for preview images","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adding or managing icons for MicroSims","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generating index pages for MicroSim directories","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Quality scoring and standardization checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Synchronizing iframe heights from JS source files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Setting up runtime iframe auto-resize via postMessage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scaffolding MicroSim directories (main.html, index.md, metadata.json) from TODO JSON specs","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 1: Identify Utility Type","type":"text"}]},{"type":"paragraph","content":[{"text":"Match the user's request to the appropriate utility guide:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Routing Table","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trigger Keywords","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Guide File","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"standardize, quality, validate, score, check, audit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/standardization.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quality validation and scoring","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"screenshot, capture, preview, image, thumbnail","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/screen-capture.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Automated screenshot generation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"icons, add icons, favicon, logo","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/add-icons.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Icon management for MicroSims","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"index page, microsim list, grid, directory, catalog, update the microsim listings, update the list of microsims, create a grid view, generate a listing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/index-generator.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generate index page with grid cards","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TODO, todo json, extract specs, diagram specs, unimplemented, create microsim todo, todo files, extract diagrams, unimplemented microsims","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/create-microsim-todo-json-files.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extract unimplemented diagram specs into TODO JSON files","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scaffold microsims, scaffold from todo, scaffold sims, create microsim stubs, generate microsim scaffolding, stub out microsims, create scaffold files, generate scaffold from json, microsim stubs from todo","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/scaffold-microsims-from-todo.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generate ","type":"text"},{"text":"main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"index.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"metadata.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" stub files for each TODO JSON spec that does not yet have an implementation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fix iframe heights, sync iframe heights, correct iframe heights, iframe height, canvas height, sync heights, update iframe heights","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/sync-iframe-heights.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Synchronize iframe heights from CANVAS_HEIGHT in JS files to sim and chapter index.md files","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"iframe auto height, iframe auto resize, iframe postMessage, runtime iframe resize, microsim auto resize, auto-size iframe, iframe self-resize","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/iframe-auto-height.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Runtime postMessage protocol so embedded MicroSims report their own height to the parent page","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Decision Tree","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Need to check MicroSim quality/standards?\n → YES: standardization.md\n\nNeed to capture screenshots for previews?\n → YES: screen-capture.md\n\nNeed to add or manage icons?\n → YES: add-icons.md\n\nNeed to generate/update the MicroSim index page?\n → YES: index-generator.md\n\nNeed to extract unimplemented diagram specs into TODO files?\n → YES: Run scripts/create-microsim-todo-json-files.py\n\nNeed to scaffold sim directories (main.html, index.md, metadata.json) from those TODO JSON files?\n → YES: Run scripts/scaffold-microsims-from-todo.py\n\nNeed to fix, sync, or correct iframe heights?\n → YES: Run scripts/sync-iframe-heights.py\n\nNeed iframes to auto-resize at runtime via postMessage?\n → YES: references/iframe-auto-height.md","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 2: Load the Matched Guide or Run the Script","type":"text"}]},{"type":"paragraph","content":[{"text":"For reference-based utilities, read the corresponding guide file from ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":" and follow its workflow.","type":"text"}]},{"type":"paragraph","content":[{"text":"For Python script utilities, run the script directly:","type":"text"}]},{"type":"paragraph","content":[{"text":"TODO JSON extractor:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python /path/to/skills/microsim-utils/scripts/create-microsim-todo-json-files.py --project-dir /path/to/project","type":"text"}]},{"type":"paragraph","content":[{"text":"Report the summary output to the user (chapters scanned, total specs found, already implemented, TODO files written, output directory).","type":"text"}]},{"type":"paragraph","content":[{"text":"Scaffold from TODO JSON:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python /path/to/skills/microsim-utils/scripts/scaffold-microsims-from-todo.py --project-dir /path/to/project","type":"text"}]},{"type":"paragraph","content":[{"text":"Report the summary output to the user (TODO specs processed, scaffolded, skipped). Use ","type":"text"},{"text":"--force","type":"text","marks":[{"type":"code_inline"}]},{"text":" only if the user explicitly asks to regenerate stubs; the script never overwrites an existing ","type":"text"},{"text":"main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" regardless of ","type":"text"},{"text":"--force","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Iframe height sync:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python /path/to/skills/microsim-utils/scripts/sync-iframe-heights.py --project-dir /path/to/project --verbose","type":"text"}]},{"type":"paragraph","content":[{"text":"Report the summary output to the user (sims synced, CANVAS_HEIGHT comments inserted, iframe heights updated).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 3: Execute Utility","type":"text"}]},{"type":"paragraph","content":[{"text":"Each guide contains:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Purpose and use cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prerequisites","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Step-by-step workflow","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output format","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Best practices","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Available Utilities","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"standardization.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Validate MicroSim quality against standards","type":"text"}]},{"type":"paragraph","content":[{"text":"Checks:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Required file presence (main.html, index.md)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code structure and patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Accessibility features","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Documentation completeness","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Responsive design implementation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output:","type":"text","marks":[{"type":"strong"}]},{"text":" Quality score (0-100) with recommendations","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"screen-capture.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Capture high-quality screenshots for social media previews","type":"text"}]},{"type":"paragraph","content":[{"text":"Script:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"~/.local/bin/bk-capture-screenshot \u003cmicrosim-directory-path>","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Features:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Uses Chrome headless mode with localhost server","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handles JavaScript-heavy visualizations (p5.js, vis-network, Chart.js)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Waits 3 seconds for proper rendering","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creates consistent 1200x800 image sizes","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output:","type":"text","marks":[{"type":"strong"}]},{"text":" PNG screenshot named ","type":"text"},{"text":"{microsim-name}.png","type":"text","marks":[{"type":"code_inline"}]},{"text":" in MicroSim directory","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"add-icons.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Add favicon and icons to MicroSim directories","type":"text"}]},{"type":"paragraph","content":[{"text":"Creates:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"favicon.ico","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"apple-touch-icon.png","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Other platform-specific icons","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"index-generator.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Generate comprehensive MicroSim index page","type":"text"}]},{"type":"paragraph","content":[{"text":"Creates:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Grid-based card layout","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Screenshots for each MicroSim","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Alphabetically sorted entries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MkDocs Material card format","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Updates mkdocs.yml navigation","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"create-microsim-todo-json-files.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Extract unimplemented MicroSim diagram specifications from chapter content and create TODO JSON files","type":"text"}]},{"type":"paragraph","content":[{"text":"Script:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"scripts/create-microsim-todo-json-files.py --project-dir /path/to/project","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"How it works:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scans all ","type":"text"},{"text":"docs/chapters/*/index.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" files for ","type":"text"},{"text":"#### Diagram:","type":"text","marks":[{"type":"code_inline"}]},{"text":" headers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extracts sim-id, library, Bloom level, learning objective, and full specification from ","type":"text"},{"text":"\u003cdetails>","type":"text","marks":[{"type":"code_inline"}]},{"text":" blocks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skips any sim-id that already has a directory with ","type":"text"},{"text":"main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" under ","type":"text"},{"text":"docs/sims/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writes one JSON file per unimplemented diagram to ","type":"text"},{"text":"docs/sims/TODO/","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Output:","type":"text","marks":[{"type":"strong"}]},{"text":" Individual JSON files in ","type":"text"},{"text":"docs/sims/TODO/{sim-id}.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" with fields:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sim_id","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"diagram_name","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"chapter_number","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"chapter_title","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"library","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"bloom_level","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"bloom_verb","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"learning_objective","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"completion_status: \"specified\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"extracted_date","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"specification","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Important:","type":"text","marks":[{"type":"strong"}]},{"text":" Always pass ","type":"text"},{"text":"--project-dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" pointing to the project root (the directory containing ","type":"text"},{"text":"mkdocs.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":"). If omitted, the script walks up from its own location to find ","type":"text"},{"text":"mkdocs.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":", which may find the wrong project.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"scaffold-microsims-from-todo.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Generate scaffold (stub) files for each MicroSim that has a TODO JSON spec but no implementation yet. This is the natural next step after ","type":"text"},{"text":"create-microsim-todo-json-files.py","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Script:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"scripts/scaffold-microsims-from-todo.py --project-dir /path/to/project","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"How it works:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reads each ","type":"text"},{"text":"docs/sims/TODO/\u003csim-id>.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For any sim-id that does NOT already have ","type":"text"},{"text":"docs/sims/\u003csim-id>/main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":", creates the directory with three stub files:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" — placeholder canvas with the spec embedded as a comment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"index.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — frontmatter, learning objective, iframe embed, full spec","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"metadata.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — mapped from the TODO JSON","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skips any sim-id whose ","type":"text"},{"text":"main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" already exists (so real implementations are never clobbered)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Flags:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--project-dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Project root containing ","type":"text"},{"text":"mkdocs.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" (required or auto-detected)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--force","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Overwrite existing ","type":"text"},{"text":"index.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"metadata.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" stubs. ","type":"text"},{"text":"Never","type":"text","marks":[{"type":"strong"}]},{"text":" overwrites an existing ","type":"text"},{"text":"main.html","type":"text","marks":[{"type":"code_inline"}]},{"text":", even with ","type":"text"},{"text":"--force","type":"text","marks":[{"type":"code_inline"}]},{"text":", because that file may contain a real implementation.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output:","type":"text","marks":[{"type":"strong"}]},{"text":" Summary showing TODO specs processed, scaffolded, and skipped (already implemented).","type":"text"}]},{"type":"paragraph","content":[{"text":"Important:","type":"text","marks":[{"type":"strong"}]},{"text":" Always pass ","type":"text"},{"text":"--project-dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" pointing to the project root. The script auto-detects by walking up from cwd to find ","type":"text"},{"text":"mkdocs.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" if omitted.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"sync-iframe-heights.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Synchronize iframe heights across MicroSim index.md files and chapter files using the ","type":"text"},{"text":"CANVAS_HEIGHT","type":"text","marks":[{"type":"code_inline"}]},{"text":" comment in each sim's JavaScript file as the single source of truth.","type":"text"}]},{"type":"paragraph","content":[{"text":"Script:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"scripts/sync-iframe-heights.py --project-dir /path/to/project","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"How it works:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reads ","type":"text"},{"text":"// CANVAS_HEIGHT: \u003cint>","type":"text","marks":[{"type":"code_inline"}]},{"text":" from the first 15 lines of each sim's ","type":"text"},{"text":".js","type":"text","marks":[{"type":"code_inline"}]},{"text":" file","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If the comment is missing, computes CANVAS_HEIGHT from ","type":"text"},{"text":"drawHeight + controlHeight","type":"text","marks":[{"type":"code_inline"}]},{"text":" (+ ","type":"text"},{"text":"graphHeight","type":"text","marks":[{"type":"code_inline"}]},{"text":" if present) and ","type":"text"},{"text":"inserts","type":"text","marks":[{"type":"strong"}]},{"text":" the comment on line 2 of the JS file","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sets iframe height = ","type":"text"},{"text":"CANVAS_HEIGHT + 2","type":"text","marks":[{"type":"code_inline"}]},{"text":" (2px for iframe border) in:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The sim's own ","type":"text"},{"text":"docs/sims/\u003csim-id>/index.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any chapter file (","type":"text"},{"text":"docs/chapters/*/index.md","type":"text","marks":[{"type":"code_inline"}]},{"text":") that embeds the sim","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reports all changes with colored output","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Flags:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--project-dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Project root containing ","type":"text"},{"text":"mkdocs.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" (required or auto-detected)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--sim \u003csim-id>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Sync a single sim instead of all","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--dry-run","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Preview changes without writing files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--verbose","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Show status for all sims, not just changes","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output:","type":"text","marks":[{"type":"strong"}]},{"text":" Summary showing sims synced, CANVAS_HEIGHT comments inserted, and iframe heights updated.","type":"text"}]},{"type":"paragraph","content":[{"text":"Important:","type":"text","marks":[{"type":"strong"}]},{"text":" Always pass ","type":"text"},{"text":"--project-dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" pointing to the project root. The script auto-detects by walking up from cwd to find ","type":"text"},{"text":"mkdocs.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" if omitted.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"iframe-auto-height.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Runtime alternative to ","type":"text"},{"text":"sync-iframe-heights.py","type":"text","marks":[{"type":"code_inline"}]},{"text":". Documents the two-part ","type":"text"},{"text":"postMessage","type":"text","marks":[{"type":"code_inline"}]},{"text":" protocol that lets an embedded MicroSim report its own measured content height to the parent page, which then resizes the iframe automatically.","type":"text"}]},{"type":"paragraph","content":[{"text":"When to use:","type":"text","marks":[{"type":"strong"}]},{"text":" Sims with responsive or content-dependent heights that are hard to predict at build time (e.g., diagram-overlay sims whose height depends on the longest callout text). Coexists with ","type":"text"},{"text":"sync-iframe-heights.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" without conflict.","type":"text"}]},{"type":"paragraph","content":[{"text":"What's in the guide:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"'microsim-resize'","type":"text","marks":[{"type":"code_inline"}]},{"text":" message contract (type, height fields)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Drop-in parent-side listener block for ","type":"text"},{"text":"docs/js/extra.js","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Child-side reporter snippets for diagram-overlay, p5.js, and Mermaid sims","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why to match by ","type":"text"},{"text":"event.source === iframe.contentWindow","type":"text","marks":[{"type":"code_inline"}]},{"text":" (not by URL)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Caveats: one-shot vs. live, target origin, sandbox attributes, fullscreen mode","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reference implementation paths in the ","type":"text"},{"text":"digital-citizenship","type":"text","marks":[{"type":"code_inline"}]},{"text":" project","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Setup is one-time per project:","type":"text","marks":[{"type":"strong"}]},{"text":" add the listener block once to ","type":"text"},{"text":"docs/js/extra.js","type":"text","marks":[{"type":"code_inline"}]},{"text":", then any MicroSim that posts the ","type":"text"},{"text":"microsim-resize","type":"text","marks":[{"type":"code_inline"}]},{"text":" message participates automatically.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Examples","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 1: Quality Check","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"Check if my bouncing-ball MicroSim meets standards\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"check\", \"standards\" → ","type":"text"},{"text":"references/standardization.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Read standardization.md and follow its workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 2: Capture Screenshot","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"Create a preview image for the timeline MicroSim\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"preview\", \"image\" → ","type":"text"},{"text":"references/screen-capture.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Run ","type":"text"},{"text":"~/.local/bin/bk-capture-screenshot /path/to/docs/sims/timeline","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 3: Update Index","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"Update the MicroSim index page with all new sims\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"index\", \"update\" → ","type":"text"},{"text":"references/index-generator.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Read index-generator.md and follow its workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 4: Create TODO JSON Files","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"Create MicroSim TODO JSON files\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"TODO\", \"create microsim todo\" → ","type":"text"},{"text":"scripts/create-microsim-todo-json-files.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Run ","type":"text"},{"text":"python scripts/create-microsim-todo-json-files.py --project-dir /path/to/project","type":"text","marks":[{"type":"code_inline"}]},{"text":" and report results (chapters scanned, specs found, already implemented, TODO files written)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 5: Scaffold MicroSims from TODO JSON","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"scaffold the microsims\" or \"create the stub files for the TODO sims\" or \"generate scaffold files from the JSON specs\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"scaffold microsims\", \"stub out microsims\", \"scaffold from todo\" → ","type":"text"},{"text":"scripts/scaffold-microsims-from-todo.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Run ","type":"text"},{"text":"python scripts/scaffold-microsims-from-todo.py --project-dir /path/to/project","type":"text","marks":[{"type":"code_inline"}]},{"text":" and report results (TODO specs processed, scaffolded, skipped). Typically follows immediately after ","type":"text"},{"text":"create-microsim-todo-json-files.py","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 6: Fix Iframe Heights","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"fix the iframe heights\" or \"sync the iframe heights\" or \"correct the iframe heights\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"fix iframe heights\", \"sync iframe heights\", \"correct iframe heights\" → ","type":"text"},{"text":"scripts/sync-iframe-heights.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Run ","type":"text"},{"text":"python scripts/sync-iframe-heights.py --project-dir /path/to/project --verbose","type":"text","marks":[{"type":"code_inline"}]},{"text":" and report results (sims synced, comments inserted, iframe heights updated)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 7: Set Up Iframe Auto-Resize","type":"text"}]},{"type":"paragraph","content":[{"text":"User:","type":"text","marks":[{"type":"strong"}]},{"text":" \"make the iframes auto-size\" or \"set up iframe auto height\" or \"I want microsims to report their own height\" ","type":"text"},{"text":"Routing:","type":"text","marks":[{"type":"strong"}]},{"text":" Keywords \"iframe auto height\", \"auto-size iframe\", \"iframe postMessage\" → ","type":"text"},{"text":"references/iframe-auto-height.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"Action:","type":"text","marks":[{"type":"strong"}]},{"text":" Read ","type":"text"},{"text":"iframe-auto-height.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" and follow the two-part setup: paste the parent-side listener block at the top of ","type":"text"},{"text":"docs/js/extra.js","type":"text","marks":[{"type":"code_inline"}]},{"text":", then ensure the relevant MicroSims post ","type":"text"},{"text":"{ type: 'microsim-resize', height }","type":"text","marks":[{"type":"code_inline"}]},{"text":" after layout settles. Confirm both sides are in place and report which sims now participate.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Workflows","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"After Creating New MicroSim","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"standardization.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" to validate quality","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"~/.local/bin/bk-capture-screenshot \u003cmicrosim-path>","type":"text","marks":[{"type":"code_inline"}]},{"text":" to create preview image","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"index-generator.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" to add to index page","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bulk Quality Audit","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"standardization.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" to audit all MicroSims in a project and generate a quality report.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Integration Notes","type":"text"}]},{"type":"paragraph","content":[{"text":"These utilities work with the standard MicroSim directory structure:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"docs/sims/\u003cmicrosim-name>/\n├── main.html # Main visualization\n├── index.md # Documentation\n├── *.js # JavaScript code\n├── style.css # Styles (optional)\n└── \u003cname>.png # Preview screenshot (created by screen-capture)","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"microsim-utils","author":"@skillopedia","source":{"stars":72,"repo_name":"claude-skills","origin_url":"https://github.com/dmccreary/claude-skills/blob/HEAD/skills/microsim-utils/SKILL.md","repo_owner":"dmccreary","body_sha256":"683b8c6b1c803feb3eae205ad70959b40730e4e393c9742a21a6bf9e65dacf29","cluster_key":"2d1cfd65399f575d3762d69486f88980c4b6eb6df7d21eee2fa42f961d4cabe3","clean_bundle":{"format":"clean-skill-bundle-v1","source":"dmccreary/claude-skills/skills/microsim-utils/SKILL.md","attachments":[{"id":"52e5be2b-bb98-5e7d-8183-cfa1c5e16176","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/52e5be2b-bb98-5e7d-8183-cfa1c5e16176/attachment.md","path":"TODO.md","size":2020,"sha256":"5046cda64f45e5c64f57258e6d869feb4054a4cf7d14a1bb3dfc0a5b1d1f8e98","contentType":"text/markdown; charset=utf-8"},{"id":"f733a18d-88ee-568c-a11e-0526d858d70e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f733a18d-88ee-568c-a11e-0526d858d70e/attachment.json","path":"assets/microsim-schema.json","size":37413,"sha256":"eb122589321568e5863147da576828a52629a925073b3cb7a6eb785d88993996","contentType":"application/json; charset=utf-8"},{"id":"57bc10d9-aec3-581a-8b22-183bdf2164b6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/57bc10d9-aec3-581a-8b22-183bdf2164b6/attachment.md","path":"references/add-icons.md","size":5974,"sha256":"73554a85d6ced39f82be192809c820926e22f2b6c591588687c1beb9bc695194","contentType":"text/markdown; charset=utf-8"},{"id":"5ab58e8e-389b-5dec-9556-6b42d272873f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ab58e8e-389b-5dec-9556-6b42d272873f/attachment.md","path":"references/iframe-auto-height.md","size":8864,"sha256":"d4ff80a178ac7cbb22220dcfd42a2ccced75111a3d08b2b8f85989fbfeca96b4","contentType":"text/markdown; charset=utf-8"},{"id":"582acd47-f8fb-540b-b2a5-90ea850f0451","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/582acd47-f8fb-540b-b2a5-90ea850f0451/attachment.md","path":"references/index-generator.md","size":11541,"sha256":"745ce1eb1a9714d239b8efc560421bc4abc56f6113baa322697624bca8e25831","contentType":"text/markdown; charset=utf-8"},{"id":"8a473ec0-f3cd-5999-9d77-b299cc505799","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8a473ec0-f3cd-5999-9d77-b299cc505799/attachment.md","path":"references/screen-capture.md","size":8090,"sha256":"af979b2a5b852b307baf0c464426064a98be3d87ecf0ae0d581300f0c11e50cd","contentType":"text/markdown; charset=utf-8"},{"id":"edbdb208-844a-5f69-8cff-578f5f9ea3ea","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/edbdb208-844a-5f69-8cff-578f5f9ea3ea/attachment.md","path":"references/standardization.md","size":12583,"sha256":"cee99d56535771420f73b5d7d961bdd91591c192ce3e9ddde5ae9f3c3231e7b9","contentType":"text/markdown; charset=utf-8"},{"id":"1035d11a-81ba-5168-89d5-95225e7ee4d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1035d11a-81ba-5168-89d5-95225e7ee4d3/attachment.py","path":"scripts/calculate-quality-score.py","size":15833,"sha256":"837918bf096e552b3d7f54b2fca83544bbc644d481b613579880c85ed0c73713","contentType":"text/x-python; charset=utf-8"},{"id":"1d27e51c-c589-594b-bda6-1275c20da7d1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1d27e51c-c589-594b-bda6-1275c20da7d1/attachment.py","path":"scripts/create-microsim-todo-json-files.py","size":10074,"sha256":"12e6532a5053a6a56e89968207bd7119530bd5fa3b0874383b23a0de52be0aae","contentType":"text/x-python; charset=utf-8"},{"id":"ac566bf5-95ca-5751-83bf-c781aa9eb72e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ac566bf5-95ca-5751-83bf-c781aa9eb72e/attachment.py","path":"scripts/generate-microsim-index.py","size":3507,"sha256":"d0ff31a8f92cbd201a4fc91dbf7d2ef2a4502e6cc48b1e1e84469927d5e01280","contentType":"text/x-python; charset=utf-8"},{"id":"60689f76-0abe-50dc-8f92-bc637fe2127d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/60689f76-0abe-50dc-8f92-bc637fe2127d/attachment.py","path":"scripts/scaffold-microsims-from-todo.py","size":9231,"sha256":"27de9f94231b443b3c570a1465efe4059271d5f463bf1b1a3be9bb1f830cf247","contentType":"text/x-python; charset=utf-8"},{"id":"0f5186ae-9232-5326-ba17-81931dfcf21c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0f5186ae-9232-5326-ba17-81931dfcf21c/attachment.py","path":"scripts/sync-iframe-heights.py","size":12850,"sha256":"529f49ed57a3e6df344de363e2be48b8e49dbfe402f4c289d00f8a52a4c9cd62","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"b4f5dffdedc3afe3d5c15033ee538eabcde3e07fca8396a6afa772899b86ed0a","attachment_count":12,"text_attachments":12,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/microsim-utils/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"browser-automation-scraping","category_label":"Browser"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"browser-automation-scraping","import_tag":"clean-skills-v1","description":"Utility tools for MicroSim management including quality validation, screenshot capture, icon management, index page generation, and iframe height synchronization. Routes to the appropriate utility based on the task needed."}},"renderedAt":1782979418895}

MicroSim Utilities Overview This meta-skill provides utility functions for managing and maintaining MicroSims in intelligent textbook projects. It consolidates four utility skills into a single entry point with on-demand loading of specific utility guides. When to Use This Skill Use this skill when users request: - Validating MicroSim quality and standards - Capturing screenshots for preview images - Adding or managing icons for MicroSims - Generating index pages for MicroSim directories - Quality scoring and standardization checks - Synchronizing iframe heights from JS source files - Setting…