mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-23 12:12:51 +00:00
fix(live): enable node filtering after its handlers are ready (#2103)
Red commit: `8974616` ([CI assertion failures](https://github.com/Kpa-clawbot/CoreScope/actions/runs/37064461392)). Fixes #2094. The Live node-filter input now starts natively disabled and becomes editable after its input, keyboard and blur handlers attach. Previously it accepted text while initial node loading was pending, then never processed that text without another keystroke. The change adds one HTML attribute and one initialization assignment. Failed initial loading still reaches the existing error handler and installs the controls. Saved filters, URL precedence, autocomplete and keyboard selection keep their existing behavior. No new settings, styling, dependencies, requests or packet-processing work. ## Validation - The existing CI-wired Live browser suite delays or aborts the real initial node request. It verifies rejected premature typing, later editability, actual suggestions, keyboard selection and URL state. Both new cases failed on the red commit; all six cases pass locally on `11fab46`. - All 183 standalone frontend suites passed, plus lint and XSS gates. Separate browser checks passed saved-filter restoration and URL override; desktop/mobile loading/ready screenshots were inspected. - A broader local core run exposed the unchanged stale observation-handle test tracked in #2104. It is outside this Live fix. Full [CI](https://github.com/Kpa-clawbot/CoreScope/actions/runs/37067337557) passed at `11fab46`, including Go, browsers and both container architectures; three independent reviews are clear. E2E assertion added: `tests/e2e/test-1110-live-filter.js:69`. Preflight overrides: external script/OpenClaw profile unavailable; repository checks and local Chromium used.
This commit is contained in:
1 parent
b9fb3d244c
commit
3b415fb007
2 files changed
+50
-1
No files matched your search
+3
-1
@@ -1300,7 +1300,7 @@
|
||||
<div class="live-toggles">
|
||||
<div class="live-node-filter-wrap" style="position:relative">
|
||||
<label class="live-node-filter-hitarea" style="display:inline-flex; align-items:center; min-height:44px; cursor:text;">
|
||||
<input type="text" id="liveNodeFilterInput" placeholder="Filter by node…" autocomplete="off" class="live-node-filter-input" role="combobox" aria-expanded="false" aria-owns="liveNodeFilterDropdown" aria-autocomplete="list" aria-activedescendant="">
|
||||
<input type="text" id="liveNodeFilterInput" placeholder="Filter by node…" autocomplete="off" class="live-node-filter-input" role="combobox" aria-expanded="false" aria-owns="liveNodeFilterDropdown" aria-autocomplete="list" aria-activedescendant="" disabled>
|
||||
</label>
|
||||
<div id="liveNodeFilterDropdown" class="live-node-filter-dropdown hidden" role="listbox"></div>
|
||||
<button id="liveNodeFilterClear" class="vcr-btn" title="Clear node filter" style="display:none">×</button>
|
||||
@@ -1903,6 +1903,8 @@
|
||||
// Slight delay so click on a suggestion can register first.
|
||||
setTimeout(hideDropdown, 150);
|
||||
});
|
||||
// Initial node loading yields before these handlers are installed (#2094).
|
||||
nodeFilterInput.disabled = false;
|
||||
}
|
||||
if (nodeFilterClear) {
|
||||
nodeFilterClear.addEventListener('click', () => {
|
||||
|
||||
@@ -41,6 +41,53 @@ async function test(name, fn) {
|
||||
page.setDefaultTimeout(10000);
|
||||
console.log(`#1110 Live filter E2E against ${BASE}`);
|
||||
|
||||
for (const abortInitialLoad of [false, true]) {
|
||||
await test(`#2094 node filter becomes editable after initial nodes ${abortInitialLoad ? 'fail' : 'load'}`, async () => {
|
||||
const response = await ctx.request.get(BASE + '/api/nodes?limit=1');
|
||||
assert(response.ok(), 'fixture node request must succeed');
|
||||
const { nodes } = await response.json();
|
||||
assert(nodes && nodes[0] && nodes[0].public_key, 'fixture must contain a searchable node');
|
||||
const key = nodes[0].public_key;
|
||||
const loadingPage = await ctx.newPage();
|
||||
loadingPage.setDefaultTimeout(10000);
|
||||
let releaseNodes;
|
||||
const heldNodes = new Promise(resolve => { releaseNodes = resolve; });
|
||||
await loadingPage.route(url => url.pathname === '/api/nodes', async route => {
|
||||
await heldNodes;
|
||||
if (abortInitialLoad) await route.abort();
|
||||
else await route.continue();
|
||||
});
|
||||
try {
|
||||
const initialRequest = loadingPage.waitForRequest(req => new URL(req.url()).pathname === '/api/nodes');
|
||||
await loadingPage.goto(BASE + '#/live', { waitUntil: 'domcontentloaded' });
|
||||
await initialRequest;
|
||||
const input = loadingPage.locator('#liveNodeFilterInput');
|
||||
await input.waitFor({ state: 'visible' });
|
||||
// A real keyboard cannot enter text into a control that is not ready.
|
||||
await input.evaluate(el => el.focus());
|
||||
await loadingPage.keyboard.type('pending');
|
||||
assert(await input.inputValue() === '', 'node filter accepted text before its handlers were ready');
|
||||
assert(await input.isDisabled(), 'node filter must be disabled while initial nodes are pending');
|
||||
releaseNodes();
|
||||
await loadingPage.waitForFunction(() => !document.getElementById('liveNodeFilterInput').disabled);
|
||||
await input.fill(key.slice(0, -1));
|
||||
const suggestion = loadingPage.locator(`#liveNodeFilterDropdown:not(.hidden) [data-key="${key}"]`);
|
||||
await suggestion.waitFor({ state: 'visible' });
|
||||
const optionKeys = await loadingPage.locator('#liveNodeFilterDropdown [data-key]')
|
||||
.evaluateAll(options => options.map(option => option.getAttribute('data-key')));
|
||||
for (let i = 0; i <= optionKeys.indexOf(key); i++) await input.press('ArrowDown');
|
||||
await input.press('Enter');
|
||||
const selected = await loadingPage.evaluate(() => window._liveGetNodeFilterKeys());
|
||||
assert(selected.length === 1 && selected[0] === key, 'keyboard selection must apply the matching node filter');
|
||||
assert(new URLSearchParams(loadingPage.url().split('?')[1]).get('node') === key,
|
||||
'selected node must be preserved in the URL');
|
||||
} finally {
|
||||
releaseNodes();
|
||||
await loadingPage.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await test('input matches toolbar styling (theme-aware bg, comparable height)', async () => {
|
||||
await page.goto(BASE + '#/live', { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#liveNodeFilterInput', { timeout: 10000 });
|
||||
|
||||
Reference in new issue
Block a user