# Guanghan GHC-05 Topology Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Replace the inaccurate Guanghan topology demo with a responsive, interactive single-canvas engineering diagram and correct the related GHC-05 timeline records. **Architecture:** Keep the deliverable as one standalone HTML file. Render the topology as an inline SVG with fixed semantic node IDs and connection types, keep module facts in one `MODULE_DETAILS` JavaScript object, and populate one accessible modal from that object. Add Python contract tests for source-level topology facts and a Playwright test for rendered pixels and modal behavior. **Tech Stack:** HTML5, CSS, inline SVG, vanilla JavaScript, Python `unittest`, Python `http.server`, Playwright. --- ## File Structure - Create `tests/test_guanghan_topology_demo.py`: static contract tests for node inventory, connection types, future-extension comments, dimensions, and corrected timeline facts. - Create `tests/test_guanghan_topology_e2e.py`: browser test for visible green corridors, hidden future markers, click targets, and modal closing behavior. - Modify `data/wiki/guanghan_topology_demo.html`: complete standalone diagram, centralized details data, and accessible modal. - Modify `user_input/广寒计划/Timeline.md`: remove the nonexistent ring corridor and correct all dependent GHC-05 records. ### Task 1: Add failing source-contract tests **Files:** - Create: `tests/test_guanghan_topology_demo.py` - Read: `docs/superpowers/specs/2026-07-09-guanghan-topology-design.md` - [ ] **Step 1: Write the contract test** ```python from __future__ import annotations import re import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] HTML_PATH = ROOT / "data" / "wiki" / "guanghan_topology_demo.html" TIMELINE_PATH = ROOT / "user_input" / "广寒计划" / "Timeline.md" CLICKABLE_MODULES = { "core", "habitat-1", "habitat-2", "greenhouse", "laboratory", "power-node", "power-north", "power-east", "power-south", "power-west", "wugang-2", "binglun-1", "binglun-2", "binglun-3", } class GuanghanTopologySourceTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.html = HTML_PATH.read_text(encoding="utf-8") timeline = TIMELINE_PATH.read_text(encoding="utf-8") cls.ghc05 = timeline.split("GHC-05任务", 1)[1].split("JC-02任务", 1)[0] def test_clickable_module_inventory_is_exact(self) -> None: found = set(re.findall(r'data-module="([^"]+)"', self.html)) self.assertEqual(found, CLICKABLE_MODULES) def test_connection_inventory_matches_approved_topology(self) -> None: self.assertEqual(self.html.count('data-connection="pressurized"'), 2) self.assertIn('data-link="wugang-2--greenhouse"', self.html) self.assertIn('data-link="habitat-1--power-north"', self.html) self.assertNotIn("perimeter-corridor", self.html) def test_walkways_and_radiators_are_not_clickable(self) -> None: self.assertNotRegex( self.html, r'<(?:line|path|rect)[^>]+data-connection="pressurized"[^>]+data-module=', ) self.assertNotRegex( self.html, r'<(?:g|rect)[^>]+class="[^"]*radiator[^"]*"[^>]+data-module=', ) def test_future_extensions_exist_only_as_source_comments(self) -> None: comments = "\n".join(re.findall(r"", self.html, flags=re.S)) visible_source = re.sub(r"", "", self.html, flags=re.S) for direction in ("north", "east", "west"): self.assertIn(f"FUTURE_EXPANSION_ANCHOR:{direction}", comments) self.assertNotIn("未来北部十字结构", visible_source) self.assertNotIn("未来东部十字结构", visible_source) self.assertNotIn("未来西部十字结构", visible_source) def test_every_clickable_module_has_dimensions_field(self) -> None: for module_id in CLICKABLE_MODULES: self.assertRegex( self.html, rf'"{re.escape(module_id)}"\s*:\s*\{{.*?dimensions\s*:', msg=f"{module_id} is missing dimensions", ) def test_timeline_contains_only_the_two_real_corridors(self) -> None: for incorrect in ( "4段加压走道", "居住舱#1↔居住舱#2", "居住舱#2↔实验舱", "实验舱↔温室", "温室↔居住舱#1", "环形加压通路", "8个对接面", "4段加压走道48小时", ): self.assertNotIn(incorrect, self.ghc05) self.assertIn("居住舱#1远端对接口", self.ghc05) self.assertIn("电力模块北部", self.ghc05) self.assertIn("温室远端对接口", self.ghc05) self.assertIn("吴刚#2", self.ghc05) self.assertIn("从温室模块远端接口断开", self.ghc05) if __name__ == "__main__": unittest.main() ``` - [ ] **Step 2: Run the test and verify the expected failures** Run: ```powershell python -m unittest tests.test_guanghan_topology_demo -v ``` Expected: failures for missing `data-module`/`data-connection` contracts, absent future anchor comments, incomplete dimensions data, and the incorrect ring-corridor timeline text. - [ ] **Step 3: Commit the failing contract** ```powershell git add tests/test_guanghan_topology_demo.py git commit -m "test: define Guanghan topology contract" -m "Co-Authored-By: Codex " ``` ### Task 2: Correct the GHC-05 timeline record **Files:** - Modify: `user_input/广寒计划/Timeline.md:270-296` - Test: `tests/test_guanghan_topology_demo.py` - [ ] **Step 1: Rewrite the affected GHC-05 entries without changing dates or EVA numbers** Apply these factual replacements: ```text 2024-12-25 EVA-7: 卸下居住舱#2、实验舱,以及用于“居住舱#1↔电力模块北部”和“温室↔吴刚#2”的两段加压走道组件。 2024-12-28 EVA-10: 居住舱#1安装到核心舱南侧;其远端通过加压走道连接电力模块北部。 2024-12-31 EVA-13: 吴刚#2通过加压走道连接温室远端对接口;对该走道执行结构锁紧与50 psi氦气检漏。 2025-01-01 EVA-14: 复核居住舱#1↔电力模块北部与温室↔吴刚#2两条加压走道的密封、照明和舱门联锁。 2025-01-02 EVA-15: 将“4段加压走道48小时密封保持确认”改为“两段加压走道48小时密封保持确认”。 2025-01-18 EVA-18: 将“从吴刚#2侧断开吴刚#2与广寒核心舱”改为“从吴刚#2侧断开吴刚#2与温室模块远端接口”。 ``` Preserve the existing mass, dimensions, power, crew, and mission-operation facts around these sentences. - [ ] **Step 2: Run the timeline contract** Run: ```powershell python -m unittest tests.test_guanghan_topology_demo.GuanghanTopologySourceTests.test_timeline_contains_only_the_two_real_corridors -v ``` Expected: `OK`. - [ ] **Step 3: Review the focused diff** Run: ```powershell git diff -- user_input/广寒计划/Timeline.md ``` Expected: only the six affected GHC-05 entries change; dates, EVA numbering, and unrelated facts remain intact. - [ ] **Step 4: Commit the timeline correction** ```powershell git add user_input/广寒计划/Timeline.md git commit -m "docs: 修正广寒基地加压走道记录" -m "Co-Authored-By: Codex " ``` ### Task 3: Build the semantic single-canvas topology **Files:** - Modify: `data/wiki/guanghan_topology_demo.html` - Test: `tests/test_guanghan_topology_demo.py` - Reference: `user_input/广寒计划/Timeline.md` - Reference: `data/wiki/guanghan_ghc01_zh.html` - Reference: `data/wiki/guanghan_ghc03_zh.html` - Reference: `data/wiki/guanghan_ghc05_zh.html` - Reference: `data/wiki/wugang_lunar_lander_zh.html` - Reference: `data/wiki/guanghan_bl01_zh.html` - Reference: `data/wiki/guanghan_bl02_zh.html` - Reference: `data/wiki/guanghan_bl03_zh.html` - [ ] **Step 1: Replace the old CSS grid with an inline SVG contract** Use this exact semantic pattern for every visible node and connection: ```html 广寒基地 GHC-05 任务后拓扑 核心舱十字结构向南连接电力模块十字结构。 广寒核心舱 四向安装节点 ``` Create all 14 `data-module` groups listed in the source-contract test. Position them as approved: - Core cross: habitat-2 north, laboratory east, habitat-1 south, greenhouse west. - Wugang-2 beyond greenhouse on the west green corridor. - Power cross below habitat-1: north rack/corridor, east nuclear-fuel/centrifuge, south reactor, west ISRU/mining/storage. - Binglun-1 west, Binglun-2 south, Binglun-3 east, each on purple dashed utility lines. Place one non-interactive `.radiator` group above each power branch, offset away from the green north corridor. Do not put `data-module` on radiator groups. - [ ] **Step 2: Add responsive engineering-map styling** Use CSS variables and explicit line styles: ```css :root { --bg: #070d18; --panel: #0d1728; --structural: #8fa1b8; --pressurized: #51e89c; --utility: #b178e6; --core: #ffad42; --habitat: #52baff; --power: #d5b642; --vehicle: #c28cdf; } .map-shell { overflow: auto; border: 1px solid #26344a; border-radius: 16px; } .topology-map { display: block; width: 100%; min-width: 980px; height: auto; background: var(--bg); } .connection { fill: none; vector-effect: non-scaling-stroke; } .structural { stroke: var(--structural); stroke-width: 5; } .pressurized { stroke: var(--pressurized); stroke-width: 8; } .utility { stroke: var(--utility); stroke-width: 4; stroke-dasharray: 12 9; } .topology-node { cursor: pointer; outline: none; } .topology-node:hover rect, .topology-node:focus-visible rect { filter: brightness(1.24); stroke-width: 5; } .radiator { pointer-events: none; } @media (max-width: 760px) { body { padding: 14px; } .topology-map { min-width: 900px; } } ``` Do not apply SVG filters to zero-width/zero-height corridor lines; the approved mockup proved Chromium can clip those lines even when computed styles report a green stroke. - [ ] **Step 3: Add the centralized module facts** Define all entries with this exact shape: ```javascript const MODULE_DETAILS = { "habitat-1": { name: "居住舱#1", type: "居住舱段", location: "广寒核心舱南部安装口", role: "长期驻留与乘员生活空间,提供4个折叠铺位和2个睡袋挂点。", metrics: ["质量:12.2 t", "居住配置:4铺位 + 2睡袋"], dimensions: ["运输:高2.7 m × 长6.2 m × 宽5.0 m", "展开:高3.2 m × 长6.2 m × 宽7.6 m"], status: "已展开并投入使用", connections: ["北:广寒核心舱(结构直连)", "南:电力模块北部(加压走道)"], mission: "GHC-05 · 2024-12-28 EVA-10完成安装" } }; ``` Populate the other 13 entries from the reference pages and corrected timeline. Use `"资料未记录"` for unavailable dimensions rather than inventing values. In particular, preserve these confirmed dimensions: - Greenhouse transport: `高2.8 m × 长8.7 m × 宽5.0 m`; deployed: `高3.2 m × 长8.7 m × 宽7.4 m`. - Habitat-1 transport/deployed values shown above. - Habitat-2 deployed: `高3.2 m × 长6.2 m × 宽7.6 m`; if transport dimensions are not explicitly recorded, show `资料未记录`. - Laboratory transport: `高2.7 m × 长7.5 m × 宽5.0 m`; deployed: `高3.2 m × 长7.5 m × 宽7.5 m`. - Core dry mass: `28.2 t`; unavailable physical dimensions: `资料未记录`. - [ ] **Step 4: Run the static contract** Run: ```powershell python -m unittest tests.test_guanghan_topology_demo -v ``` Expected: all tests pass except no browser behavior is asserted yet. - [ ] **Step 5: Commit the semantic topology** ```powershell git add data/wiki/guanghan_topology_demo.html tests/test_guanghan_topology_demo.py git commit -m "feat: rebuild Guanghan topology diagram" -m "Co-Authored-By: Codex " ``` ### Task 4: Add failing browser tests for modal behavior and rendered corridors **Files:** - Create: `tests/test_guanghan_topology_e2e.py` - Modify: `data/wiki/guanghan_topology_demo.html` - [ ] **Step 1: Write the browser test** ```python from __future__ import annotations import asyncio import functools import threading import unittest from contextlib import contextmanager from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from playwright.async_api import async_playwright ROOT = Path(__file__).resolve().parents[1] @contextmanager def local_server(): handler = functools.partial(SimpleHTTPRequestHandler, directory=str(ROOT)) server = ThreadingHTTPServer(("127.0.0.1", 0), handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: yield f"http://127.0.0.1:{server.server_port}" finally: server.shutdown() server.server_close() thread.join(timeout=2) class GuanghanTopologyE2ETests(unittest.TestCase): def test_rendered_topology_and_modal_interactions(self) -> None: async def scenario(base_url: str) -> None: async with async_playwright() as pw: browser = await pw.chromium.launch(headless=True) page = await browser.new_page(viewport={"width": 1440, "height": 1100}) errors: list[str] = [] page.on("console", lambda msg: errors.append(msg.text) if msg.type == "error" else None) await page.goto( f"{base_url}/data/wiki/guanghan_topology_demo.html", wait_until="networkidle", ) self.assertEqual(await page.locator("[data-module]").count(), 14) self.assertEqual(await page.locator('[data-connection="pressurized"]').count(), 2) strokes = await page.locator('[data-connection="pressurized"]').evaluate_all( "(els) => els.map((el) => getComputedStyle(el).stroke)" ) self.assertEqual(strokes, ["rgb(81, 232, 156)", "rgb(81, 232, 156)"]) for forbidden in ("未来北部十字结构", "未来东部十字结构", "未来西部十字结构"): self.assertEqual(await page.get_by_text(forbidden, exact=False).count(), 0) await page.locator('[data-module="core"]').click() self.assertEqual(await page.locator("#detail-modal").get_attribute("aria-hidden"), "false") self.assertEqual(await page.locator("#detail-title").inner_text(), "广寒核心舱") await page.keyboard.press("Escape") self.assertEqual(await page.locator("#detail-modal").get_attribute("aria-hidden"), "true") await page.locator('[data-module="habitat-1"]').click() self.assertIn("运输:高2.7 m", await page.locator("#detail-dimensions").inner_text()) await page.locator("#detail-modal").click(position={"x": 5, "y": 5}) self.assertEqual(await page.locator("#detail-modal").get_attribute("aria-hidden"), "true") self.assertEqual(errors, []) await browser.close() with local_server() as base_url: asyncio.run(scenario(base_url)) if __name__ == "__main__": unittest.main() ``` - [ ] **Step 2: Run the E2E test and verify it fails** Run: ```powershell python -m unittest tests.test_guanghan_topology_e2e -v ``` Expected: fail because the accessible modal contract or event handlers are not yet complete. - [ ] **Step 3: Implement the accessible modal** Add this modal structure after the map: ```html ``` Use these functions and handlers: ```javascript const modal = document.querySelector("#detail-modal"); const dialog = modal.querySelector(".detail-dialog"); const closeButton = modal.querySelector(".modal-close"); let opener = null; function lines(items) { return items.map((item) => `${item}`).join(""); } function openDetails(moduleId, trigger) { const detail = MODULE_DETAILS[moduleId]; if (!detail) return; opener = trigger; document.querySelector("#detail-type").textContent = detail.type; document.querySelector("#detail-title").textContent = detail.name; document.querySelector("#detail-role").textContent = detail.role; document.querySelector("#detail-location").textContent = detail.location; document.querySelector("#detail-status").textContent = detail.status; document.querySelector("#detail-metrics").innerHTML = lines(detail.metrics); document.querySelector("#detail-dimensions").innerHTML = lines(detail.dimensions); document.querySelector("#detail-connections").innerHTML = lines(detail.connections); document.querySelector("#detail-mission").textContent = detail.mission; modal.setAttribute("aria-hidden", "false"); document.body.classList.add("modal-open"); dialog.focus(); } function closeDetails() { modal.setAttribute("aria-hidden", "true"); document.body.classList.remove("modal-open"); opener?.focus(); } document.querySelectorAll("[data-module]").forEach((node) => { node.addEventListener("click", () => openDetails(node.dataset.module, node)); node.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openDetails(node.dataset.module, node); } }); }); closeButton.addEventListener("click", closeDetails); modal.addEventListener("click", (event) => { if (event.target === modal) closeDetails(); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && modal.getAttribute("aria-hidden") === "false") closeDetails(); }); ``` Render detail values as text except for arrays passed through the fixed `lines()` helper; all array entries are authored local constants, not user input. - [ ] **Step 4: Run E2E and full source tests** Run: ```powershell python -m unittest tests.test_guanghan_topology_demo tests.test_guanghan_topology_e2e -v ``` Expected: all tests report `OK`, with no browser console errors. - [ ] **Step 5: Commit interaction behavior** ```powershell git add data/wiki/guanghan_topology_demo.html tests/test_guanghan_topology_e2e.py git commit -m "feat: add Guanghan module detail modal" -m "Co-Authored-By: Codex " ``` ### Task 5: Browser visual verification and final polish **Files:** - Modify if needed: `data/wiki/guanghan_topology_demo.html` - Verify: `user_input/广寒计划/Timeline.md` - [ ] **Step 1: Serve the workspace locally** Run from the project root: ```powershell python -m http.server 8765 --bind 127.0.0.1 ``` Expected URL: ```text http://127.0.0.1:8765/data/wiki/guanghan_topology_demo.html ``` - [ ] **Step 2: Capture and inspect a 1440×1100 desktop screenshot** Verify in the screenshot: - Core is the four-way center of habitat-2, laboratory, habitat-1, and greenhouse. - Both green corridors are visible as actual pixels, with no inline corridor labels. - Power north radiator does not cover the habitat-1 corridor. - Power cross is fully expanded. - Binglun-1/2/3 appear west/south/east. - No future expansion label or placeholder is visible. - The top legend appears exactly once. - [ ] **Step 3: Capture and inspect representative modal screenshots** Open and inspect: - Habitat-1: transport and deployed dimensions are visible. - Power west: ISRU, mining, and ore/fuel storage appear together. - Wugang-2: greenhouse distal-port connection is stated. Verify close button, backdrop close, `Esc`, and keyboard focus. - [ ] **Step 4: Run final verification** Run: ```powershell python -m unittest tests.test_guanghan_topology_demo tests.test_guanghan_topology_e2e -v git diff --check git status --short ``` Expected: tests `OK`; no whitespace errors; only intended task files differ from the starting worktree. - [ ] **Step 5: Commit any screenshot-driven polish** If visual inspection required code changes: ```powershell git add data/wiki/guanghan_topology_demo.html git commit -m "style: polish Guanghan topology layout" -m "Co-Authored-By: Codex " ``` If no code changes were needed, do not create an empty commit.