top bar drag standardization: auto carve-out + the overlay DOM law (owner ruling)
The chains-overlay TopBar had dead drag zones (counts, around search, close gap) — cause: app-region subtraction resolves by DOM ORDER, not z-index; the graph chrome's lh-no-drag column came later in the DOM and punched holes through the overlay's drag band despite sitting visually underneath (z-40 vs z-10). Two laws, per the owner's standardization ruling: - AUTO CARVE-OUT (globals.css): inside any .lh-drag band, every native interactive element (button/input/textarea/select/a/summary/ [contenteditable]) is automatically a no-drag island — draggable anywhere that is not a clickable element becomes structural. Manual lh-no-drag is only for clickable non-natives. - THE OVERLAY DOM LAW: a surface containing a TopBar that covers other content must be the LAST child of its container — ChainTableOverlay moved after the chrome column (verified live: last child, after the column; auto-carve rule compiled; search/close/sidebar-toggle all still click).
This commit is contained in:
@@ -478,6 +478,9 @@ body {
|
|||||||
resolves by DOM order, not z-index: no-drag subtracts
|
resolves by DOM order, not z-index: no-drag subtracts
|
||||||
only regions added earlier in the tree)
|
only regions added earlier in the tree)
|
||||||
- .lh-no-drag carve-out for interactive elements inside a drag band
|
- .lh-no-drag carve-out for interactive elements inside a drag band
|
||||||
|
(native interactive elements carve out AUTOMATICALLY —
|
||||||
|
see the auto carve-out rule below; manual use is for
|
||||||
|
clickable non-natives only)
|
||||||
- .lh-lights-gutter the sidebar LH band's traffic-light gutter: lights
|
- .lh-lights-gutter the sidebar LH band's traffic-light gutter: lights
|
||||||
end at x≈68, the wordmark starts at 80px (5rem)
|
end at x≈68, the wordmark starts at 80px (5rem)
|
||||||
- .lh-closed-toggle same gutter for the sidebar toggle when the sidebar
|
- .lh-closed-toggle same gutter for the sidebar toggle when the sidebar
|
||||||
@@ -489,6 +492,17 @@ body {
|
|||||||
.lh-electron .lh-no-drag {
|
.lh-electron .lh-no-drag {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
/* THE AUTO CARVE-OUT (owner standardization ruling, 2026-09-29 night):
|
||||||
|
inside a drag band, every NATIVE interactive element is automatically
|
||||||
|
a no-drag island — "draggable anywhere that is not a clickable
|
||||||
|
element" becomes structural, not per-child discipline. TopBar
|
||||||
|
contents can differ arbitrarily; buttons/inputs/links always stay
|
||||||
|
clickable with zero opt-out bookkeeping. Manual .lh-no-drag remains
|
||||||
|
only for clickable NON-native elements (spans/divs with onClick —
|
||||||
|
e.g. the graph chrome's control column). */
|
||||||
|
.lh-electron .lh-drag :is(button, input, textarea, select, a, summary, [contenteditable]) {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
/* 92px = measured lights' right edge (~75pt) + the lights' own 16px
|
/* 92px = measured lights' right edge (~75pt) + the lights' own 16px
|
||||||
inset from the window edge — comfortable, symmetric air (owner). */
|
inset from the window edge — comfortable, symmetric air (owner). */
|
||||||
.lh-electron .lh-lights-gutter {
|
.lh-electron .lh-lights-gutter {
|
||||||
|
|||||||
+22
-14
@@ -1555,19 +1555,7 @@ export function GraphView() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* The chain table overlay (chains-overlay plan, 2026-09-29):
|
|
||||||
ABSOLUTE INSIDE the graph area (the session-overlay idiom —
|
|
||||||
never a body portal) so the app chrome stays sharp and
|
|
||||||
reachable; the overlay itself is read-only toward the graph
|
|
||||||
and owns its selection + the custom practice dialog. */}
|
|
||||||
<ChainTableOverlay
|
|
||||||
open={chainsOpen && !isEmpty}
|
|
||||||
nodes={snapshot?.nodes ?? []}
|
|
||||||
edges={snapshot?.edges ?? []}
|
|
||||||
sessions={snapshot?.sessions ?? {}}
|
|
||||||
fileSources={snapshot?.fileSources ?? {}}
|
|
||||||
onClose={() => setChainsOpen(false)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Legend + filters: search box and per-state toggle rows. */}
|
{/* Legend + filters: search box and per-state toggle rows. */}
|
||||||
{/* Top-right controls — separate floating containers (owner,
|
{/* Top-right controls — separate floating containers (owner,
|
||||||
@@ -1734,6 +1722,26 @@ export function GraphView() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
{/* The chain table overlay (chains-overlay plan, 2026-09-29):
|
||||||
|
ABSOLUTE INSIDE the graph area (the session-overlay idiom —
|
||||||
|
never a body portal) so the app chrome stays sharp and
|
||||||
|
reachable; the overlay itself is read-only toward the graph
|
||||||
|
and owns its selection + the custom practice dialog.
|
||||||
|
LAST CHILD OF THE ROOT, DELIBERATELY (owner, 2026-09-29
|
||||||
|
night): app-region subtraction resolves by DOM order, not
|
||||||
|
z-index — mounted earlier, the chrome's lh-no-drag column
|
||||||
|
punched dead zones through the overlay's TopBar drag band
|
||||||
|
(over the counts, around the search field). A full-bleed
|
||||||
|
surface covering a drag band must paint AFTER everything it
|
||||||
|
covers. (DeleteNodeDialog is body-portaled — DOM-irrelevant
|
||||||
|
here.) */}
|
||||||
|
<ChainTableOverlay
|
||||||
|
open={chainsOpen && !isEmpty}
|
||||||
|
nodes={snapshot?.nodes ?? []}
|
||||||
|
edges={snapshot?.edges ?? []}
|
||||||
|
sessions={snapshot?.sessions ?? {}}
|
||||||
|
fileSources={snapshot?.fileSources ?? {}}
|
||||||
|
onClose={() => setChainsOpen(false)}
|
||||||
|
/> </div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-5
@@ -11,17 +11,28 @@
|
|||||||
*
|
*
|
||||||
* Mechanics baked in (the chat-session bar's proven pattern):
|
* Mechanics baked in (the chat-session bar's proven pattern):
|
||||||
* - h-15 (60px) — matches the sidebar's wordmark band, one border line.
|
* - h-15 (60px) — matches the sidebar's wordmark band, one border line.
|
||||||
* - lh-drag — the window-drag band; interactive children carve out with
|
* - lh-drag — the window-drag band. NATIVE interactive children
|
||||||
* lh-no-drag (the mask law: carve-outs must come later in the DOM).
|
* (button/input/a/…) carve out AUTOMATICALLY via the globals.css auto
|
||||||
|
* carve-out rule (owner standardization ruling, 2026-09-29 night) —
|
||||||
|
* "draggable anywhere that is not a clickable element" is structural,
|
||||||
|
* not per-child discipline; manual lh-no-drag is only for clickable
|
||||||
|
* non-natives (spans/divs with onClick).
|
||||||
* - select-none — dragging never highlights the title.
|
* - select-none — dragging never highlights the title.
|
||||||
* - SidebarToggle FIRST — the sidebar reopen when closed, with its
|
* - SidebarToggle FIRST — the sidebar reopen when closed, with its
|
||||||
* Electron traffic-light gutter (lh-closed-toggle).
|
* Electron traffic-light gutter (lh-closed-toggle).
|
||||||
* - border-b border-border px-3, gap-3 rhythm.
|
* - border-b border-border px-3, gap-3 rhythm.
|
||||||
*
|
*
|
||||||
* The consumer brings the title (font-title text-3xl tracking-tight) and
|
* The consumer brings the title (font-title text-3xl tracking-tight) and
|
||||||
* any right-side controls (lh-no-drag on each). `drag={false}` is the one
|
* any right-side controls. `drag={false}` is the one escape hatch —
|
||||||
* escape hatch — chat's rename mode (app-region:drag would eat the
|
* chat's rename mode (app-region:drag would eat the mousedown that
|
||||||
* mousedown that exits the input; owner ask).
|
* exits the input; owner ask).
|
||||||
|
*
|
||||||
|
* THE OVERLAY TWIN OF THE MASK LAW (owner, 2026-09-29 night): app-region
|
||||||
|
* subtraction resolves by DOM order, not z-index — a surface containing
|
||||||
|
* a TopBar that COVERS other content (a full-bleed overlay) must be the
|
||||||
|
* LAST child of its container, or later no-drag elements punch dead
|
||||||
|
* zones through the bar (the chains-overlay bug: the graph chrome's
|
||||||
|
* no-drag column killed drag over the counts/search/close area).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user