fix(ui): keep Galaxy scene labels below its overlay panels

drei hangs every Html label of the scene into the canvas wrapper and gives
it a z-index from its zIndexRange (up to 10, agent layer up to 80). The
wrapper formed no stacking context, so those numbers competed with the
Galaxy overlays: the path target's name was painted over Selection
details (z-index 4) and the fit view button (3).

The wrapper now isolates its stacking context. The labels keep their order
among themselves, stay above the canvas, and sit below every overlay with
a z-index of 1 or more, in the Galaxy and in the Explore mini-Galaxy.

Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
This commit is contained in:
Bernhard Jackiewicz committed 2026-10-04 13:19:17 +02:00
1 parent 1d4c4ad125
commit 81e470bc89
2 files changed
+57 -1

No files matched your search

@@ -0,0 +1,37 @@
// @vitest-environment jsdom
/*
* Handtest 2026-10-04 (G1): der Name des Pfadziels lag ueber dem Text von
* "Selection details". drei haengt jedes <Html> der Szene als Geschwister des
* Canvas in dessen Huelle und gibt ihm einen eigenen z-index (bis 10, die
* Agentenebene bis 80). Ohne eigenen Stapelkontext der Huelle stehen diese
* z-indizes im selben Stapel wie die Bedienflaechen der Galaxie, und "Selection
* details" (4) lag darunter. Hier steht, dass die Huelle ihren Stapel bildet:
* alles, was die Szene als DOM zeichnet, bleibt darin und damit unter jeder
* Bedienflaeche mit z-index ab 1, und ueber dem Canvas selbst.
*/
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, expect, it } from 'vitest';
import { GraphScene } from './GraphScene';
let host: HTMLDivElement, root: Root;
beforeEach(() => {
(globalThis as unknown as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
// jsdom misst nichts; mit der Groesse null setzt der Canvas keine WebGL-Wurzel auf, die Huelle steht trotzdem.
(globalThis as unknown as Record<string, unknown>).ResizeObserver ??= class { observe() {} unobserve() {} disconnect() {} };
host = document.createElement('div'); document.body.append(host); root = createRoot(host);
});
afterEach(() => { act(() => root.unmount()); host.remove(); });
it('G1: the canvas wrapper that hosts every scene label forms its own stacking context', () => {
const node = { id: 1, x: 0, y: 0, z: 0, name: 'n1', label: 'Function', size: 3, color: '#abcdef' };
act(() => root.render(<GraphScene active={false} data={{ nodes: [node], edges: [], total_nodes: 1 }} highlightedIds={null} cameraTarget={null}
showLabels onNodeClick={() => {}} />));
const canvas = host.querySelector('canvas');
expect(canvas).not.toBeNull();
// r3f: <div (events, Html-Ziel)><div (Messung)><canvas/></div></div>.
const wrapper = canvas!.parentElement!.parentElement!;
expect(wrapper.parentElement).toBe(host);
expect(wrapper.style.position).toBe('relative');
expect(wrapper.style.isolation).toBe('isolate');
});
+20 -1
View File
@@ -110,6 +110,8 @@
* 21. Neu (Review zu K5): die Prop `labelIds`. Mit ihr bekommen nur diese
* Knoten einen Namen (die Hierarchie eines grossen Ausschnitts benennt die
* Wurzel und ihre direkten Nachbarn). Ohne die Prop wie vorher.
* 22. Neu (Handtest 2026-10-04, G1): die Huelle des Canvas bildet ihren
* eigenen Stapelkontext (`isolation: isolate`). Siehe `CANVAS_STYLE`.
*/
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
@@ -145,6 +147,23 @@ import {
const BASE_BLOOM_INTENSITY = 1.45;
/*
* Aenderung 22 (Handtest 2026-10-04, G1): die Namen der Szene bleiben unter
* den Bedienflaechen.
*
* drei haengt jedes <Html> (Wurzelmarken, Pfad, Kantenschilder, Band, die
* Agentenebene, die Hover-Karte) in die Huelle des Canvas und rechnet ihm einen
* z-index aus seinem `zIndexRange`, bis 10 und in der Agentenebene bis 80.
* Ohne eigenen Stapelkontext der Huelle galten diese Zahlen im Stapel der
* ganzen Galaxie, und der Name des Pfadziels (10) lag ueber "Selection details"
* (4). Mit ihm ordnen sie nur noch die Namen untereinander; nach aussen steht
* die Huelle samt allen Namen auf der Ebene 0, also unter jeder Bedienflaeche
* mit z-index ab 1 (Pfadliste, Menues, Tooltips, Legende, Selection details)
* und ueber dem Canvas, der in ihr liegt. Dasselbe gilt im Mini-Galaxy von
* Explore, das dieselbe Szene ist.
*/
const CANVAS_STYLE = { background: '#0D0F12', isolation: 'isolate' } as const;
/* Camera fly-to animation */
interface CameraTarget {
@@ -1093,7 +1112,7 @@ export function GraphScene({
{...background}
frameloop={frameloop}
camera={{ position: [0, 0, 800], fov: GRAPH_CAMERA_FOV, near: 0.1, far: 100000 }}
style={{ background: '#0D0F12' }}
style={CANVAS_STYLE}
dpr={GRAPH_CANVAS_DPR}
gl={{
antialias: false,