From 2c26abc8d8d0dcb1e6d6409554affa6524147877 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 11:37:39 +0000 Subject: [PATCH] fix(ui): jeder Bedienpunkt 32 px, Menue-Gruppe rollt (#862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemessen im echten Browser (2026-09-13, Chromium gegen dist/renderer, 390/768/1440 px) mit scripts/bedienbar.mjs aus av-planner-suite. DER ANLASS. Der Waechter misst seit B-77 vier Planer. Dieser stand nicht in der Liste, weil sein dist/ das Electron-Layout traegt (renderer/index.html statt einer Wurzel-Datei). Das ist eine Eigenschaft des LAUFS und keine Auskunft ueber die App — der Ordner dist/renderer laesst sich ohne Weiteres bedienen. Als er einmal bedient wurde, kamen 62 Bedienpunkte unter 32 px heraus (33 bei 390 px): Menueknoepfe (File · Edit · Tools · View · Help) 26 px hoch Projektname in der Kopfzeile 22 px Undo / Redo 30 x 22 px Einstellungen 22 px Spalten-Griffe (‹ ›) 28 x 28 px Reiter der Seitenleiste 26 px Suchfeld der Bibliothek 28 px die 16 Kategoriezeilen der Bibliothek 18 px Kategorie-Chips (Video · Audio · Control · …) 24 px Schematic / Circuit 24 px die vier ReactFlow-Regler 26 x 27 px das Stil-Feld am Kabel 49 x 17 px Achtzehn Pixel ist eine Zeile, die man mit der Maus trifft und mit dem Finger nicht — und die Bibliothek ist die Flaeche, auf der in dieser App am meisten geklickt wird. WARUM KEIN WAECHTER DAS GESAGT HAT. `mobil:check` misst UEBERLAUF und war auf allen 62 Punkten gruen. Das ist kein Versaeumnis: er beantwortet eine andere Frage, und er beantwortet sie gut — nichts ragt in keiner Breite und keiner Sprache hinaus. `greifzonen:check` misst, welcher Pfad auf dem Canvas obenauf liegt. Auch das ist eine andere Frage. Drei Laeufe, drei Fragen. EINE GLOBALE REGEL statt 62 Klassen-Strings: die Hoehen entstehen aus Tailwind-Polsterung und nicht aus einer Hoehen-Angabe, und die 63. Stelle faellt sonst morgen wieder durch. `min-height` gewinnt gegen `height`, deshalb greift sie auch bei den ReactFlow-Reglern, deren Groesse aus dem Paket kommt. AUSGENOMMEN ist alles auf dem Canvas (`.react-flow__node`, `.react-flow__handle`). Eine Buchse am Geraet ist kein Knopf in einer Leiste: ihre Groesse kommt aus dem Raster des Geraets (B-48, `gridSize`), und sie auf 32 px zu heben verschoebe jeden Port und damit jedes Kabel. Der Canvas hat seine eigene Bedienung — Zoom — und seine eigene Messung. Ebenfalls ausgenommen: Kaestchen, Auswahlknoepfe, Schieberegler. WAS DIE REGEL MIT SICH BRACHTE, und was daraus folgte: 1. DIE KOPFZEILE PASSTE AUF DEUTSCH NICHT MEHR. Breitere kleine Knoepfe, und die Zeile hatte keinen Spielraum: `mobil:check` wurde rot (Projektname bis 410 px, rechte Gruppe bis 438 px) — auf Englisch nicht. Genau dafuer misst er jede ausgelieferte Sprache. Die Abhilfe aus B-66 (Polsterung verkleinern) ist ausgereizt. Die fuenf Menues stehen jetzt in einer eigenen Gruppe (`.cp-menubar`), und die rollt waagerecht. Nicht die ganze Zeile: rollte sie, rollte der Einstellungen-Knopf mit aus dem Bild — die Lage, die B-66 beschreibt. DASS SIE ROLLEN DARF, ist keine Selbstverstaendlichkeit: ein Rollbereich schneidet ab, was nicht hineinpasst, und die Klappen WAREN Kinder dieser Zeile. Sie haengen jetzt am Fenster. Im light-planner ist genau diese Kombination schiefgegangen — dort war jede Klappe 0 px hoch sichtbar. Rollen fuehrt die Klappe nach statt sie zu schliessen; im multicam-planner ging sie sonst auf und sofort wieder zu, weil das Rollereignis NACH dem Klick kommt. 2. DIE MENUETITEL BRACHEN UM. Mit 32 px Hoehe hatte ihr Inhalt Platz fuer eine zweite Zeile, und weil sie in einer schrumpfenden Reihe stehen, nahmen sie sie sich: aus 46 x 26 px wurden 36 x 37 px, das Zeichen stand unter dem Wort. `inline-flex items-center` bricht nicht um — dieselbe Bauart wie in den anderen vier Apps. Nachgemessen: in allen drei Breiten kein Bedienpunkt unter 32 px, nichts ragt hinaus, kein Feld ohne Namen. 4130 Tests gruen, alle fuenf Waechter gruen (lang, form, mobil, actions, greifzonen). --- src/renderer/components/Layout/MenuBar.tsx | 75 +++++++++++- src/renderer/index.css | 132 +++++++++++++++++++++ 2 files changed, 201 insertions(+), 6 deletions(-) diff --git a/src/renderer/components/Layout/MenuBar.tsx b/src/renderer/components/Layout/MenuBar.tsx index 778dcc72..cf947b93 100644 --- a/src/renderer/components/Layout/MenuBar.tsx +++ b/src/renderer/components/Layout/MenuBar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useSyncExternalStore, type ChangeEvent } from 'react' +import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore, type ChangeEvent } from 'react' import { BarChart3, Boxes, @@ -570,12 +570,19 @@ export const MenuBar = ({ -
+
{t('app.title', 'Cable Planner')} │ + {/* Die fuenf Menues in EINER Gruppe (B-77) — sie ist es, die auf + schmalen Fenstern rollt, und nicht die ganze Kopfzeile. Rollte die + Zeile, rollte der Einstellungen-Knopf rechts mit aus dem Bild, und + genau das verhindert der Kommentar weiter unten seit B-66. + Dieselbe Bauart wie `.bc-menubar` im `multicam-planner` und + `.tb-menubar` im `light-planner`. */} +
} shortcut={t('shortcut.ctrlN', 'Ctrl+N')}> {t('app.menu.file.new', 'New project')} @@ -1145,6 +1152,7 @@ export const MenuBar = ({ {t('app.menu.help.about', 'About Cable Planner…')} +
@@ -1250,6 +1258,10 @@ interface MenuProps { const Menu = ({ label, children }: MenuProps) => { const [open, setOpen] = useState(false) const ref = useRef(null) + const knopf = useRef(null) + const klappe = useRef(null) + /** Das Rechteck des angeklickten Titels — Ausgangspunkt der Klappe. */ + const [anker, setAnker] = useState<{ links: number; oben: number } | null>(null) useEffect(() => { if (!open) return @@ -1261,14 +1273,52 @@ const Menu = ({ label, children }: MenuProps) => { const onKey = (event: KeyboardEvent) => { if (event.key === 'Escape') setOpen(false) } + // ROLLEN FUEHRT NACH, GROESSE SCHLIESST (B-77). Die Menue-Gruppe rollt + // auf schmalen Fenstern waagerecht, also bewegt sich der Titel — und die + // Klappe haengt seit B-77 am Fenster und merkt das nicht von selbst. + // Schliessen beim Rollen waere falsch: liegt ein Titel ausserhalb der + // Leiste, rollt ein Klick sie erst dorthin, und das Rollereignis kommt + // NACH dem Klick. Im `multicam-planner` ging die Klappe damit auf und + // sofort wieder zu (gemessen bei 390 px). + const nachfuehren = () => { + const r = knopf.current?.getBoundingClientRect() + if (r) setAnker({ links: r.left, oben: r.bottom + 4 }) + } + const zu = () => setOpen(false) document.addEventListener('mousedown', onDocClick) document.addEventListener('keydown', onKey) + window.addEventListener('resize', zu) + window.addEventListener('scroll', nachfuehren, true) return () => { document.removeEventListener('mousedown', onDocClick) document.removeEventListener('keydown', onKey) + window.removeEventListener('resize', zu) + window.removeEventListener('scroll', nachfuehren, true) } }, [open]) + // B-77 — DIE KLAPPE HAENGT AM FENSTER, NICHT AN DER LEISTE. + // + // Sie war ein `absolute`-Kind der Kopfzeile. Solange nichts abschneidet, + // faellt das nicht auf; sobald ein Vorfahr rollt, ist die Klappe weg. Im + // `light-planner` ist genau das passiert: ein `overflow: hidden`, das die + // Kopfzeile einzeilig halten sollte, machte aus einer 250 x 505 px grossen + // Datei-Klappe eine mit 0 px sichtbarer Flaeche — vier Menues, kein + // einziges ging auf. + // + // Gemessen wird NACH dem Einhaengen: die Breite haengt am laengsten + // Eintrag und ist auf Deutsch eine andere als auf Englisch. + useLayoutEffect(() => { + const el = klappe.current + if (!el || !anker) return + const r = el.getBoundingClientRect() + const rand = 8 + const links = Math.max(rand, Math.min(anker.links, window.innerWidth - rand - r.width)) + const oben = Math.max(rand, Math.min(anker.oben, window.innerHeight - rand - r.height)) + el.style.left = `${links}px` + el.style.top = `${oben}px` + }, [anker, open]) + // #461 — Tastatur-Navigation im offenen Menue: beim Oeffnen ersten Eintrag // fokussieren, Pfeile/Home/End bewegen den Fokus zwischen den menuitems. useEffect(() => { @@ -1311,10 +1361,15 @@ const Menu = ({ label, children }: MenuProps) => { } return ( -
+