Skip to content

web: Remove 'Update' button in static HTML GUI (#10220)#10349

Open
openroad-ci wants to merge 3 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:remove-button-update-static-GUI
Open

web: Remove 'Update' button in static HTML GUI (#10220)#10349
openroad-ci wants to merge 3 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:remove-button-update-static-GUI

Conversation

@openroad-ci
Copy link
Copy Markdown
Collaborator

fix #10220

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

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 a "static mode" for the ChartsWidget, ClockTreeWidget, HierarchyBrowser, and TimingWidget components, which hides the "Update" button, automates data fetching on initialization, and updates empty-state messages. The reviewer noted that the PR contains extensive unrelated formatting and indentation changes across all modified files and requested that these refactoring efforts be moved to a separate pull request to keep the functional changes focused.

// Canvas-based clock tree viewer widget.

import { getThemeColors } from './theme.js';
import {getThemeColors} from './theme.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

The pull request includes formatting changes (e.g., import style) that are unrelated to the main task of removing the 'Update' button. Please separate these unrelated refactoring changes into a distinct pull request to keep the current PR focused.

Comment on lines +6 to 19
import {CheckboxTreeModel} from './checkbox-tree-model.js';
import {makeResizableHeaders} from './ui-utils.js';

const COLS = [
'Instance', 'Module', 'Instances', 'Macros', 'Modules',
'Area', 'Local Inst', 'Local Macros', 'Local Modules',
'Instance',
'Module',
'Instances',
'Macros',
'Modules',
'Area',
'Local Inst',
'Local Macros',
'Local Modules',
];
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 pull request includes several formatting changes (e.g., import style, array formatting) that are unrelated to the main task of removing the 'Update' button. Please separate these unrelated refactoring changes into a distinct pull request to keep the current PR focused.

Comment on lines +6 to +407
import {makeResizableHeaders} from './ui-utils.js';

export class TimingWidget {
constructor(app, redrawAllLayers) {
this._app = app;
this._redrawAllLayers = redrawAllLayers;

this._currentTab = 'setup';
this._setupPaths = [];
this._holdPaths = [];
this._selectedPathIndex = -1;
this._detailTab = 'data';
this._selectedDetailIndex = -1;

this._build();
constructor(app, redrawAllLayers) {
this._app = app;
this._redrawAllLayers = redrawAllLayers;

this._currentTab = 'setup';
this._setupPaths = [];
this._holdPaths = [];
this._selectedPathIndex = -1;
this._detailTab = 'data';
this._selectedDetailIndex = -1;

this._build();
}

_build() {
const el = document.createElement('div');
el.className = 'timing-widget';

// --- Toolbar ---
const toolbar = document.createElement('div');
toolbar.className = 'timing-toolbar';

this._updateBtn = document.createElement('button');
this._updateBtn.className = 'timing-btn';
this._updateBtn.textContent = 'Update';
if (this._app.websocketManager && this._app.websocketManager.isStaticMode) {
this._updateBtn.style.display = 'none';
}

_build() {
const el = document.createElement('div');
el.className = 'timing-widget';

// --- Toolbar ---
const toolbar = document.createElement('div');
toolbar.className = 'timing-toolbar';

this._updateBtn = document.createElement('button');
this._updateBtn.className = 'timing-btn';
this._updateBtn.textContent = 'Update';

this._pathCountLabel = document.createElement('span');
this._pathCountLabel.className = 'timing-path-count';

toolbar.appendChild(this._updateBtn);
toolbar.appendChild(this._pathCountLabel);
el.appendChild(toolbar);

// --- Setup/Hold Tab Bar ---
const tabBar = document.createElement('div');
tabBar.className = 'timing-tab-bar';

this._setupTab = this._makeTab('Setup', true);
this._holdTab = this._makeTab('Hold', false);
tabBar.appendChild(this._setupTab);
tabBar.appendChild(this._holdTab);
el.appendChild(tabBar);

// --- Path listing table ---
this._pathTableContainer = document.createElement('div');
this._pathTableContainer.className = 'timing-path-table-container';
this._pathTable = document.createElement('table');
this._pathTable.className = 'timing-table';
this._pathTableContainer.appendChild(this._pathTable);
el.appendChild(this._pathTableContainer);

// --- Detail Tab Bar ---
const detailTabBar = document.createElement('div');
detailTabBar.className = 'timing-tab-bar';
this._dataTab = this._makeTab('Data Path', true);
this._captureTab = this._makeTab('Capture Path', false);
detailTabBar.appendChild(this._dataTab);
detailTabBar.appendChild(this._captureTab);
el.appendChild(detailTabBar);

// --- Detail table ---
this._detailTableContainer = document.createElement('div');
this._detailTableContainer.className = 'timing-detail-table-container';
this._detailTable = document.createElement('table');
this._detailTable.className = 'timing-table';
this._detailTableContainer.appendChild(this._detailTable);
el.appendChild(this._detailTableContainer);

this.element = el;

this._bindEvents();
this._pathCountLabel = document.createElement('span');
this._pathCountLabel.className = 'timing-path-count';

toolbar.appendChild(this._updateBtn);
toolbar.appendChild(this._pathCountLabel);
el.appendChild(toolbar);

// --- Setup/Hold Tab Bar ---
const tabBar = document.createElement('div');
tabBar.className = 'timing-tab-bar';

this._setupTab = this._makeTab('Setup', true);
this._holdTab = this._makeTab('Hold', false);
tabBar.appendChild(this._setupTab);
tabBar.appendChild(this._holdTab);
el.appendChild(tabBar);

// --- Path listing table ---
this._pathTableContainer = document.createElement('div');
this._pathTableContainer.className = 'timing-path-table-container';
this._pathTable = document.createElement('table');
this._pathTable.className = 'timing-table';
this._pathTableContainer.appendChild(this._pathTable);
el.appendChild(this._pathTableContainer);

// --- Detail Tab Bar ---
const detailTabBar = document.createElement('div');
detailTabBar.className = 'timing-tab-bar';
this._dataTab = this._makeTab('Data Path', true);
this._captureTab = this._makeTab('Capture Path', false);
detailTabBar.appendChild(this._dataTab);
detailTabBar.appendChild(this._captureTab);
el.appendChild(detailTabBar);

// --- Detail table ---
this._detailTableContainer = document.createElement('div');
this._detailTableContainer.className = 'timing-detail-table-container';
this._detailTable = document.createElement('table');
this._detailTable.className = 'timing-table';
this._detailTableContainer.appendChild(this._detailTable);
el.appendChild(this._detailTableContainer);

this.element = el;

this._bindEvents();

if (this._app.websocketManager && this._app.websocketManager.isStaticMode) {
setTimeout(() => this.update(), 0);
}

_makeTab(label, active) {
const btn = document.createElement('button');
btn.className = 'timing-tab' + (active ? ' active' : '');
btn.textContent = label;
return btn;
}

_bindEvents() {
// Tab switching
this._setupTab.addEventListener('click', () => {
this._currentTab = 'setup';
this._setupTab.classList.add('active');
this._holdTab.classList.remove('active');
this._selectedPathIndex = -1;
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
});
this._holdTab.addEventListener('click', () => {
this._currentTab = 'hold';
this._holdTab.classList.add('active');
this._setupTab.classList.remove('active');
this._selectedPathIndex = -1;
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
});
this._dataTab.addEventListener('click', () => {
this._detailTab = 'data';
this._dataTab.classList.add('active');
this._captureTab.classList.remove('active');
this._renderDetailTable();
});
this._captureTab.addEventListener('click', () => {
this._detailTab = 'capture';
this._captureTab.classList.add('active');
this._dataTab.classList.remove('active');
this._renderDetailTable();
});

// Fetch paths
this._updateBtn.addEventListener('click', () => this.update());

// Keyboard navigation — path table
this._pathTableContainer.setAttribute('tabindex', '0');
this._pathTableContainer.style.outline = 'none';
this._pathTableContainer.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
const rows = this._pathTable.querySelectorAll('tbody tr');
if (this._selectedPathIndex < rows.length - 1) {
this._selectPathRow(this._selectedPathIndex + 1);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (this._selectedPathIndex > 0) {
this._selectPathRow(this._selectedPathIndex - 1);
}
}
});

// Keyboard navigation — detail table
this._detailTableContainer.setAttribute('tabindex', '0');
this._detailTableContainer.style.outline = 'none';
this._detailTableContainer.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
const rows = this._detailTable.querySelectorAll('tbody tr');
if (this._selectedDetailIndex < rows.length - 1) {
this._selectDetailRow(this._selectedDetailIndex + 1);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (this._selectedDetailIndex > 0) {
this._selectDetailRow(this._selectedDetailIndex - 1);
}
}
});
}

showPaths(tab, paths) {
this._currentTab = tab;
if (tab === 'setup') {
this._setupPaths = paths;
this._setupTab.classList.add('active');
this._holdTab.classList.remove('active');
} else {
this._holdPaths = paths;
this._holdTab.classList.add('active');
this._setupTab.classList.remove('active');
}

_makeTab(label, active) {
const btn = document.createElement('button');
btn.className = 'timing-tab' + (active ? ' active' : '');
btn.textContent = label;
return btn;
}

_bindEvents() {
// Tab switching
this._setupTab.addEventListener('click', () => {
this._currentTab = 'setup';
this._setupTab.classList.add('active');
this._holdTab.classList.remove('active');
this._selectedPathIndex = -1;
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
});
this._holdTab.addEventListener('click', () => {
this._currentTab = 'hold';
this._holdTab.classList.add('active');
this._setupTab.classList.remove('active');
this._selectedPathIndex = -1;
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
});
this._dataTab.addEventListener('click', () => {
this._detailTab = 'data';
this._dataTab.classList.add('active');
this._captureTab.classList.remove('active');
this._renderDetailTable();
});
this._captureTab.addEventListener('click', () => {
this._detailTab = 'capture';
this._captureTab.classList.add('active');
this._dataTab.classList.remove('active');
this._renderDetailTable();
});

// Fetch paths
this._updateBtn.addEventListener('click', () => this.update());

// Keyboard navigation — path table
this._pathTableContainer.setAttribute('tabindex', '0');
this._pathTableContainer.style.outline = 'none';
this._pathTableContainer.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
const rows = this._pathTable.querySelectorAll('tbody tr');
if (this._selectedPathIndex < rows.length - 1) {
this._selectPathRow(this._selectedPathIndex + 1);
}
this._selectedPathIndex = -1;
this._pathCountLabel.textContent = paths.length + ' paths';
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
}

async update() {
this._updateBtn.disabled = true;
this._updateBtn.textContent = 'Loading...';
try {
const [setupData, holdData] = await Promise.all([
this._app.websocketManager.request({ type: 'timing_report', is_setup: true, max_paths: 100 }),
this._app.websocketManager.request({ type: 'timing_report', is_setup: false, max_paths: 100 }),
]);
this._setupPaths = setupData.paths || [];
this._holdPaths = holdData.paths || [];
this._selectedPathIndex = -1;
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
} catch (e) {
console.error('Timing fetch failed:', e);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (this._selectedPathIndex > 0) {
this._selectPathRow(this._selectedPathIndex - 1);
}
}
});

// Keyboard navigation — detail table
this._detailTableContainer.setAttribute('tabindex', '0');
this._detailTableContainer.style.outline = 'none';
this._detailTableContainer.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
const rows = this._detailTable.querySelectorAll('tbody tr');
if (this._selectedDetailIndex < rows.length - 1) {
this._selectDetailRow(this._selectedDetailIndex + 1);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (this._selectedDetailIndex > 0) {
this._selectDetailRow(this._selectedDetailIndex - 1);
}
this._updateBtn.disabled = false;
this._updateBtn.textContent = 'Update';
}
});
}

showPaths(tab, paths) {
this._currentTab = tab;
if (tab === 'setup') {
this._setupPaths = paths;
this._setupTab.classList.add('active');
this._holdTab.classList.remove('active');
} else {
this._holdPaths = paths;
this._holdTab.classList.add('active');
this._setupTab.classList.remove('active');
}

_clearTimingHighlight() {
this._app.websocketManager.request({ type: 'timing_highlight', path_index: -1 })
.then(() => this._redrawAllLayers());
this._selectedPathIndex = -1;
this._pathCountLabel.textContent = paths.length + ' paths';
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
}

async update() {
this._updateBtn.disabled = true;
this._updateBtn.textContent = 'Loading...';
try {
const [setupData, holdData] = await Promise.all([
this._app.websocketManager.request(
{type: 'timing_report', is_setup: true, max_paths: 100}),
this._app.websocketManager.request(
{type: 'timing_report', is_setup: false, max_paths: 100}),
]);
this._setupPaths = setupData.paths || [];
this._holdPaths = holdData.paths || [];
this._selectedPathIndex = -1;
this._renderPathTable();
this._renderDetailTable();
this._clearTimingHighlight();
} catch (e) {
console.error('Timing fetch failed:', e);
}

_selectPathRow(idx) {
const rows = this._pathTable.querySelectorAll('tbody tr');
if (idx < 0 || idx >= rows.length) return;
this._selectedPathIndex = idx;
for (const row of rows) row.classList.remove('selected');
rows[idx].classList.add('selected');
rows[idx].scrollIntoView({ block: 'nearest' });
this._pathTableContainer.focus();
this._renderDetailTable();
// Use _originalIndex when paths were filtered (e.g. by histogram
// column click in static mode) so the overlay lookup matches.
const paths = this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
const highlightIdx = paths[idx]?._originalIndex ?? idx;
this._app.websocketManager.request({
type: 'timing_highlight',
path_index: highlightIdx,
is_setup: this._currentTab === 'setup',
}).then(() => this._redrawAllLayers())
.catch(err => console.error('timing_highlight error:', err));
this._updateBtn.disabled = false;
this._updateBtn.textContent = 'Update';
}

_clearTimingHighlight() {
this._app.websocketManager
.request({type: 'timing_highlight', path_index: -1})
.then(() => this._redrawAllLayers());
}

_selectPathRow(idx) {
const rows = this._pathTable.querySelectorAll('tbody tr');
if (idx < 0 || idx >= rows.length) return;
this._selectedPathIndex = idx;
for (const row of rows) row.classList.remove('selected');
rows[idx].classList.add('selected');
rows[idx].scrollIntoView({block: 'nearest'});
this._pathTableContainer.focus();
this._renderDetailTable();
// Use _originalIndex when paths were filtered (e.g. by histogram
// column click in static mode) so the overlay lookup matches.
const paths =
this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
const highlightIdx = paths[idx]?._originalIndex ?? idx;
this._app.websocketManager
.request({
type: 'timing_highlight',
path_index: highlightIdx,
is_setup: this._currentTab === 'setup',
})
.then(() => this._redrawAllLayers())
.catch(err => console.error('timing_highlight error:', err));
}

_renderPathTable() {
const paths =
this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
this._pathCountLabel.textContent = paths.length + ' paths';

// Preserve column widths across re-renders.
const oldHeaders = this._pathTable.querySelectorAll('thead th');
const savedWidths = Array.from(oldHeaders, th => th.style.width);

this._pathTable.innerHTML = '';

const thead = document.createElement('thead');
const hr = document.createElement('tr');
for (const col of TimingWidget.PATH_COLS) {
const th = document.createElement('th');
th.textContent = col;
hr.appendChild(th);
}

_renderPathTable() {
const paths = this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
this._pathCountLabel.textContent = paths.length + ' paths';

// Preserve column widths across re-renders.
const oldHeaders = this._pathTable.querySelectorAll('thead th');
const savedWidths = Array.from(oldHeaders, th => th.style.width);

this._pathTable.innerHTML = '';

const thead = document.createElement('thead');
const hr = document.createElement('tr');
for (const col of TimingWidget.PATH_COLS) {
const th = document.createElement('th');
th.textContent = col;
hr.appendChild(th);
}
thead.appendChild(hr);
this._pathTable.appendChild(thead);

const tbody = document.createElement('tbody');
paths.forEach((p, idx) => {
const tr = document.createElement('tr');
if (idx === this._selectedPathIndex) tr.classList.add('selected');
const vals = [
p.end_clk,
fmtTime(p.required),
fmtTime(p.arrival),
fmtTime(p.slack),
fmtTime(p.skew),
fmtTime(p.path_delay),
p.logic_depth,
p.fanout,
p.start_pin,
p.end_pin,
];
vals.forEach((v, ci) => {
const td = document.createElement('td');
td.textContent = v;
if (ci === 3 && p.slack < 0) td.classList.add('slack-negative');
tr.appendChild(td);
});
tr.style.cursor = 'pointer';
tr.addEventListener('click', () => this._selectPathRow(idx));
tbody.appendChild(tr);
});
this._pathTable.appendChild(tbody);

// Restore previous widths if available, otherwise compute fresh.
if (savedWidths.length > 0 && savedWidths[0]) {
const newHeaders = this._pathTable.querySelectorAll('thead th');
this._pathTable.style.tableLayout = 'fixed';
newHeaders.forEach((th, i) => {
if (i < savedWidths.length) th.style.width = savedWidths[i];
});
} else {
makeResizableHeaders(this._pathTable);
}
thead.appendChild(hr);
this._pathTable.appendChild(thead);

const tbody = document.createElement('tbody');
paths.forEach((p, idx) => {
const tr = document.createElement('tr');
if (idx === this._selectedPathIndex) tr.classList.add('selected');
const vals = [
p.end_clk,
fmtTime(p.required),
fmtTime(p.arrival),
fmtTime(p.slack),
fmtTime(p.skew),
fmtTime(p.path_delay),
p.logic_depth,
p.fanout,
p.start_pin,
p.end_pin,
];
vals.forEach((v, ci) => {
const td = document.createElement('td');
td.textContent = v;
if (ci === 3 && p.slack < 0) td.classList.add('slack-negative');
tr.appendChild(td);
});
tr.style.cursor = 'pointer';
tr.addEventListener('click', () => this._selectPathRow(idx));
tbody.appendChild(tr);
});
this._pathTable.appendChild(tbody);

// Restore previous widths if available, otherwise compute fresh.
if (savedWidths.length > 0 && savedWidths[0]) {
const newHeaders = this._pathTable.querySelectorAll('thead th');
this._pathTable.style.tableLayout = 'fixed';
newHeaders.forEach((th, i) => {
if (i < savedWidths.length) th.style.width = savedWidths[i];
});
} else {
makeResizableHeaders(this._pathTable);
}

_selectDetailRow(idx) {
const rows = this._detailTable.querySelectorAll('tbody tr');
if (idx < 0 || idx >= rows.length) return;
this._selectedDetailIndex = idx;
for (const row of rows) {
row.classList.remove('timing-selected-row');
}
rows[idx].classList.add('timing-selected-row');
rows[idx].scrollIntoView({ block: 'nearest' });
this._detailTableContainer.focus();

const paths = this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
const path = paths[this._selectedPathIndex];
const nodes = this._detailTab === 'data' ? path.data_nodes : path.capture_nodes;
// Use _originalIndex when paths were filtered (e.g. by histogram
// column click in static mode) so the overlay lookup matches.
const highlightIdx = path._originalIndex ?? this._selectedPathIndex;
this._app.websocketManager.request({
type: 'timing_highlight',
path_index: highlightIdx,
is_setup: this._currentTab === 'setup',
pin_name: nodes[idx].pin,
}).then(() => this._redrawAllLayers());
}

_selectDetailRow(idx) {
const rows = this._detailTable.querySelectorAll('tbody tr');
if (idx < 0 || idx >= rows.length) return;
this._selectedDetailIndex = idx;
for (const row of rows) {
row.classList.remove('timing-selected-row');
}

_renderDetailTable() {
// Preserve column widths across re-renders.
const oldHeaders = this._detailTable.querySelectorAll('thead th');
const savedWidths = Array.from(oldHeaders, th => th.style.width);

this._detailTable.innerHTML = '';
this._selectedDetailIndex = -1;
const paths = this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
if (this._selectedPathIndex < 0 || this._selectedPathIndex >= paths.length) return;

const path = paths[this._selectedPathIndex];
const nodes = this._detailTab === 'data' ? path.data_nodes : path.capture_nodes;

const thead = document.createElement('thead');
const hr = document.createElement('tr');
for (const col of TimingWidget.DETAIL_COLS) {
const th = document.createElement('th');
th.textContent = col;
hr.appendChild(th);
}
thead.appendChild(hr);
this._detailTable.appendChild(thead);

const tbody = document.createElement('tbody');
nodes.forEach((n, idx) => {
const tr = document.createElement('tr');
if (n.clk) tr.classList.add('timing-clock-row');
const vals = [
n.pin,
n.fanout,
n.rise ? '↑' : '↓',
fmtTime(n.time),
fmtTime(n.delay),
fmtTime(n.slew),
fmtTime(n.load),
];
for (const v of vals) {
const td = document.createElement('td');
td.textContent = v;
tr.appendChild(td);
}
tr.style.cursor = 'pointer';
tr.addEventListener('click', () => this._selectDetailRow(idx));
tbody.appendChild(tr);
});
this._detailTable.appendChild(tbody);

if (savedWidths.length > 0 && savedWidths[0]) {
const newHeaders = this._detailTable.querySelectorAll('thead th');
this._detailTable.style.tableLayout = 'fixed';
newHeaders.forEach((th, i) => {
if (i < savedWidths.length) th.style.width = savedWidths[i];
});
} else {
makeResizableHeaders(this._detailTable);
// Pin column: set initial width to 30 characters
const pinTh = this._detailTable.querySelector('thead th');
if (pinTh) {
pinTh.style.width = '30ch';
}
}
rows[idx].classList.add('timing-selected-row');
rows[idx].scrollIntoView({block: 'nearest'});
this._detailTableContainer.focus();

const paths =
this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
const path = paths[this._selectedPathIndex];
const nodes =
this._detailTab === 'data' ? path.data_nodes : path.capture_nodes;
// Use _originalIndex when paths were filtered (e.g. by histogram
// column click in static mode) so the overlay lookup matches.
const highlightIdx = path._originalIndex ?? this._selectedPathIndex;
this._app.websocketManager
.request({
type: 'timing_highlight',
path_index: highlightIdx,
is_setup: this._currentTab === 'setup',
pin_name: nodes[idx].pin,
})
.then(() => this._redrawAllLayers());
}

_renderDetailTable() {
// Preserve column widths across re-renders.
const oldHeaders = this._detailTable.querySelectorAll('thead th');
const savedWidths = Array.from(oldHeaders, th => th.style.width);

this._detailTable.innerHTML = '';
this._selectedDetailIndex = -1;
const paths =
this._currentTab === 'setup' ? this._setupPaths : this._holdPaths;
if (this._selectedPathIndex < 0 || this._selectedPathIndex >= paths.length)
return;

const path = paths[this._selectedPathIndex];
const nodes =
this._detailTab === 'data' ? path.data_nodes : path.capture_nodes;

const thead = document.createElement('thead');
const hr = document.createElement('tr');
for (const col of TimingWidget.DETAIL_COLS) {
const th = document.createElement('th');
th.textContent = col;
hr.appendChild(th);
}

thead.appendChild(hr);
this._detailTable.appendChild(thead);

const tbody = document.createElement('tbody');
nodes.forEach((n, idx) => {
const tr = document.createElement('tr');
if (n.clk) tr.classList.add('timing-clock-row');
const vals = [
n.pin,
n.fanout,
n.rise ? '↑' : '↓',
fmtTime(n.time),
fmtTime(n.delay),
fmtTime(n.slew),
fmtTime(n.load),
];
for (const v of vals) {
const td = document.createElement('td');
td.textContent = v;
tr.appendChild(td);
}
tr.style.cursor = 'pointer';
tr.addEventListener('click', () => this._selectDetailRow(idx));
tbody.appendChild(tr);
});
this._detailTable.appendChild(tbody);

if (savedWidths.length > 0 && savedWidths[0]) {
const newHeaders = this._detailTable.querySelectorAll('thead th');
this._detailTable.style.tableLayout = 'fixed';
newHeaders.forEach((th, i) => {
if (i < savedWidths.length) th.style.width = savedWidths[i];
});
} else {
makeResizableHeaders(this._detailTable);
// Pin column: set initial width to 30 characters
const pinTh = this._detailTable.querySelector('thead th');
if (pinTh) {
pinTh.style.width = '30ch';
}
}
}
}

export function fmtTime(v) {
if (v === undefined || v === null) return '';
return typeof v === 'number' ? v.toFixed(4) : String(v);
if (v === undefined || v === null) return '';
return typeof v === 'number' ? v.toFixed(4) : String(v);
}

TimingWidget.PATH_COLS = ['Clock', 'Required', 'Arrival', 'Slack', 'Skew',
'Logic Delay', 'Logic Depth', 'Fanout', 'Start', 'End'];
TimingWidget.DETAIL_COLS = ['Pin', 'Fanout', 'R/F', 'Time', 'Delay', 'Slew', 'Load'];
TimingWidget.PATH_COLS = [
'Clock', 'Required', 'Arrival', 'Slack', 'Skew', 'Logic Delay', 'Logic Depth',
'Fanout', 'Start', 'End'
];
TimingWidget.DETAIL_COLS =
['Pin', 'Fanout', 'R/F', 'Time', 'Delay', 'Slew', 'Load'];
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 pull request includes several formatting changes (e.g., import style, array formatting) that are unrelated to the main task of removing the 'Update' button. Please separate these unrelated refactoring changes into a distinct pull request to keep the current PR focused.

Pure formatting pass over four widget files in preparation for the
The-OpenROAD-Project#10220 static-mode fix. Switches 4-space indentation to 2-space and
normalizes a few import/array literals. No runtime behavior changes.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
…10220)

In the static HTML report (window.__STATIC_CACHE__) the Update button
serves no purpose -- responses come from an in-page cache. Hide it in
the four widgets that show it (charts, clock-tree, hierarchy, timing)
and auto-fetch their data on construction. Update the empty-state
messages in charts and clock-tree to reflect that the user cannot
trigger a manual fetch.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
@openroad-ci openroad-ci force-pushed the remove-button-update-static-GUI branch from 4902230 to 8973930 Compare May 17, 2026 07:14
@openroad-ci openroad-ci requested a review from a team as a code owner May 17, 2026 07:14
…enROAD-Project#10220)

In static mode the Update button is hidden, so users have no way to
retry a load if the table happens to be empty. Render a contextual
placeholder row inside the table body for both hierarchy-browser and
timing-widget.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
@github-actions
Copy link
Copy Markdown
Contributor

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

@openroad-ci openroad-ci force-pushed the remove-button-update-static-GUI branch from 8973930 to ecec639 Compare May 17, 2026 07:19
@github-actions
Copy link
Copy Markdown
Contributor

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

@maliberty
Copy link
Copy Markdown
Member

What are you doing that is reformatting the code? It obscures whatever the real change is in this PR.

@jorge-ferreira-pii
Copy link
Copy Markdown
Contributor

What are you doing that is reformatting the code? It obscures whatever the real change is in this PR.

Hi Matt, I use Claude to refactor what I've implemented, but I accidentally refactored the entire code. I apologize.

I will redo the fix.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove "Update" button in static HTML GUI

3 participants