mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-02 01:51:46 +00:00
Red commit: 7eeeee5d76 (CI run: pending —
first PR-triggered run)
Fixes #1619
## Problem
The `feed-detail-card` popup in the Live view (the one with the ↻ Replay
button) is undraggable and frequently sits behind the legend (z=1000) in
the lower-right, leaving the Replay button unreachable.
## Fix
1. `public/live.css` — bump `.feed-detail-card` z-index from `600` →
`1050` (above legend z=1000, below mobile bottom-nav z=1100). Immediate
unblock.
2. `public/live.js` — add a `<div class="panel-header">` containing a
small title + the existing close button to the card markup; register the
card with the existing `DragManager`. The bootstrap-scoped `dragMgr` is
exposed on `window._liveDragMgr` so the popup-creation site (outside
that scope) can call `dragMgr.register(card)` after appending.
Responsive gate (`enabled` flag) is handled inside DragManager — no
extra wiring needed.
No localStorage persistence: the popup is ephemeral (dismissed on
outside-click). Initial position (`right:14px; top:50%`) unchanged —
drag is opt-in.
## Test (RED → GREEN)
Source-invariant assertions on live.css and live.js:
- `.feed-detail-card` z-index === 1050
- card markup contains `.panel-header`
- `window._liveDragMgr` is assigned
- popup-creation site calls `_liveDragMgr.register(card)`
RED commit asserts all four — failed CI as expected. GREEN commit makes
them pass.
E2E assertion added: test-issue-1619-feed-detail-card-draggable.js:36
Triage:
https://github.com/Kpa-clawbot/CoreScope/issues/1619#issuecomment-4641392168
This commit is contained in:
@@ -135,6 +135,7 @@ jobs:
|
||||
node test-issue-1509-detect-preset.js
|
||||
node test-live.js
|
||||
node test-issue-1532-live-fullscreen.js
|
||||
node test-issue-1619-feed-detail-card-draggable.js
|
||||
node test-xss-escape-sinks.js
|
||||
node test-preflight-xss-gate.js
|
||||
|
||||
|
||||
+7
-3
@@ -786,7 +786,9 @@ body.live-fullscreen #liveFullscreenToggle:hover,
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
z-index: 600;
|
||||
/* #1619: was 600 — sat behind legend (z=1000); 1050 keeps it below
|
||||
mobile bottom-nav (z=1100) while clearing all live overlays. */
|
||||
z-index: 1050;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
||||
animation: fadeSlideIn 0.15s ease-out;
|
||||
font-size: .8rem;
|
||||
@@ -794,14 +796,16 @@ body.live-fullscreen #liveFullscreenToggle:hover,
|
||||
}
|
||||
@keyframes fadeSlideIn { from { opacity:0; transform: translateY(-50%) translateX(8px); } to { opacity:1; transform: translateY(-50%) translateX(0); } }
|
||||
|
||||
.fdc-header {
|
||||
.fdc-header,
|
||||
.feed-detail-card .panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.fdc-header strong { font-size: .85rem; color: var(--text); }
|
||||
.fdc-header strong,
|
||||
.feed-detail-card .panel-header strong { font-size: .85rem; color: var(--text); }
|
||||
.fdc-sender { color: var(--text-muted); font-size: .75rem; }
|
||||
.fdc-close {
|
||||
margin-left: auto;
|
||||
|
||||
+10
-1
@@ -2105,6 +2105,9 @@
|
||||
// Initialize DragManager for free-form panel dragging (#608 M1)
|
||||
if (window.DragManager) {
|
||||
var dragMgr = new DragManager();
|
||||
// #1619: expose so the feed-detail-card popup (constructed in a
|
||||
// different scope) can register itself as draggable.
|
||||
window._liveDragMgr = dragMgr;
|
||||
var dragPanels = ['liveFeed', 'liveLegend', 'liveNodeDetail'];
|
||||
for (var di = 0; di < dragPanels.length; di++) {
|
||||
dragMgr.register(document.getElementById(dragPanels[di]));
|
||||
@@ -4271,7 +4274,7 @@
|
||||
const card = document.createElement('div');
|
||||
card.className = 'feed-detail-card';
|
||||
card.innerHTML = `
|
||||
<div class="fdc-header" style="border-left:3px solid ${color}">
|
||||
<div class="panel-header" style="border-left:3px solid ${color}">
|
||||
<strong>${typeName}</strong>
|
||||
${sender ? `<span class="fdc-sender">${escapeHtml(sender)}</span>` : ''}
|
||||
<button class="fdc-close">✕</button>
|
||||
@@ -4301,6 +4304,12 @@
|
||||
});
|
||||
const feedEl = document.getElementById('liveFeed');
|
||||
if (feedEl) feedEl.parentElement.appendChild(card);
|
||||
// #1619: register the popup with the live DragManager so users can move
|
||||
// it out from behind the legend (responsive gate is handled inside the
|
||||
// manager via its `enabled` flag — no extra wiring required here).
|
||||
if (window._liveDragMgr) {
|
||||
try { window._liveDragMgr.register(card); } catch (_) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* #1619 — Live view: feed-detail-card popup (with ↻ Replay) is undraggable
|
||||
* and frequently sits behind the legend (z=1000), leaving the Replay button
|
||||
* unreachable.
|
||||
*
|
||||
* Source-invariant assertions on public/live.css and public/live.js:
|
||||
* A. .feed-detail-card z-index is bumped to 1050 (above legend z=1000,
|
||||
* below mobile bottom-nav z=1100).
|
||||
* B. The card markup created in live.js includes a `panel-header` div
|
||||
* (the drag handle expected by DragManager).
|
||||
* C. The bootstrap exposes the DragManager instance (window._liveDragMgr
|
||||
* or equivalent) so the popup-creation site can register the card.
|
||||
* D. The popup-creation site calls dragMgr.register(card) — wired through
|
||||
* the exposed instance.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passed = 0, failed = 0;
|
||||
function assert(cond, msg) {
|
||||
if (cond) { passed++; console.log(' ✓ ' + msg); }
|
||||
else { failed++; console.error(' ✗ ' + msg); }
|
||||
}
|
||||
|
||||
const liveCss = fs.readFileSync(path.join(__dirname, 'public', 'live.css'), 'utf8');
|
||||
const liveJs = fs.readFileSync(path.join(__dirname, 'public', 'live.js'), 'utf8');
|
||||
|
||||
console.log('\n=== #1619 A: .feed-detail-card z-index above legend ===');
|
||||
|
||||
// Capture the .feed-detail-card { ... } block (the FIRST/base rule, not a
|
||||
// nested @media override). Match the rule selector at start-of-line.
|
||||
const fdcRuleMatch = liveCss.match(/^\.feed-detail-card\s*\{([\s\S]*?)\}/m);
|
||||
assert(!!fdcRuleMatch, '.feed-detail-card base rule found in live.css');
|
||||
if (fdcRuleMatch) {
|
||||
const body = fdcRuleMatch[1];
|
||||
const zMatch = body.match(/z-index\s*:\s*(\d+)/);
|
||||
assert(!!zMatch, '.feed-detail-card declares a z-index');
|
||||
if (zMatch) {
|
||||
const z = parseInt(zMatch[1], 10);
|
||||
assert(z === 1050,
|
||||
'.feed-detail-card z-index === 1050 (above legend 1000, below bottom-nav 1100) — got ' + z);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== #1619 B: card markup includes .panel-header drag handle ===');
|
||||
|
||||
// Locate the feed-detail-card construction block and verify it contains a
|
||||
// panel-header div (DragManager.register requires panel.querySelector('.panel-header')).
|
||||
const cardBlockMatch = liveJs.match(
|
||||
/card\.className\s*=\s*['"]feed-detail-card['"][\s\S]{0,2000}?card\.innerHTML\s*=\s*`([\s\S]*?)`/
|
||||
);
|
||||
assert(!!cardBlockMatch, 'feed-detail-card construction site found in live.js');
|
||||
if (cardBlockMatch) {
|
||||
const html = cardBlockMatch[1];
|
||||
assert(/class\s*=\s*["']panel-header["']/.test(html) ||
|
||||
/class\s*=\s*["'][^"']*\bpanel-header\b[^"']*["']/.test(html),
|
||||
'feed-detail-card innerHTML contains a .panel-header element (drag handle)');
|
||||
}
|
||||
|
||||
console.log('\n=== #1619 C: DragManager instance exposed for popup-site use ===');
|
||||
|
||||
// The popup is created in a different scope than the bootstrap dragMgr.
|
||||
// Expose it on window (or equivalent global registrar) so the popup site
|
||||
// can call .register(card). Accept any of: window._liveDragMgr,
|
||||
// window.liveDragMgr, or a registrar function exposed on window.
|
||||
const exposeMatch = liveJs.match(
|
||||
/window\.(_liveDragMgr|liveDragMgr|liveRegisterDraggable)\s*=/
|
||||
);
|
||||
assert(!!exposeMatch,
|
||||
'DragManager instance / registrar exposed on window (e.g. window._liveDragMgr = dragMgr)');
|
||||
|
||||
console.log('\n=== #1619 D: popup-creation site registers the card with DragManager ===');
|
||||
|
||||
// Look for a call to register/registrar that takes `card` near the
|
||||
// feed-detail-card construction block.
|
||||
const popupTail = cardBlockMatch
|
||||
? liveJs.slice(liveJs.indexOf(cardBlockMatch[0]), liveJs.indexOf(cardBlockMatch[0]) + 4000)
|
||||
: '';
|
||||
const registerCall =
|
||||
/(_liveDragMgr|liveDragMgr)\s*(?:&&\s*\1\s*)?\.register\s*\(\s*card\s*\)/.test(popupTail) ||
|
||||
/liveRegisterDraggable\s*\(\s*card\s*\)/.test(popupTail);
|
||||
assert(registerCall,
|
||||
'popup-creation site calls <exposed-dragMgr>.register(card) (or registrar) after appending');
|
||||
|
||||
console.log('\n=== Summary ===');
|
||||
console.log(' Passed: ' + passed);
|
||||
console.log(' Failed: ' + failed);
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
Reference in New Issue
Block a user