Add interactive layer stack diagram to docs - #233
Conversation
Add .github/write_layer_stack.py that generates docs/layer_stack.md with interactive SVG/JS visualizations of the layer stack and cross-sections. Features: Uniform/To Scale toggle, cross-section profiles, tooltips, pan+zoom. Runs during `make docs`. Closes gdsfactory#232 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Reviewer's GuideAdds a generated, interactive layer stack documentation page wired into the docs build, including SVG/JS visualizations of PDK layers and cross-sections, plus navigation updates. Flow diagram for generating interactive layer_stack.md in docs buildflowchart LR
A[make docs / docs-pdf / docs-serve] --> B[uv run python .github/write_layer_stack.py]
B --> C[main]
C --> D[PDK.activate]
C --> E[_extract_layers]
E --> F[_compute_layout]
C --> G[_extract_cross_sections]
F --> H[_render_layer_stack]
G --> I[_render_cross_sections]
H --> J[assemble Markdown content]
I --> J
J --> K[write docs/layer_stack.md]
K --> L[mkdocs / zensical build]
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The zoom/pan and tooltip logic is duplicated between the layer-stack and cross-section SVGs; consider extracting this into a small reusable JS helper or shared snippet to reduce repetition and keep future behavior changes in one place.
- The Makefile currently runs
.github/write_layer_stack.pyseparately fordocs,docs-pdf, anddocs-serve; you could instead factor this into a single phony target (e.g.layer-stack) that all three depend on to avoid duplication and keep the doc-generation pipeline easier to maintain.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The zoom/pan and tooltip logic is duplicated between the layer-stack and cross-section SVGs; consider extracting this into a small reusable JS helper or shared snippet to reduce repetition and keep future behavior changes in one place.
- The Makefile currently runs `.github/write_layer_stack.py` separately for `docs`, `docs-pdf`, and `docs-serve`; you could instead factor this into a single phony target (e.g. `layer-stack`) that all three depend on to avoid duplication and keep the doc-generation pipeline easier to maintain.
## Individual Comments
### Comment 1
<location path=".github/write_layer_stack.py" line_range="264-273" />
<code_context>
+ d = json.dumps(layers, separators=(",", ": "))
</code_context>
<issue_to_address>
**🚨 issue (security):** Escape JSON when embedding into inline <script> blocks to avoid '</script>' breakage or XSS edge cases.
Since this JSON is embedded directly into a `<script>` block (e.g. `const data={d}`), it should be escaped to avoid prematurely closing the script with sequences like `</script>` and to reduce XSS risk if these values ever become user-controlled. Consider wrapping `json.dumps` with a helper that post-processes the output (for example, `json.dumps(...).replace('</', '<\/')`).
</issue_to_address>
### Comment 2
<location path=".github/write_layer_stack.py" line_range="334-335" />
<code_context>
+ gL.addEventListener("mousemove",e=>{{
+ const g=e.target.closest("g[data-idx]");if(!g){{tip.style.display="none";return}}
+ const d=data[+g.dataset.idx];
+ tip.innerHTML=`<b>${{d.name}}</b><br>GDS: ${{d.gds??"N/A"}}<br>Material: ${{d.material}}<br>Type: ${{d.type}}<br>z: ${{d.zmin}}–${{d.zmax}} µm<br>Thickness: ${{d.thickness}} µm`;
+ const r=root.getBoundingClientRect();tip.style.display="block";
+ tip.style.left=(e.clientX-r.left+12)+"px";tip.style.top=(e.clientY-r.top+12)+"px";
+ }});
</code_context>
<issue_to_address>
**🚨 issue (security):** Avoid directly injecting unescaped data into innerHTML for tooltips.
`tip.innerHTML` is built from raw `d.name`, `d.material`, etc. If these ever contain `<`, `&`, or other HTML, the tooltip can be malformed or expose HTML injection/XSS risk. Please either escape these values before concatenation or build the tooltip via DOM APIs (using `textContent` for text nodes) to avoid relying on HTML parsing.
</issue_to_address>
### Comment 3
<location path=".github/write_layer_stack.py" line_range="466-335" />
<code_context>
+ root.querySelector("svg").addEventListener("mousemove",e=>{{
+ const g=e.target.closest("g[data-idx]");if(!g){{tip.style.display="none";return}}
+ const d=data[+g.dataset.idx];
+ tip.innerHTML=`<b>${{d.name}}</b><br>Material: ${{d.material}}${{d.width!=null?`<br>Width: ${{d.width}} µm`:""}}<br>GDS: ${{d.gds??"N/A"}}<br>z: ${{d.zmin}}–${{d.zmax}} µm<br>Thickness: ${{d.thickness}} µm`;
+ const r=root.getBoundingClientRect();tip.style.display="block";
+ tip.style.left=(e.clientX-r.left+12)+"px";tip.style.top=(e.clientY-r.top+12)+"px";
+ }});
</code_context>
<issue_to_address>
**🚨 issue (security):** Apply the same HTML-escaping considerations to cross-section tooltips.
This tooltip also sets `innerHTML` from raw `d.name` / `d.material`. For consistency and to avoid XSS-style issues, please escape these values or construct the tooltip via DOM nodes using `textContent` instead.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| d = json.dumps(layers, separators=(",", ": ")) | ||
| t = json.dumps(ticks, separators=(",", ": ")) | ||
| m = json.dumps(margin, separators=(",", ": ")) | ||
|
|
||
| return f'''<div id="{svg_id}" style="position:relative;display:inline-block;font-family:system-ui,sans-serif;padding:10px 10px 40px 10px"> | ||
| <div style="margin-bottom:6px"> | ||
| <button class="sv-btn" data-mode="uniform" style="font-size:12px;padding:3px 10px;cursor:pointer;border:1px solid #aaa;border-radius:3px;background:#e0e0e0">Uniform</button> | ||
| <button class="sv-btn" data-mode="scale" style="font-size:12px;padding:3px 10px;cursor:pointer;border:1px solid #aaa;border-radius:3px;background:#fff;margin-left:4px">To Scale</button> | ||
| </div> | ||
| <svg width="{svg_w}" height="{svg_h}" xmlns="http://www.w3.org/2000/svg" font-family="system-ui,sans-serif" font-size="11"> |
There was a problem hiding this comment.
🚨 issue (security): Escape JSON when embedding into inline <script> blocks to avoid '</script>' breakage or XSS edge cases.
Since this JSON is embedded directly into a <script> block (e.g. const data={d}), it should be escaped to avoid prematurely closing the script with sequences like </script> and to reduce XSS risk if these values ever become user-controlled. Consider wrapping json.dumps with a helper that post-processes the output (for example, json.dumps(...).replace('</', '<\/')).
| tip.innerHTML=`<b>${{d.name}}</b><br>GDS: ${{d.gds??"N/A"}}<br>Material: ${{d.material}}<br>Type: ${{d.type}}<br>z: ${{d.zmin}}–${{d.zmax}} µm<br>Thickness: ${{d.thickness}} µm`; | ||
| const r=root.getBoundingClientRect();tip.style.display="block"; |
There was a problem hiding this comment.
🚨 issue (security): Avoid directly injecting unescaped data into innerHTML for tooltips.
tip.innerHTML is built from raw d.name, d.material, etc. If these ever contain <, &, or other HTML, the tooltip can be malformed or expose HTML injection/XSS risk. Please either escape these values before concatenation or build the tooltip via DOM APIs (using textContent for text nodes) to avoid relying on HTML parsing.
| const g=e.target.closest("g[data-idx]");if(!g){{tip.style.display="none";return}} | ||
| const d=data[+g.dataset.idx]; | ||
| tip.innerHTML=`<b>${{d.name}}</b><br>GDS: ${{d.gds??"N/A"}}<br>Material: ${{d.material}}<br>Type: ${{d.type}}<br>z: ${{d.zmin}}–${{d.zmax}} µm<br>Thickness: ${{d.thickness}} µm`; | ||
| const r=root.getBoundingClientRect();tip.style.display="block"; |
There was a problem hiding this comment.
🚨 issue (security): Apply the same HTML-escaping considerations to cross-section tooltips.
This tooltip also sets innerHTML from raw d.name / d.material. For consistency and to avoid XSS-style issues, please escape these values or construct the tooltip via DOM nodes using textContent instead.
Summary
.github/write_layer_stack.pythat generatesdocs/layer_stack.mdwith interactive SVG/JS visualizationsmake docsCloses #232
Reminder
Test plan
make docslocally and verifydocs/layer_stack.mdis generated🤖 Generated with Claude Code
Summary by Sourcery
Generate an interactive layer stack documentation page and integrate it into the docs build.
New Features:
Build: