cd /Users/marcus.hinz/projects/adesso-cpq
echo "=== buildCreateEvent Signatur + GroupCreateEvent-Struktur ==="
grep -nE 'buildCreateEvent|GroupCreateEvent|type: "group-create"|rootHandleId|groupId:' packages/frontend/src/configurator/model/GroupProcessor.ts | head
sed -n "$(grep -n 'buildCreateEvent' packages/frontend/src/configurator/model/GroupProcessor.ts | head -1 | cut -d: -f1),+15p" packages/frontend/src/configurator/model/GroupProcessor.ts

Read buildCreateEvent structure


=== buildCreateEvent Signatur + GroupCreateEvent-Struktur ===
52:export interface GroupCreateEvent {
53:    type: "group-create";
55:    groupId: string;
57:    rootHandleId: string; // the element root among `handles`
78:    groupId: string;
91:    newGroups: Array<{ members: string[]; edges: Edge[]; groupId: string; groupKey: string; transform: Transform }>;
100:    newGroups: Array<{ members: string[]; edges: Edge[]; groupId: string; groupKey: string; transform: Transform }>;
104:    | GroupCreateEvent
133:    buildCreateEvent(productScene: Scene, position: Vec3): GroupCreateEvent {
134:        const { handles, rootHandleId } = this.reidentify(productScene);
    buildCreateEvent(productScene: Scene, position: Vec3): GroupCreateEvent {
        const { handles, rootHandleId } = this.reidentify(productScene);
        return {
            type: "group-create",
            timestamp: Date.now(),
            groupId: this.nextId("g"),
            groupKey: this.nextId("gk"),
            rootHandleId,
            handles,
            position,
            rotation: IDENTITY_QUAT,
        };
    }

    private reidentify(productScene: Scene): { handles: Handle[]; rootHandleId: string } {
        const src = structuredClone(productScene);


Ich habe die Bausteine: `buildCreateEvent` trägt `groupId`/`rootHandleId` direkt (kein Zwischen-Replay nötig), `elementHandles` hält den Subtree, `occupiedPmis` die Platz-Prüfung. Ein Realismus-Hinweis vorab, dann baue ich:

Der **Move-Teil** (oben/unten → `group-move`) ist trivial. Der **Anbau-Teil** ist der eigentliche Hack: aktives Teil aus dem Store → dessen Subtree als Scene rekonstruieren → `buildCreateEvent` (Kopie) → `group-merge` an `CS_OUT_R_0`/`CS_OUT_L_0` (falls frei). Die Andock-PMIs hartcodiere ich (grob, Denkanregung).

Erst die GroupProcessor-Hilfe, um den Subtree eines platzierten Teils als frische Scene zu holen:


Edit

    // Docking class of a group, read from the `class-…` tag of its element root(s). Null if none of
    // the members carry a class tag. Used to look the group up in the client-supplied docking rules.
    classOfGroup(groupId: string): string | null {

    // Rebuild a standalone product scene from a placed element's subtree — for duplicating it. The
    // element root's parentKey is stripped so reidentify treats it as a fresh element root again.
    // (Prototype helper for the keyboard build shortcut.)
    sceneOfElement(elementRoot: string): { _resourceClass: string; handles: Handle[] } | null {
        const handles = this.elementHandles.get(elementRoot);
        if (!handles) return null;
        const clone = structuredClone(handles);
        for (const h of clone) {
            if (h.id !== elementRoot) continue;
            const tr = h.components?.find((c: any) => c._resourceClass === "component_transform");
            if (tr) delete tr.parentKey;
        }
        return { _resourceClass: "sceneGraph_entity", handles: clone };
    }

    // Docking class of a group, read from the `class-…` tag of its element root(s). Null if none of
    // the members carry a class tag. Used to look the group up in the client-supplied docking rules.
    classOfGroup(groupId: string): string | null {


The file /Users/marcus.hinz/projects/adesso-cpq/packages/frontend/src/configurator/model/GroupProcessor.ts has been updated successfully. (file state is current in your context — no need to Read it back)

    // Rebuild a standalone product scene from a placed element's subtree — for duplicating it. The
    // element root's parentKey is stripped so reidentify treats it as a fresh element root again.
    // (Prototype helper for the keyboard build shortcut.)
    sceneOfElement(elementRoot: string): { _resourceClass: string; handles: Handle[] } | null {
        const handles = this.elementHandles.get(elementRoot);
        if (!handles) return null;
        const clone = structuredClone(handles);
        for (const h of clone) {
            if (h.id !== elementRoot) continue;
            const tr = h.components?.find((c: any) => c._resourceClass === "component_transform");
            if (tr) delete tr.parentKey;
        }
        return { _resourceClass: "sceneGraph_entity", handles: clone };
    }

    // Docking class of a group, read from the `class-…` tag of its element root(s). Null if none of
    // the members carry a class tag. Used to look the group up in the client-supplied docking rules.
    classOfGroup(groupId: string): string | null {

return this.nodeIndex.get(handleId)?.role ?? null;

+    // Rebuild a standalone product scene from a placed element's subtree — for duplicating it. The

+    // element root's parentKey is stripped so reidentify treats it as a fresh element root again.

+    // (Prototype helper for the keyboard build shortcut.)

+    sceneOfElement(elementRoot: string): { _resourceClass: string; handles: Handle[] } | null {

+        const handles = this.elementHandles.get(elementRoot);

+        const clone = structuredClone(handles);

+            if (h.id !== elementRoot) continue;

+            const tr = h.components?.find((c: any) => c._resourceClass === "component_transform");

+            if (tr) delete tr.parentKey;

+        return { _resourceClass: "sceneGraph_entity", handles: clone };


1 - es gibt schon einen Listener oder? der muss einfach erweitert werden -> kann es nicht das letzte aktive Teil aus dem Store holen? --> Nur dann ausführen, wenn am aktiven Teil platz fü die Kation i…


Jetzt der Processor — Handler + Switch-Case. Ich lese den Switch-Kopf:


39	            this.pendingRegroup ? [...events, this.pendingRegroup] : events,
40	        );
41	    }
42
43	    handleEvent(event: any): UIReaction[] {
44	        switch (event?.eventTypeName) {
45	            case "interaction-mode":
46	                if (event.lifecycleState !== "update") return [];
47	                return this.handleInteractionMode(event);
48	            case "add-product":
49	                return this.handleAddProduct(event);
50	            case "detach-element":
51	                return this.handleDetachElement(event);
52	            case "escalate-selection":
53	                return this.handleEscalateSelection(event);
54	            case "prepare-action":
55	                return this.handlePrepareAction(event);
56	            case "undo":
57	                return this.handleUndo();
58	            case "redo":
59	                return this.handleRedo();
60	            case "esc":

            this.pendingRegroup ? [...events, this.pendingRegroup] : events,
        );
    }

    handleEvent(event: any): UIReaction[] {
        switch (event?.eventTypeName) {
            case "interaction-mode":
                if (event.lifecycleState !== "update") return [];
                return this.handleInteractionMode(event);
            case "add-product":
                return this.handleAddProduct(event);
            case "detach-element":
                return this.handleDetachElement(event);
            case "escalate-selection":
                return this.handleEscalateSelection(event);
            case "prepare-action":
                return this.handlePrepareAction(event);
            case "undo":
                return this.handleUndo();
            case "redo":
                return this.handleRedo();
            case "esc":


            case "add-product":
                return this.handleAddProduct(event);
            case "detach-element":

            case "add-product":
                return this.handleAddProduct(event);
            case "keyboard-build":
                return this.handleKeyboardBuild(event);
            case "detach-element":


The file /Users/marcus.hinz/projects/adesso-cpq/packages/frontend/src/configurator/events/DefaultCommandProcessor.ts has been updated successfully. (file state is current in your context — no need to Read it back)

            case "add-product":
                return this.handleAddProduct(event);
            case "detach-element":

            case "add-product":
                return this.handleAddProduct(event);
            case "keyboard-build":
                return this.handleKeyboardBuild(event);
            case "detach-element":

+                return this.handleKeyboardBuild(event);


Jetzt der Handler nach `handleAddProduct`:


        return [
            { type: "sendCommand", cmd: "stop_interaction_mode", args: [] },
            { type: "sendCommand", cmd: "setScene", args: [scene] },
        ];
    }
    // Default click selects the single element; re-clicking the same spot escalates up the role

        return [
            { type: "sendCommand", cmd: "stop_interaction_mode", args: [] },
            { type: "sendCommand", cmd: "setScene", args: [scene] },
        ];
    }

    // PROTOTYPE — keyboard build shortcut ("+" held + arrow), fed from Fire3D. Works off the last
    // active selection taken from the store. right/left dock a COPY of the active part onto its open
    // CS_OUT_R_0 / CS_OUT_L_0 end (only if that end is still free); up/down nudge the active group
    // along Y. Docking PMIs are hardcoded for now — this is a thinking prop, not the final wiring.
    private handleKeyboardBuild(event: any): UIReaction[] {
        this.replay();
        const active = this.selection[this.selection.length - 1]?.pickedId;
        if (!active) return [];
        const activeRoot = this.groups.elementRootOf(active);
        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;
        if (!activeRoot || !groupId) return [];

        if (event.direction === "up" || event.direction === "down") {
            const t = this.groups.transformOf(groupId);
            if (!t) return [];
            const step = event.direction === "up" ? 100 : -100;
            this.eventStore.push({
                type: "group-move",
                timestamp: Date.now(),
                groupId,
                position: [t.pos[0], t.pos[1] + step, t.pos[2]],
                rotation: t.rot,
            } as GroupEvent);
            return [{ type: "sendCommand", cmd: "setScene", args: [this.replay()] }];
        }

        // right / left: dock a copy of the active part onto its open end
        const anchorPmi = event.direction === "right" ? "CS_OUT_R_0" : "CS_OUT_L_0";
        const joinPmi = event.direction === "right" ? "CS_EIN_L" : "CS_EIN_R";
        if (this.groups.occupiedPmis().some((o) => o.element === activeRoot && o.pmi === anchorPmi)) {
            return []; // no room at the active part's chosen end
        }
        const scene = this.groups.sceneOfElement(activeRoot);
        if (!scene) return [];
        const createEvent = this.groups.buildCreateEvent(scene as never, [0, 0, 0]);
        this.eventStore.push(createEvent);
        this.eventStore.push({
            type: "group-merge",
            timestamp: Date.now(),
            targetGroupId: groupId,
            movingGroupId: createEvent.groupId,
            anchorRootId: activeRoot,
            joinRootId: createEvent.rootHandleId,
            anchorPmiId: anchorPmi,
            joinPmiId: joinPmi,
            rot: IDENTITY_QUAT,
        } as GroupEvent);
        return [{ type: "sendCommand", cmd: "setScene", args: [this.replay()] }];
    }
    // Default click selects the single element; re-clicking the same spot escalates up the role


        return [
            { type: "sendCommand", cmd: "stop_interaction_mode", args: [] },
            { type: "sendCommand", cmd: "setScene", args: [scene] },
        ];
    }
    // Default click selects the single element; re-clicking the same spot escalates up the role

        return [
            { type: "sendCommand", cmd: "stop_interaction_mode", args: [] },
            { type: "sendCommand", cmd: "setScene", args: [scene] },
        ];
    }

    // PROTOTYPE — keyboard build shortcut ("+" held + arrow), fed from Fire3D. Works off the last
    // active selection taken from the store. right/left dock a COPY of the active part onto its open
    // CS_OUT_R_0 / CS_OUT_L_0 end (only if that end is still free); up/down nudge the active group
    // along Y. Docking PMIs are hardcoded for now — this is a thinking prop, not the final wiring.
    private handleKeyboardBuild(event: any): UIReaction[] {
        this.replay();
        const active = this.selection[this.selection.length - 1]?.pickedId;
        if (!active) return [];
        const activeRoot = this.groups.elementRootOf(active);
        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;
        if (!activeRoot || !groupId) return [];

        if (event.direction === "up" || event.direction === "down") {
            const t = this.groups.transformOf(groupId);
            if (!t) return [];
            const step = event.direction === "up" ? 100 : -100;
            this.eventStore.push({
                type: "group-move",
                timestamp: Date.now(),
                groupId,
                position: [t.pos[0], t.pos[1] + step, t.pos[2]],
                rotation: t.rot,
            } as GroupEvent);
            return [{ type: "sendCommand", cmd: "setScene", args: [this.replay()] }];
        }

        // right / left: dock a copy of the active part onto its open end
        const anchorPmi = event.direction === "right" ? "CS_OUT_R_0" : "CS_OUT_L_0";
        const joinPmi = event.direction === "right" ? "CS_EIN_L" : "CS_EIN_R";
        if (this.groups.occupiedPmis().some((o) => o.element === activeRoot && o.pmi === anchorPmi)) {
            return []; // no room at the active part's chosen end
        }
        const scene = this.groups.sceneOfElement(activeRoot);
        if (!scene) return [];
        const createEvent = this.groups.buildCreateEvent(scene as never, [0, 0, 0]);
        this.eventStore.push(createEvent);
        this.eventStore.push({
            type: "group-merge",
            timestamp: Date.now(),
            targetGroupId: groupId,
            movingGroupId: createEvent.groupId,
            anchorRootId: activeRoot,
            joinRootId: createEvent.rootHandleId,
            anchorPmiId: anchorPmi,
            joinPmiId: joinPmi,
            rot: IDENTITY_QUAT,
        } as GroupEvent);
        return [{ type: "sendCommand", cmd: "setScene", args: [this.replay()] }];
    }
    // Default click selects the single element; re-clicking the same spot escalates up the role

+    // PROTOTYPE — keyboard build shortcut ("+" held + arrow), fed from Fire3D. Works off the last

+    // active selection taken from the store. right/left dock a COPY of the active part onto its open

+    // CS_OUT_R_0 / CS_OUT_L_0 end (only if that end is still free); up/down nudge the active group

+    // along Y. Docking PMIs are hardcoded for now — this is a thinking prop, not the final wiring.

+    private handleKeyboardBuild(event: any): UIReaction[] {

+        const active = this.selection[this.selection.length - 1]?.pickedId;

+        const activeRoot = this.groups.elementRootOf(active);

+        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;

+        if (!activeRoot || !groupId) return [];

+        if (event.direction === "up" || event.direction === "down") {

+            const t = this.groups.transformOf(groupId);

+            const step = event.direction === "up" ? 100 : -100;

+                position: [t.pos[0], t.pos[1] + step, t.pos[2]],

+            return [{ type: "sendCommand", cmd: "setScene", args: [this.replay()] }];

+        // right / left: dock a copy of the active part onto its open end

+        const anchorPmi = event.direction === "right" ? "CS_OUT_R_0" : "CS_OUT_L_0";

+        const joinPmi = event.direction === "right" ? "CS_EIN_L" : "CS_EIN_R";

+        if (this.groups.occupiedPmis().some((o) => o.element === activeRoot && o.pmi === anchorPmi)) {

+            return []; // no room at the active part's chosen end

+        const scene = this.groups.sceneOfElement(activeRoot);

+        const createEvent = this.groups.buildCreateEvent(scene as never, [0, 0, 0]);

+        this.eventStore.push(createEvent);

+            movingGroupId: createEvent.groupId,

+            joinRootId: createEvent.rootHandleId,

// hierarchy (element → group → …). An additive click (selectionAction "add") adds another pick to

// the set, or toggles it off if already in. fire is driven with the full set via set_selection.


Jetzt der Fire3D-Listener — `onKeydown` erweitern + `plusHeld`-Tracking:


function onKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
}

// PROTOTYPE: hold "+" and press an arrow to build/move off the active part.
let plusHeld = false;
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

function onKeydown(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = true;
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
    if (plusHeld && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }
}

function onKeyup(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = false;
}


The file /Users/marcus.hinz/projects/adesso-cpq/packages/frontend/src/components/visual/Fire3D.vue has been updated successfully. (file state is current in your context — no need to Read it back)

function onKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
}

// PROTOTYPE: hold "+" and press an arrow to build/move off the active part.
let plusHeld = false;
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

function onKeydown(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = true;
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
    if (plusHeld && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }
}

function onKeyup(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = false;
}

<template>

            <FireControls :class="['fire3d__controls', { visible: controlsVisible }]" />

        <FireContextMenu
            v-if="contextMenu?.entries?.length"
            :entries="contextMenu.entries"
            @select="onContextMenuSelect"
            @close="closeContextMenu"
        />

            <slot name="editor" />

            <slot name="bottom" />

        <ToastNotification ref="toast" />
        <AppDialog ref="dialog" />

</template>

+// PROTOTYPE: hold "+" and press an arrow to build/move off the active part.

+const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

+    if (e.key === "+") plusHeld = true;

+    if (plusHeld && ARROW_DIR[e.key]) {

+        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));

+    if (e.key === "+") plusHeld = false;

const root = ref<HTMLElement | null>(null);


    window.addEventListener("keydown", onKeydown);
    window.addEventListener("keyup", onKeyup);


    window.addEventListener("keydown", onKeydown);
    window.addEventListener("keyup", onKeyup);

+    window.addEventListener("keyup", onKeyup);

visual.value = await createVisual3D(root.value!, props.config);

visual.value.subscribe((event: any) => {

// TEMP Analyse: eingehende fire-Events (v.a. selection-click) sichtbar machen


    window.removeEventListener("keydown", onKeydown);
    window.removeEventListener("keyup", onKeyup);


    window.removeEventListener("keydown", onKeydown);
    window.removeEventListener("keyup", onKeyup);

+    window.removeEventListener("keyup", onKeyup);


Alle drei Teile gebaut. Build zum Prüfen:


Bash

cd /Users/marcus.hinz/projects/adesso-cpq
pnpm --filter @ad-cpq/common-frontend build 2>&1 | grep -iE 'error TS|built|success' | grep -v 'ProductBrowser.vue' | head -12
echo "EXIT=${PIPESTATUS[0]}"

Build to check keyboard prototype compiles


[vite:dts] Declaration files built in 1054ms.
✓ built in 2.47s
EXIT=


Fertig, Build grün. Der Prototyp steht — als Denkanregung, wie gewünscht:

**Fire3D** (bestehender `onKeydown` erweitert): `+` gedrückt halten (via `plusHeld`, `keyup` löst) + Pfeiltaste → dispatcht `{ eventTypeName: "keyboard-build", direction }` an den Processor.

**Processor** (`handleKeyboardBuild`): arbeitet mit dem **letzten aktiven Teil** aus der Selektion:
- **→ / ←**: dockt eine **Kopie** des aktiven Teils an dessen offenes Ende (`CS_OUT_R_0` / `CS_OUT_L_0`) — **nur wenn dieser Punkt frei ist** (`occupiedPmis`-Check), sonst passiert nichts.
- **↑ / ↓**: verschiebt die aktive Gruppe um ±100 entlang Y (`group-move`).

**GroupProcessor** (`sceneOfElement`): rekonstruiert den Subtree eines platzierten Teils als frische Scene (root-`parentKey` gestrippt), damit `buildCreateEvent` eine Kopie erzeugen kann.

**Bewusst grob (Prototyp):**
- Andock-PMIs (`CS_OUT_R_0`, `CS_EIN_L`) und der Y-Schritt sind hartcodiert.
- Anbau-Rotation ist Identity (keine Winkel-Docks).
- Nur kompiliert, **visuell noch nicht verifiziert** — im Storybook (Configurator, ein Teil wählen, dann `+`+Pfeil) prüfen, ob die Mechanik das Erwartete andeutet.

Uncommitted im Working Tree, HMR übernimmt beim Reload.


shit -- der wird vom graphen gefangen
können wir auf strg statt plau?


Ctrl ist ein echter Modifier — kein `+`-Tracking nötig, und `Ctrl+Pfeil` kollidiert nicht mit Ctrl+Z/Y. Ich stelle um und entferne das `plusHeld`/`keyup`-Zeug:


Edit

// PROTOTYPE: hold "+" and press an arrow to build/move off the active part.
let plusHeld = false;
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

function onKeydown(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = true;
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
    if (plusHeld && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }
}

function onKeyup(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = false;
}

// PROTOTYPE: hold Ctrl and press an arrow to build/move off the active part.
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

function onKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }
}


// PROTOTYPE: hold "+" and press an arrow to build/move off the active part.
let plusHeld = false;
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

function onKeydown(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = true;
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
    if (plusHeld && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }
}

function onKeyup(e: KeyboardEvent) {
    if (e.key === "+") plusHeld = false;
}

// PROTOTYPE: hold Ctrl and press an arrow to build/move off the active part.
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

function onKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === "z") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "undo" });
        processReactions(reactions);
    }
    if (e.ctrlKey && e.key === "y") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "redo" });
        processReactions(reactions);
    }
    if (e.key === "Escape") {
        e.preventDefault();
        const reactions = processor.handleEvent({ eventTypeName: "esc" });
        processReactions(reactions);
    }
    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }
}

-// PROTOTYPE: hold "+" and press an arrow to build/move off the active part.

+// PROTOTYPE: hold Ctrl and press an arrow to build/move off the active part.

-    if (e.key === "+") plusHeld = true;

-    if (plusHeld && ARROW_DIR[e.key]) {

+    if (e.ctrlKey && ARROW_DIR[e.key]) {

-    if (e.key === "+") plusHeld = false;


shit -- der wird vom graphen gefangen können wir auf strg statt plau?


Und die jetzt überflüssige `keyup`-Registrierung entfernen:


    window.addEventListener("keydown", onKeydown);
    window.addEventListener("keyup", onKeyup);


    window.addEventListener("keydown", onKeydown);
    window.addEventListener("keyup", onKeyup);

-    window.addEventListener("keyup", onKeyup);


    window.removeEventListener("keydown", onKeydown);
    window.removeEventListener("keyup", onKeyup);


    window.removeEventListener("keydown", onKeydown);
    window.removeEventListener("keyup", onKeyup);

-    window.removeEventListener("keyup", onKeyup);


Bash

cd /Users/marcus.hinz/projects/adesso-cpq
pnpm --filter @ad-cpq/common-frontend build 2>&1 | grep -iE 'error TS|built|success' | grep -v 'ProductBrowser.vue' | head -5

Build check after Ctrl switch


[vite:dts] Declaration files built in 1140ms.
✓ built in 2.54s


Umgestellt auf **Ctrl + Pfeil**, Build grün. Das `+`-Tracking (`plusHeld`/`keyup`) ist raus — `Ctrl` ist ein echter Modifier (`e.ctrlKey`), wird nicht vom Viewport abgefangen und kollidiert nicht mit Ctrl+Z/Y.

- **Ctrl + → / ←**: Kopie an das offene Ende andocken (wenn frei).
- **Ctrl + ↑ / ↓**: aktive Gruppe entlang Y verschieben.

HMR übernimmt beim Reload — im Storybook ein Teil selektieren, dann Ctrl+Pfeil.


passiert gar nichts


Ich kann's nicht im Browser sehen — lass uns loggen, wo es hakt. Wahrscheinliche Kandidaten: der Handler wird nicht getriggert, oder die Selektion ist leer. Ich baue Debug-Logs ein:


Edit

    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        console.log("⌨ keyboard-build dispatch:", ARROW_DIR[e.key]);
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }


    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        console.log("⌨ keyboard-build dispatch:", ARROW_DIR[e.key]);
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

+        console.log("⌨ keyboard-build dispatch:", ARROW_DIR[e.key]);


    private handleKeyboardBuild(event: any): UIReaction[] {
        this.replay();
        const active = this.selection[this.selection.length - 1]?.pickedId;
        if (!active) return [];
        const activeRoot = this.groups.elementRootOf(active);
        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;
        if (!activeRoot || !groupId) return [];

    private handleKeyboardBuild(event: any): UIReaction[] {
        this.replay();
        const active = this.selection[this.selection.length - 1]?.pickedId;
        const activeRoot = active ? this.groups.elementRootOf(active) : null;
        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;
        console.log("🔨 keyboard-build", event.direction, {
            selectionLen: this.selection.length,
            active,
            activeRoot,
            groupId,
        });
        if (!active) return [];
        if (!activeRoot || !groupId) return [];


    private handleKeyboardBuild(event: any): UIReaction[] {
        this.replay();
        const active = this.selection[this.selection.length - 1]?.pickedId;
        if (!active) return [];
        const activeRoot = this.groups.elementRootOf(active);
        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;
        if (!activeRoot || !groupId) return [];

    private handleKeyboardBuild(event: any): UIReaction[] {
        this.replay();
        const active = this.selection[this.selection.length - 1]?.pickedId;
        const activeRoot = active ? this.groups.elementRootOf(active) : null;
        const groupId = activeRoot ? this.groups.groupIdOfElement(activeRoot) : null;
        console.log("🔨 keyboard-build", event.direction, {
            selectionLen: this.selection.length,
            active,
            activeRoot,
            groupId,
        });
        if (!active) return [];
        if (!activeRoot || !groupId) return [];

-        const activeRoot = this.groups.elementRootOf(active);

+        const activeRoot = active ? this.groups.elementRootOf(active) : null;

+        console.log("🔨 keyboard-build", event.direction, {

+            selectionLen: this.selection.length,

if (event.direction === "up" || event.direction === "down") {


Logs drin, HMR übernimmt beim Reload. Bitte im Storybook:
1. Ein Teil **anklicken** (selektieren),
2. dann **Ctrl + Pfeil** drücken,
3. die Konsolen-Ausgabe posten.

Was die Logs verraten:
- **kein `⌨ keyboard-build dispatch`** → der Listener/`Ctrl+Pfeil` greift gar nicht (Fokus/Viewport fängt es doch).
- **`⌨` da, aber `🔨` mit `selectionLen: 0`** → nichts selektiert (Selektion landet nicht im Processor).
- **`🔨` mit `active` gesetzt, aber `activeRoot: null`** → der angeklickte Punkt lässt sich nicht auf ein Element-Root auflösen.

Je nachdem, welcher Fall es ist, weiß ich, wo ich ansetzen muss.


da kommt kein log


Kein Log heißt: `onKeydown` erreicht den Ctrl+Pfeil-Zweig nicht. Ich logge **jeden** Tastendruck ganz am Anfang, um zu sehen, ob der Listener überhaupt feuert und was ankommt:


function onKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === "z") {

function onKeydown(e: KeyboardEvent) {
    console.log("⌨ keydown:", JSON.stringify({ key: e.key, ctrl: e.ctrlKey, meta: e.metaKey }));
    if (e.ctrlKey && e.key === "z") {


function onKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === "z") {

function onKeydown(e: KeyboardEvent) {
    console.log("⌨ keydown:", JSON.stringify({ key: e.key, ctrl: e.ctrlKey, meta: e.metaKey }));
    if (e.ctrlKey && e.key === "z") {

const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

+    console.log("⌨ keydown:", JSON.stringify({ key: e.key, ctrl: e.ctrlKey, meta: e.metaKey }));

const reactions = processor.handleEvent({ eventTypeName: "undo" });


HMR übernimmt (falls nicht: **hart neu laden**, F5 — evtl. hat HMR den alten Stand behalten, dann käme gar kein Log).

Test: in den 3D-Bereich klicken, dann **irgendeine Taste** drücken. Erwartung: `⌨ keydown: {…}` bei jedem Tastendruck.

- **Gar kein Log bei irgendeiner Taste** → der window-Listener feuert nicht (Fokus liegt im Storybook-UI, nicht im Story-iframe — dann in den Viewport klicken) oder der alte Code läuft noch (hart neu laden).
- **Log bei normalen Tasten, aber nicht bei Ctrl+Pfeil** → Ctrl+Pfeil wird vorher abgefangen.
- **Log bei Ctrl+Pfeil, aber `ctrl:false` oder `key` anders als `ArrowRight`** → dann sehen wir genau, was ankommt.

Poste, was beim Tippen (und dann bei Ctrl+Pfeil) erscheint.


visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/images/empty_warehouse_2K.jpg', originalPath: 'empty_warehouse_2K.jpg', typeKey: undefined, configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/images/<%=path%>', responseType: 'blob'}
@ad_fire.js?v=339f371e:37819 Nw - onInstantiate: worker processor was instantiated 1783533450587
@ad_fire.js?v=339f371e:37819 sx - onInstantiate: worker processor was instantiated 1783533450590
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/images/empty_warehouse_2K.jpg', status: 200, contentType: 'image/jpeg', contentLength: '743896'}
visual-3d.ts:204 [Fire3D Asset Loader] Loaded BLOB asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/images/empty_warehouse_2K.jpg', size: 743896, type: 'image/jpeg'}
@ad_fire.js?v=339f371e:37819 Cl - onExecute: deserializing 'component_camera-controller' as 'w'. 1783533450916
@ad_fire.js?v=339f371e:37819 gd - applyMappings: no mapping defined for key 'colors'. 1783533450917
visual-3d.ts:274 [DEPRECATED] _d - getMaterial: Version >= 0.9.0 1783533450918
processMessage @ @ad_fire.js?v=339f371e:37819
warn @ @ad_fire.js?v=339f371e:37835
i3 @ @ad_fire.js?v=339f371e:38196
e3.<computed> @ @ad_fire.js?v=339f371e:38178
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27352
execute @ @ad_fire.js?v=339f371e:23902
deserializeComponent @ @ad_fire.js?v=339f371e:27400
parseComponents @ @ad_fire.js?v=339f371e:27392
await in parseComponents
(anonymous) @ @ad_fire.js?v=339f371e:27370
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27409
execute @ @ad_fire.js?v=339f371e:23902
(anonymous) @ @ad_fire.js?v=339f371e:27423
await in (anonymous)
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27446
await in onExecute
execute @ @ad_fire.js?v=339f371e:23902
createEntityFromSettings @ @ad_fire.js?v=339f371e:26813
(anonymous) @ @ad_fire.js?v=339f371e:26737
await in (anonymous)
executeAsync @ @ad_fire.js?v=339f371e:24100
create @ @ad_fire.js?v=339f371e:26734
createEntity @ @ad_fire.js?v=339f371e:24277
assureSceneGraph @ @ad_fire.js?v=339f371e:24324
(anonymous) @ @ad_fire.js?v=339f371e:24315
executeAsync @ @ad_fire.js?v=339f371e:24100
instantiateScene @ @ad_fire.js?v=339f371e:24314
(anonymous) @ @ad_fire.js?v=339f371e:39811
(anonymous) @ @ad_fire.js?v=339f371e:17367
initializePipeline @ @ad_fire.js?v=339f371e:17367
await in initializePipeline
start @ @ad_fire.js?v=339f371e:17357
a2 @ @ad_fire.js?v=339f371e:39812
(anonymous) @ visual-3d.ts:274
await in (anonymous)
(anonymous) @ Fire3D.vue:192
(anonymous) @ chunk-LLX7KAUS.js?v=339f371e:5547
callWithErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2620
callWithAsyncErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2627
hook.__weh.hook.__weh @ chunk-LLX7KAUS.js?v=339f371e:5527
flushPostFlushCbs @ chunk-LLX7KAUS.js?v=339f371e:2805
flushJobs @ chunk-LLX7KAUS.js?v=339f371e:2847
Promise.then
queueFlush @ chunk-LLX7KAUS.js?v=339f371e:2742
queueJob @ chunk-LLX7KAUS.js?v=339f371e:2737
effect2.scheduler @ chunk-LLX7KAUS.js?v=339f371e:8713
trigger @ chunk-LLX7KAUS.js?v=339f371e:803
endBatch @ chunk-LLX7KAUS.js?v=339f371e:861
notify @ chunk-LLX7KAUS.js?v=339f371e:1123
trigger @ chunk-LLX7KAUS.js?v=339f371e:1097
set value @ chunk-LLX7KAUS.js?v=339f371e:2011
(anonymous) @ ProductConfigurator.vue:38
await in (anonymous)
(anonymous) @ chunk-LLX7KAUS.js?v=339f371e:5547
callWithErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2620
callWithAsyncErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2627
hook.__weh.hook.__weh @ chunk-LLX7KAUS.js?v=339f371e:5527
flushPostFlushCbs @ chunk-LLX7KAUS.js?v=339f371e:2805
render2 @ chunk-LLX7KAUS.js?v=339f371e:9286
mount @ chunk-LLX7KAUS.js?v=339f371e:6689
app.mount @ chunk-LLX7KAUS.js?v=339f371e:12999
renderToCanvas @ chunk-E7UYYORE.js?v=339f371e:52
await in renderToCanvas
renderToCanvas @ storybook_internal_preview_runtime.js?v=339f371e:8388
(anonymous) @ chunk-E7UYYORE.js?v=339f371e:91
(anonymous) @ storybook_internal_preview_runtime.js?v=339f371e:8401
runPhase @ storybook_internal_preview_runtime.js?v=339f371e:8320
mount @ storybook_internal_preview_runtime.js?v=339f371e:8400
render @ storybook_internal_preview_runtime.js?v=339f371e:8427
await in render
renderToElement @ storybook_internal_preview_runtime.js?v=339f371e:8344
renderSelection @ storybook_internal_preview_runtime.js?v=339f371e:9259
await in renderSelection
selectSpecifiedStory @ storybook_internal_preview_runtime.js?v=339f371e:9125
initializeWithStoryIndex @ storybook_internal_preview_runtime.js?v=339f371e:9097
await in initializeWithStoryIndex
initializeWithProjectAnnotations @ storybook_internal_preview_runtime.js?v=339f371e:8581
await in initializeWithProjectAnnotations
initialize @ storybook_internal_preview_runtime.js?v=339f371e:8555
await in initialize
PreviewWithSelection @ storybook_internal_preview_runtime.js?v=339f371e:9081
PreviewWeb @ storybook_internal_preview_runtime.js?v=339f371e:9528
(anonymous) @ vite-app.js:29
visual-3d.ts:274 [DEPRECATED] _d - getMaterial: Version >= 0.9.0 1783533450919
processMessage @ @ad_fire.js?v=339f371e:37819
warn @ @ad_fire.js?v=339f371e:37835
i3 @ @ad_fire.js?v=339f371e:38196
e3.<computed> @ @ad_fire.js?v=339f371e:38178
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27352
execute @ @ad_fire.js?v=339f371e:23902
deserializeComponent @ @ad_fire.js?v=339f371e:27400
parseComponents @ @ad_fire.js?v=339f371e:27392
await in parseComponents
(anonymous) @ @ad_fire.js?v=339f371e:27370
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27409
execute @ @ad_fire.js?v=339f371e:23902
(anonymous) @ @ad_fire.js?v=339f371e:27423
await in (anonymous)
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27446
await in onExecute
execute @ @ad_fire.js?v=339f371e:23902
createEntityFromSettings @ @ad_fire.js?v=339f371e:26813
(anonymous) @ @ad_fire.js?v=339f371e:26737
await in (anonymous)
executeAsync @ @ad_fire.js?v=339f371e:24100
create @ @ad_fire.js?v=339f371e:26734
createEntity @ @ad_fire.js?v=339f371e:24277
assureSceneGraph @ @ad_fire.js?v=339f371e:24324
(anonymous) @ @ad_fire.js?v=339f371e:24315
executeAsync @ @ad_fire.js?v=339f371e:24100
instantiateScene @ @ad_fire.js?v=339f371e:24314
(anonymous) @ @ad_fire.js?v=339f371e:39811
(anonymous) @ @ad_fire.js?v=339f371e:17367
initializePipeline @ @ad_fire.js?v=339f371e:17367
await in initializePipeline
start @ @ad_fire.js?v=339f371e:17357
a2 @ @ad_fire.js?v=339f371e:39812
(anonymous) @ visual-3d.ts:274
await in (anonymous)
(anonymous) @ Fire3D.vue:192
(anonymous) @ chunk-LLX7KAUS.js?v=339f371e:5547
callWithErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2620
callWithAsyncErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2627
hook.__weh.hook.__weh @ chunk-LLX7KAUS.js?v=339f371e:5527
flushPostFlushCbs @ chunk-LLX7KAUS.js?v=339f371e:2805
flushJobs @ chunk-LLX7KAUS.js?v=339f371e:2847
Promise.then
queueFlush @ chunk-LLX7KAUS.js?v=339f371e:2742
queueJob @ chunk-LLX7KAUS.js?v=339f371e:2737
effect2.scheduler @ chunk-LLX7KAUS.js?v=339f371e:8713
trigger @ chunk-LLX7KAUS.js?v=339f371e:803
endBatch @ chunk-LLX7KAUS.js?v=339f371e:861
notify @ chunk-LLX7KAUS.js?v=339f371e:1123
trigger @ chunk-LLX7KAUS.js?v=339f371e:1097
set value @ chunk-LLX7KAUS.js?v=339f371e:2011
(anonymous) @ ProductConfigurator.vue:38
await in (anonymous)
(anonymous) @ chunk-LLX7KAUS.js?v=339f371e:5547
callWithErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2620
callWithAsyncErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2627
hook.__weh.hook.__weh @ chunk-LLX7KAUS.js?v=339f371e:5527
flushPostFlushCbs @ chunk-LLX7KAUS.js?v=339f371e:2805
render2 @ chunk-LLX7KAUS.js?v=339f371e:9286
mount @ chunk-LLX7KAUS.js?v=339f371e:6689
app.mount @ chunk-LLX7KAUS.js?v=339f371e:12999
renderToCanvas @ chunk-E7UYYORE.js?v=339f371e:52
await in renderToCanvas
renderToCanvas @ storybook_internal_preview_runtime.js?v=339f371e:8388
(anonymous) @ chunk-E7UYYORE.js?v=339f371e:91
(anonymous) @ storybook_internal_preview_runtime.js?v=339f371e:8401
runPhase @ storybook_internal_preview_runtime.js?v=339f371e:8320
mount @ storybook_internal_preview_runtime.js?v=339f371e:8400
render @ storybook_internal_preview_runtime.js?v=339f371e:8427
await in render
renderToElement @ storybook_internal_preview_runtime.js?v=339f371e:8344
renderSelection @ storybook_internal_preview_runtime.js?v=339f371e:9259
await in renderSelection
selectSpecifiedStory @ storybook_internal_preview_runtime.js?v=339f371e:9125
initializeWithStoryIndex @ storybook_internal_preview_runtime.js?v=339f371e:9097
await in initializeWithStoryIndex
initializeWithProjectAnnotations @ storybook_internal_preview_runtime.js?v=339f371e:8581
await in initializeWithProjectAnnotations
initialize @ storybook_internal_preview_runtime.js?v=339f371e:8555
await in initialize
PreviewWithSelection @ storybook_internal_preview_runtime.js?v=339f371e:9081
PreviewWeb @ storybook_internal_preview_runtime.js?v=339f371e:9528
(anonymous) @ vite-app.js:29
visual-3d.ts:274 [DEPRECATED] _d - getMaterial: Version >= 0.9.0 1783533450919
processMessage @ @ad_fire.js?v=339f371e:37819
warn @ @ad_fire.js?v=339f371e:37835
i3 @ @ad_fire.js?v=339f371e:38196
e3.<computed> @ @ad_fire.js?v=339f371e:38178
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27352
execute @ @ad_fire.js?v=339f371e:23902
deserializeComponent @ @ad_fire.js?v=339f371e:27400
parseComponents @ @ad_fire.js?v=339f371e:27392
await in parseComponents
(anonymous) @ @ad_fire.js?v=339f371e:27370
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27409
execute @ @ad_fire.js?v=339f371e:23902
(anonymous) @ @ad_fire.js?v=339f371e:27423
await in (anonymous)
executeMapping @ @ad_fire.js?v=339f371e:23743
(anonymous) @ @ad_fire.js?v=339f371e:23737
applyMappings @ @ad_fire.js?v=339f371e:23735
onExecute @ @ad_fire.js?v=339f371e:27446
await in onExecute
execute @ @ad_fire.js?v=339f371e:23902
createEntityFromSettings @ @ad_fire.js?v=339f371e:26813
(anonymous) @ @ad_fire.js?v=339f371e:26737
await in (anonymous)
executeAsync @ @ad_fire.js?v=339f371e:24100
create @ @ad_fire.js?v=339f371e:26734
createEntity @ @ad_fire.js?v=339f371e:24277
assureSceneGraph @ @ad_fire.js?v=339f371e:24324
(anonymous) @ @ad_fire.js?v=339f371e:24315
executeAsync @ @ad_fire.js?v=339f371e:24100
instantiateScene @ @ad_fire.js?v=339f371e:24314
(anonymous) @ @ad_fire.js?v=339f371e:39811
(anonymous) @ @ad_fire.js?v=339f371e:17367
initializePipeline @ @ad_fire.js?v=339f371e:17367
await in initializePipeline
start @ @ad_fire.js?v=339f371e:17357
a2 @ @ad_fire.js?v=339f371e:39812
(anonymous) @ visual-3d.ts:274
await in (anonymous)
(anonymous) @ Fire3D.vue:192
(anonymous) @ chunk-LLX7KAUS.js?v=339f371e:5547
callWithErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2620
callWithAsyncErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2627
hook.__weh.hook.__weh @ chunk-LLX7KAUS.js?v=339f371e:5527
flushPostFlushCbs @ chunk-LLX7KAUS.js?v=339f371e:2805
flushJobs @ chunk-LLX7KAUS.js?v=339f371e:2847
Promise.then
queueFlush @ chunk-LLX7KAUS.js?v=339f371e:2742
queueJob @ chunk-LLX7KAUS.js?v=339f371e:2737
effect2.scheduler @ chunk-LLX7KAUS.js?v=339f371e:8713
trigger @ chunk-LLX7KAUS.js?v=339f371e:803
endBatch @ chunk-LLX7KAUS.js?v=339f371e:861
notify @ chunk-LLX7KAUS.js?v=339f371e:1123
trigger @ chunk-LLX7KAUS.js?v=339f371e:1097
set value @ chunk-LLX7KAUS.js?v=339f371e:2011
(anonymous) @ ProductConfigurator.vue:38
await in (anonymous)
(anonymous) @ chunk-LLX7KAUS.js?v=339f371e:5547
callWithErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2620
callWithAsyncErrorHandling @ chunk-LLX7KAUS.js?v=339f371e:2627
hook.__weh.hook.__weh @ chunk-LLX7KAUS.js?v=339f371e:5527
flushPostFlushCbs @ chunk-LLX7KAUS.js?v=339f371e:2805
render2 @ chunk-LLX7KAUS.js?v=339f371e:9286
mount @ chunk-LLX7KAUS.js?v=339f371e:6689
app.mount @ chunk-LLX7KAUS.js?v=339f371e:12999
renderToCanvas @ chunk-E7UYYORE.js?v=339f371e:52
await in renderToCanvas
renderToCanvas @ storybook_internal_preview_runtime.js?v=339f371e:8388
(anonymous) @ chunk-E7UYYORE.js?v=339f371e:91
(anonymous) @ storybook_internal_preview_runtime.js?v=339f371e:8401
runPhase @ storybook_internal_preview_runtime.js?v=339f371e:8320
mount @ storybook_internal_preview_runtime.js?v=339f371e:8400
render @ storybook_internal_preview_runtime.js?v=339f371e:8427
await in render
renderToElement @ storybook_internal_preview_runtime.js?v=339f371e:8344
renderSelection @ storybook_internal_preview_runtime.js?v=339f371e:9259
await in renderSelection
selectSpecifiedStory @ storybook_internal_preview_runtime.js?v=339f371e:9125
initializeWithStoryIndex @ storybook_internal_preview_runtime.js?v=339f371e:9097
await in initializeWithStoryIndex
initializeWithProjectAnnotations @ storybook_internal_preview_runtime.js?v=339f371e:8581
await in initializeWithProjectAnnotations
initialize @ storybook_internal_preview_runtime.js?v=339f371e:8555
await in initialize
PreviewWithSelection @ storybook_internal_preview_runtime.js?v=339f371e:9081
PreviewWeb @ storybook_internal_preview_runtime.js?v=339f371e:9528
(anonymous) @ vite-app.js:29
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/sobelMultichannel.json', originalPath: 'sobelMultichannel.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/depth.json', originalPath: 'depth.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/normal.json', originalPath: 'normal.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/copy.json', originalPath: 'copy.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/sao.json', originalPath: 'sao.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/depthLimitedBlur.json', originalPath: 'depthLimitedBlur.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/unpackDepthRGBA.json', originalPath: 'unpackDepthRGBA.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/fxaa.json', originalPath: 'fxaa.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/normal.json', status: 200, contentType: 'application/json', contentLength: '505'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/copy.json', status: 200, contentType: 'application/json', contentLength: '627'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/sobelMultichannel.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/normal.json', keys: Array(3)}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/copy.json', keys: Array(4)}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/depth.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/sao.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/sobelMultichannel.json', keys: Array(5)}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/depthLimitedBlur.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/unpackDepthRGBA.json', status: 200, contentType: 'application/json', contentLength: '699'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/fxaa.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/depth.json', keys: Array(5)}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/sao.json', keys: Array(5)}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/depthLimitedBlur.json', keys: Array(5)}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/unpackDepthRGBA.json', keys: Array(4)}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/fxaa.json', keys: Array(5)}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: './fire/shader/blend_additiveAlpha.json', originalPath: 'blend_additiveAlpha.json', typeKey: 'shader', configPathTemplate: './fire/<%=typeKey%>/<%=path%>', responseType: 'json'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: './fire/shader/blend_additiveAlpha.json', status: 200, contentType: 'application/json', contentLength: '899'}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: './fire/shader/blend_additiveAlpha.json', keys: Array(4)}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/pil_eloxiert_natur.json', originalPath: 'pil_eloxiert_natur.json', typeKey: 'material', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=typeKey%>s/<%=path%>', responseType: 'json'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/pil_eloxiert_natur.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/pil_eloxiert_natur.json', keys: Array(5)}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/kunststoff_schwarz.json', originalPath: 'kunststoff_schwarz.json', typeKey: 'material', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=typeKey%>s/<%=path%>', responseType: 'json'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/kunststoff_schwarz.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/kunststoff_schwarz.json', keys: Array(5)}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/kunststoff_grau.json', originalPath: 'kunststoff_grau.json', typeKey: 'material', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=typeKey%>s/<%=path%>', responseType: 'json'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/kunststoff_grau.json', status: 200, contentType: 'application/json', contentLength: null}
visual-3d.ts:196 [Fire3D Asset Loader] Parsed JSON asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/materials/kunststoff_grau.json', keys: Array(5)}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_27133175.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_27133175.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_38239278.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_38239278.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/CAP4080.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/CAP4080.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/CAP4040.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/CAP4040.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/BAP2051.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/BAP2051.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_83160222.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_83160222.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_16181215.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_16181215.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4501.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/DOR4501.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4553.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/DOR4553.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4555.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/DOR4555.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4511.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/DOR4511.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/TIN4511.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044SRA/TIN4511.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_59182232.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_59182232.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PRA/CAN4501.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PRA/CAN4501.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_68051095.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_68051095.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_13941978.glb', originalPath: 'katalog/schutzzaun_basic/SBS1044PLA/clone_13941978.glb', typeKey: 'sceneGraph', configPathTemplate: 'https://rob-geko.hifassets.com/fire/assets/<%=path%>', responseType: 'arraybuffer'}
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460648
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460650
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460651
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460652
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460652
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460653
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460654
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460655
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460655
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460656
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460657
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460657
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460658
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460659
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460659
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460660
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460661
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460662
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460663
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460663
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460664
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460665
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460665
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460666
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460667
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460667
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460668
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460668
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460669
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460670
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460671
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460672
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460672
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460673
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460674
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460674
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460675
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460676
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460676
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460677
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460677
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460678
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460679
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460680
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460681
@ad_fire.js?v=339f371e:37846 console.group
@ad_fire.js?v=339f371e:37819 FeatureComponent(39) - deregister 1783533460682
@ad_fire.js?v=339f371e:37846 console.group
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_38239278.glb', status: 200, contentType: 'application/octet-stream', contentLength: '14988'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_27133175.glb', status: 200, contentType: 'application/octet-stream', contentLength: '23304'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/CAP4080.glb', status: 200, contentType: 'application/octet-stream', contentLength: '11960'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/BAP2051.glb', status: 200, contentType: 'application/octet-stream', contentLength: '155440'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4501.glb', status: 200, contentType: 'application/octet-stream', contentLength: '262404'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4553.glb', status: 200, contentType: 'application/octet-stream', contentLength: '723288'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4511.glb', status: 200, contentType: 'application/octet-stream', contentLength: '395752'}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_38239278.glb', byteLength: 14988}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_27133175.glb', byteLength: 23304}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/CAP4080.glb', byteLength: 11960}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/BAP2051.glb', byteLength: 155440}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4501.glb', byteLength: 262404}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PRA/CAN4501.glb', status: 200, contentType: 'application/octet-stream', contentLength: '10488'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_13941978.glb', status: 200, contentType: 'application/octet-stream', contentLength: '78524'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/CAP4040.glb', status: 200, contentType: 'application/octet-stream', contentLength: '7592'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_83160222.glb', status: 200, contentType: 'application/octet-stream', contentLength: '14988'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_16181215.glb', status: 200, contentType: 'application/octet-stream', contentLength: '14988'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4555.glb', status: 200, contentType: 'application/octet-stream', contentLength: '218756'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/TIN4511.glb', status: 200, contentType: 'application/octet-stream', contentLength: '241024'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_59182232.glb', status: 200, contentType: 'application/octet-stream', contentLength: '1668'}
visual-3d.ts:186 [Fire3D Asset Loader] Successfully loaded asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_68051095.glb', status: 200, contentType: 'application/octet-stream', contentLength: '78524'}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PRA/CAN4501.glb', byteLength: 10488}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_59182232.glb', byteLength: 1668}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/CAP4040.glb', byteLength: 7592}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_83160222.glb', byteLength: 14988}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_16181215.glb', byteLength: 14988}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_68051095.glb', byteLength: 78524}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044PLA/clone_13941978.glb', byteLength: 78524}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/TIN4511.glb', byteLength: 241024}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4555.glb', byteLength: 218756}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4511.glb', byteLength: 395752}
visual-3d.ts:213 [Fire3D Asset Loader] Loaded ArrayBuffer asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/katalog/schutzzaun_basic/SBS1044SRA/DOR4553.glb', byteLength: 723288}
Fire3D.vue:196 🔎 fire-event: selection-click | target.id: h-ae7b43df-1422-46d3-bc98-855aa33d4ecb | target.selector: > handle#root:nth-child(7) > handle:nth-child(1) > handle#root.role-scene:nth-child(1) > handle#gk-95c7d251-5b69-4205-992c-0cae266193ea.role-assembly:nth-child(1) > handle#h-f1e99d8a-5821-42f7-bb77-7c19b0f13dc8.class-schutzzaun_basic-tuer.role-part:nth-child(1) > handle#h-d1d325a8-79ea-4fba-9bee-1be0052969be.name-assembly-root:nth-child(1) > handle#h-ed92919f-723c-4552-a6f1-0ac0b8b77747.name-EINZELTUER:nth-child(1) > handle#h-ae7b43df-1422-46d3-bc98-855aa33d4ecb.name-generic.part-FUELLUNG_LINKS_OBEN:nth-child(49) | target.tags: (2) ['name-generic', 'part-FUELLUNG_LINKS_OBEN'] | action: set | raw: {eventTypeName: 'selection-click', selectionAction: 'set', target: {…}}
Fire3D.vue:196 🔎 fire-event: selection | target.id: undefined | target.selector: undefined | target.tags: undefined | action: undefined | raw: {eventTypeName: 'selection', selected: Array(1)}
Fire3D.vue:64 ⌨ keydown: {"key":"Control","ctrl":true,"meta":false}
Fire3D.vue:64 ⌨ keydown: {"key":"Control","ctrl":true,"meta":false}


visual-3d.ts:167 [Fire3D Asset Loader] Attempting to load asset: {uri: 'https://rob-geko.hifassets.com/fire/assets/images/empty_warehouse_2K.jpg', originalPath: 'empty_warehouse_2K.jpg', typeKey: unde…


Diagnose steht: Der Listener feuert (`keydown: {"key":"Control"...}` kommt), aber der **Pfeil-keydown taucht gar nicht auf** — nur `Control`. Das heißt: fire fängt die Pfeiltasten (Kamera-Steuerung) ab und stoppt die Propagation, bevor sie den window-Listener erreichen. `Control` interessiert fire nicht → kommt durch; `ArrowRight` schluckt fire.

Fix: Listener in der **Capture-Phase** registrieren (greift vor fire) und die Kombination per `stopPropagation` abfangen:


    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        console.log("⌨ keyboard-build dispatch:", ARROW_DIR[e.key]);
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }


    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        console.log("⌨ keyboard-build dispatch:", ARROW_DIR[e.key]);
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

-        console.log("⌨ keyboard-build dispatch:", ARROW_DIR[e.key]);

+        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow


-    window.addEventListener("keydown", onKeydown);

+    window.addEventListener("keydown", onKeydown, true);

visual.value = await createVisual3D(root.value!, props.config);

visual.value.subscribe((event: any) => {

// TEMP Analyse: eingehende fire-Events (v.a. selection-click) sichtbar machen


-    window.removeEventListener("keydown", onKeydown);

+    window.removeEventListener("keydown", onKeydown, true);


Capture-Phase-Fix drin (`addEventListener(..., true)` + `stopPropagation`), HMR übernimmt. Jetzt greift der Listener **vor** fire, bekommt also den Pfeil, bevor die Kamera-Steuerung ihn schluckt.

Nochmal testen: Teil selektieren, Ctrl halten, Pfeil drücken. Erwartung im Log:
- `⌨ keydown: {"key":"ArrowRight","ctrl":true...}` (kam vorher nicht),
- dann `🔨 keyboard-build right {selectionLen, active, activeRoot, groupId}`.

Poste, was jetzt erscheint — dann sehen wir, ob der Handler durchläuft und ob die Selektion/das Element-Root sauber ankommt.


Fire3D.vue:196 🔎 fire-event: selection-click | target.id: h-49b32a5c-37dd-4e14-a112-ce26e429c004 | target.selector: > handle#root:nth-child(7) > handle:nth-child(1) > handle#root.role-scene:nth-child(1) > handle#gk-626f0414-c998-42d4-a17c-51b5810dc211.role-assembly:nth-child(1) > handle#h-57d0d7f4-eff7-4285-a9ea-f4fafccdfce0.class-schutzzaun_basic-element.role-part:nth-child(1) > handle#h-2fac21c6-d046-44c8-a44b-3c57c30217e7.name-assembly-root:nth-child(1) > handle#h-49b32a5c-37dd-4e14-a112-ce26e429c004.name-generic.part-FUELLUNG_LINKS_OBEN:nth-child(19) | target.tags: (2) ['name-generic', 'part-FUELLUNG_LINKS_OBEN'] | action: set | raw: {eventTypeName: 'selection-click', selectionAction: 'set', target: {…}}
Fire3D.vue:196 🔎 fire-event: selection | target.id: undefined | target.selector: undefined | target.tags: undefined | action: undefined | raw: {eventTypeName: 'selection', selected: Array(1)}
Fire3D.vue:64 ⌨ keydown: {"key":"Control","ctrl":true,"meta":false}


Gefunden — und es ist ein macOS-Ding: Der `ArrowRight`-keydown taucht selbst in der Capture-Phase nicht auf, weil **macOS `Ctrl+Pfeil` auf OS-Ebene abfängt** (Mission Control / Space-Wechsel). Die Taste erreicht den Browser gar nicht erst, also kann kein JS sie sehen. Deshalb kommt nur `Control` durch, nie der Pfeil.

Ich stelle auf **Shift+Pfeil** um — das ist auf macOS kein System-Shortcut und kommt im Viewport durch:


// PROTOTYPE: hold Ctrl and press an arrow to build/move off the active part.
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

// PROTOTYPE: hold Shift and press an arrow to build/move off the active part.
// (Ctrl+Arrow is swallowed by macOS Mission Control, so it never reaches JS.)
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };


// PROTOTYPE: hold Ctrl and press an arrow to build/move off the active part.
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

// PROTOTYPE: hold Shift and press an arrow to build/move off the active part.
// (Ctrl+Arrow is swallowed by macOS Mission Control, so it never reaches JS.)
const ARROW_DIR: Record<string, string> = { ArrowRight: "right", ArrowLeft: "left", ArrowUp: "up", ArrowDown: "down" };

-// PROTOTYPE: hold Ctrl and press an arrow to build/move off the active part.

+// PROTOTYPE: hold Shift and press an arrow to build/move off the active part.

+// (Ctrl+Arrow is swallowed by macOS Mission Control, so it never reaches JS.)


Fire3D.vue:196 🔎 fire-event: selection-click | target.id: h-49b32a5c-37dd-4e14-a112-ce26e429c004 | target.selector: > handle#root:nth-child(7) > handle:nth-child(1) > handle#root.role-scene:nth-child…


    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

    if (e.shiftKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }


    if (e.ctrlKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

    if (e.shiftKey && ARROW_DIR[e.key]) {
        e.preventDefault();
        e.stopPropagation(); // keep fire's camera controls from also consuming the arrow
        processReactions(processor.handleEvent({ eventTypeName: "keyboard-build", direction: ARROW_DIR[e.key] }));
    }

const reactions = processor.handleEvent({ eventTypeName: "esc" });

-    if (e.ctrlKey && ARROW_DIR[e.key]) {

+    if (e.shiftKey && ARROW_DIR[e.key]) {


Umgestellt auf **Shift + Pfeil**, HMR übernimmt. Jetzt testen: Teil selektieren, dann **Shift + Pfeil**. Der `ArrowRight`-keydown sollte jetzt durchkommen (Shift ist kein macOS-Shortcut), und Capture + `stopPropagation` halten fire von der Kamera-Steuerung ab.

Poste, was im Log erscheint — insbesondere ob jetzt `🔨 keyboard-build …` mit gefülltem `activeRoot`/`groupId` auftaucht.