Demo: static HTML viewer sharing code with live web viewer#9944
Demo: static HTML viewer sharing code with live web viewer#9944oharboe wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
Conversation
Try it:
bazelisk run //test/orfs/gcd:gcd_route_html
bazelisk run //test/orfs/gcd:gcd_route_web
The static HTML opens from file:// with all data pre-embedded.
Zero server, zero click-and-wait. Same GoldenLayout viewer as
the live web server, same widgets, same CSS — only the data
source differs (embedded JSON vs WebSocket).
This is a demo PR showing how these two use-cases can share code.
Much refinement is needed, but the architecture is proven. The
intent is for maintainers to pick useful ideas from this demo
(telling Claude to apply them on their branch), then close it.
What works:
- web_export_json TCL command extracts timing data as JSON
- StaticDataManager drops in for WebSocketManager (zero widget changes)
- Timing paths table (50 paths), endpoint slack histogram
- All stages: synth/floorplan/place/cts/grt/route
- All existing JS tests pass
See test/orfs/gcd/WEB.md for full docs and debug tips.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
6e0693b to
d66f432
Compare
|
clang-tidy review says "All clean, LGTM! 👍" |
1 similar comment
|
clang-tidy review says "All clean, LGTM! 👍" |
There was a problem hiding this comment.
Code Review
This pull request introduces static HTML reporting capabilities for the OpenROAD web viewer. Key changes include new Bazel build rules, JavaScript modules for static data management and rendering, Node.js scripts for HTML assembly, and a Tcl command to export timing data as JSON. Review comments suggest improving build hermeticity by managing external dependencies via Bazel instead of curl, making JSON file location more robust in shell scripts, removing redundant widget update calls in main.js, and making the number of exported timing paths configurable. Additionally, the documentation for debugging the static HTML viewer should address the hardcoded Chromium executable path.
| function getGoldenLayoutBundle() { | ||
| if (existsSync(GL_CACHE)) return readFileSync(GL_CACHE, 'utf-8'); | ||
| console.log('Downloading GoldenLayout bundle...'); | ||
| const esm = execSync(`curl -sL "${GL_URL}"`, { encoding: 'utf-8', maxBuffer: 1024 * 1024 }); |
There was a problem hiding this comment.
Downloading dependencies using curl during script execution makes the build process non-hermetic and dependent on network availability. This can lead to flaky or slow builds. It would be more robust to manage this external dependency using Bazel's external repository mechanisms (e.g., http_file or http_archive) to fetch and cache the file.
| exit 1 | ||
| fi | ||
|
|
||
| JSON=$(find "$RUNFILES/_main" -name "*.json" -path "*/test/orfs/*" 2>/dev/null | head -1) |
|
|
||
| WEB_SRC="$RUNFILES/_main/src/web/src" | ||
| RENDER_PAGE="$WEB_SRC/render-static-page.js" | ||
| JSON=$(find "$RUNFILES/_main" -name "*.json" -path "*/test/orfs/*" 2>/dev/null | head -1) |
|
|
||
| function createTimingWidget(container) { | ||
| app.timingWidget = new TimingWidget(container, app, redrawAllLayers); | ||
| if (app.staticMode) setTimeout(() => app.timingWidget.update(), 100); |
There was a problem hiding this comment.
This setTimeout to update the widget in static mode appears to be redundant. The widgets are already updated within the app.websocketManager.readyPromise.then block (lines 673-674), which is a more appropriate place for this logic. This call can be removed to avoid a duplicate update and simplify the code.
|
|
||
| function createChartsWidget(container) { | ||
| app.chartsWidget = new ChartsWidget(container, app, redrawAllLayers); | ||
| if (app.staticMode) setTimeout(() => app.chartsWidget.update(), 100); |
There was a problem hiding this comment.
This setTimeout to update the widget in static mode appears to be redundant. The widgets are already updated within the app.websocketManager.readyPromise.then block (lines 673-674), which is a more appropriate place for this logic. This call can be removed to avoid a duplicate update and simplify the code.
| } | ||
|
|
||
| // Fetch GoldenLayout ESM bundle from esm.sh and convert to IIFE. | ||
| const GL_CACHE = '/tmp/golden-layout-2.6.0-iife.js'; |
There was a problem hiding this comment.
Using a hardcoded path in /tmp for caching can be problematic. It's not guaranteed to persist between reboots, and might have permission issues or conflicts in multi-user environments. For better hermeticity and reliability, consider using a path within the project's build directory (e.g., managed by Bazel).
| // Timing paths — setup (top 50) | ||
| builder.beginObject("timing_report_setup"); | ||
| serializePaths(builder, report.getReport(true, 50)); | ||
| builder.endObject(); | ||
|
|
||
| // Timing paths — hold (top 50) | ||
| builder.beginObject("timing_report_hold"); | ||
| serializePaths(builder, report.getReport(false, 50)); | ||
| builder.endObject(); |
There was a problem hiding this comment.
The number of timing paths to export (50) is hardcoded for both setup and hold reports. This 'magic number' reduces flexibility and maintainability. Consider defining it as a named constant at the top of the function. For even greater flexibility, this could be exposed as an optional parameter to the web_export_json TCL command.
| node --input-type=module << 'SCRIPT' | ||
| import puppeteer from '/tmp/node_modules/puppeteer-core/lib/esm/puppeteer/puppeteer-core.js'; | ||
| const browser = await puppeteer.launch({ | ||
| executablePath: '/snap/bin/chromium', |
There was a problem hiding this comment.
The executablePath is hardcoded to a path specific to Ubuntu with Snap (/snap/bin/chromium). This will fail for users on other operating systems or with different installation methods. It would be helpful to add a comment noting that this path is system-dependent and needs to be adjusted by the user.
|
This is what we'll discuss tomorrow, this is just FYI. You can test it out even if it is closed, so out of sight, out of mind as far as a PR is concerned. |
|
replaced by #10087 |


Try it:
The static HTML opens from file:// with all data pre-embedded. Zero server, zero click-and-wait. Same GoldenLayout viewer as the live web server, same widgets, same CSS — only the data source differs (embedded JSON vs WebSocket).
This is a demo PR showing how these two use-cases can share code. Much refinement is needed, but the architecture is proven. The intent is for maintainers to pick useful ideas from this demo (telling Claude to apply them on their branch), then close it.
What works:
See test/orfs/gcd/WEB.md for full docs and debug tips.
Summary
[Describe your changes here]
Type of Change
Impact
[How does this change the tool's behavior?]
Verification
./etc/Build.sh).Related Issues
[Link issues here]