--- ## Code Review: `reports/phase3/scripts/build_top200_v2_h5.py` This diff is a major refactoring of the H5 page builder — from a standalone inline HTML generator (~925 lines) to a template-based approach (~312 lines) that reuses `skill-market-h5.html` as a shell. The table-based layout is replaced with a card grid, the scoring scale changes from 20 to 5, and the UI switches from select-based filters to chip-based filters. --- ### `build_top200_v2_h5.py:100` — `CLEANUP` — Dead return value in `extract_h5_shell()` **Summary:** The function returns a tuple `(head_and_body, "")` where the second element is always an empty string. **Suggestion:** Return just `head_and_body` as a plain string, and update the caller (`shell = extract_h5_shell()` instead of `shell, _ = extract_h5_shell()`). --- ### `build_top200_v2_h5.py:105–158` — `CLEANUP` — Fragile template text replacement **Summary:** The function performs 10+ sequential `.replace()` calls against exact HTML markup in `skill-market-h5.html`. If the template is ever reformatted or tweaked (whitespace changes, attribute reordering), these replacements silently no-op — the old strings remain in the output. **Failure scenario:** Someone edits `skill-market-h5.html` (e.g., adds a newline inside a `
` or changes `class="sub"` to `class="subtitle"`). The build script produces output with stale V1 text labels instead of V2 labels, with no warning. **Suggestion:** Consider one of: - Extract translatable strings/labels into a JSON config that both the template and this script reference. - Use regex or a small HTML parser (e.g., `html.parser`) to find elements by class or ID and replace text content only. - At minimum, add a post-build assertion that checks the output for expected V2 strings. --- ### `build_top200_v2_h5.py:247` — `CLEANUP` — `esc()` + inline `onclick` bypasses HTML escaping in JS context **Summary:** The `esc()` function escapes `'` to `'`, but when interpolated into an inline `onclick` attribute: ```javascript onclick="openDrawer('${esc(r.skill_id)}')" ``` the HTML parser decodes `'` back to `'` **before** the JavaScript engine runs. If a `skill_id` contained a single quote (e.g., `it's_a_test`), the decoded JS would be: ```javascript openDrawer('it's_a_test') ``` which is a syntax error — the string terminates at the second `'`. **Failure scenario:** A skill_id containing `'` would break the card's click handler. Extremely unlikely given skill ID naming conventions (slugs like `namespace__skill-name`), but incorrect in principle. **Suggestion:** Prefer event delegation — attach one click handler on the grid container and read `data-id` from the card: ```javascript document.getElementById('grid').addEventListener('click', e => { const card = e.target.closest('[data-id]'); if (card) openDrawer(card.dataset.id); }); ``` Same issue applies to line 268 (`onclick="copyText('${esc(r.copied_path || '')}', this)"`). --- ### `build_top200_v2_h5.py:176–182` — `CLEANUP` — `securityLabel()` fallback ordering is fragile **Summary:** The function first checks exact keys in the `m` map, then falls back to `String(s).includes('passed')` before `String(s).includes('caution')`. If a new security value containing **both** "passed" and "caution" were introduced (e.g., `passed_with_caution_but_flagged`), it would match `includes('passed')` first and return `'通过'` instead of `'谨慎通过'`. **Failure scenario:** A hypothetical new security status `passed_with_caution_but_flagged` would display as "通过" rather than "谨慎通过". In practice, all known values are explicitly mapped, so this only matters if new values appear at runtime without updating the code. **Suggestion:** Check `caution` before `passed` in the fallback chain, since "caution" is a more specific qualifier: ```javascript if (String(s).includes('caution')) return '谨慎通过'; if (String(s).includes('passed')) return '通过'; ``` --- ### `build_top200_v2_h5.py:233–237` — `CLEANUP` — Sort options unreachable via UI **Summary:** The JS sort handler supports `rank`, `rank-desc`, `score`, `score-asc`, and `name` modes. But the template's `