Skip to content

Demo: static HTML viewer sharing code with live web viewer#9944

Closed
oharboe wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
oharboe:static-html-dmeo
Closed

Demo: static HTML viewer sharing code with live web viewer#9944
oharboe wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
oharboe:static-html-dmeo

Conversation

@oharboe
Copy link
Copy Markdown
Collaborator

@oharboe oharboe commented Mar 25, 2026

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.

Summary

[Describe your changes here]

Type of Change

  • New feature

Impact

[How does this change the tool's behavior?]

Verification

  • I have verified that the local build succeeds (./etc/Build.sh).
  • I have run the relevant tests and they pass.
  • My code follows the repository's formatting guidelines.
  • I have signed my commits (DCO).

Related Issues

[Link issues here]

@oharboe oharboe requested a review from maliberty March 25, 2026 15:20
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>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of find ... | head -1 to locate the JSON file is fragile and can lead to unpredictable behavior if multiple JSON files are present. For a more robust solution, consider accepting the JSON file path as a command-line argument.


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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of find ... | head -1 to locate the JSON file is fragile and can lead to unpredictable behavior if multiple JSON files are present. For a more robust solution, consider accepting the JSON file path as a command-line argument.


function createTimingWidget(container) {
app.timingWidget = new TimingWidget(container, app, redrawAllLayers);
if (app.staticMode) setTimeout(() => app.timingWidget.update(), 100);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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).

Comment on lines +144 to +152
// 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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

@oharboe
Copy link
Copy Markdown
Collaborator Author

oharboe commented Mar 30, 2026

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.

@oharboe oharboe closed this Mar 30, 2026
@maliberty
Copy link
Copy Markdown
Member

I tried it out but when loading the static version I see no layout and notice in the chrome console
image

@oharboe
Copy link
Copy Markdown
Collaborator Author

oharboe commented Mar 31, 2026

I tried it out but when loading the static version I see no layout and notice in the chrome console image

So security settings may have been different.

We're not going to use this code, it was just a proof point for me that we can share a single source of truth and have Claude implement the static web GUI.

My thinking now is that it is easier for you to maintain this if you vibe code the static HTML version yourself than to review other peoples vibe coding.

@maliberty
Copy link
Copy Markdown
Member

replaced by #10087

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants