sync: global→pool, pool-guard raus, Session-Watcher-Setup, verbotenes Wort Ehrlich

This commit is contained in:
marcus.hinz
2026-07-04 19:09:13 +02:00
parent a7ff9378da
commit 79314d6140
43 changed files with 669 additions and 33 deletions
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+338
View File
@@ -0,0 +1,338 @@
<!--
RSS generated by JIRA (1001.0.0-SNAPSHOT#100292-rev:f152b838bb945118c071b297741ecc5e36093b62) at Fri Jul 03 09:13:25 UTC 2026
It is possible to restrict the fields that are returned in this document by specifying the 'field' parameter in your request.
For example, to request only the issue key and summary add field=key&field=summary to the URL of your request.
-->
<rss version="0.92" >
<channel>
<title>Jira adesso Group extern</title>
<link>https://adesso-group-extern.atlassian.net</link>
<description>This file is an XML representation of an issue</description>
<language>en-us</language> <build-info>
<version>1001.0.0-SNAPSHOT</version>
<build-number>100292</build-number>
<build-date>30-06-2026</build-date>
</build-info>
<item>
<title>[GEKO-16] Schutzzaun im Layout schlie&#223;en/Schutzzaun zwischen zwei Punkten planen</title>
<link>https://adesso-group-extern.atlassian.net/browse/GEKO-16</link>
<project id="12341" key="GEKO">GEKO</project>
<description>&lt;p&gt;Dieses Ticket implementiert die interaktive Planung eines Schutzzauns zwischen zwei Punkten im 3D-Layout. Die Funktion erm&#246;glicht das Ziehen einer Strecke, berechnet die Geometrie und generiert den Zaun als kaskadierende Baugruppe. Neu ist die intelligente Vererbungslogik: Werden PMIs als Start- oder Endpunkt genutzt, erbt der Zaun Typ und F&#252;llung automatisch; andernfalls werden diese &#252;ber ein modales Overlay abgefragt.&lt;/p&gt;
&lt;h3&gt;&lt;a name=&quot;%F0%9F%9F%A6DefinitionofReady%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt; Definition of Ready&lt;/b&gt;&#160;&lt;/h3&gt;
&lt;h4&gt;&lt;a name=&quot;Wer%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Wer?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;p&gt;Anwender des Schutzzaun-Konfigurators im 3D-Layout.&lt;/p&gt;
&lt;h4&gt;&lt;a name=&quot;Was%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Was?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Interaktives Setzen von Start- und Endpunkt (frei oder gesnappt an PMIs).&lt;/li&gt;
&lt;li&gt;Dynamische Anzeige von 3 Bema&#223;ungen (L&#228;nge, Diagonale, Winkel) w&#228;hrend der Mausbewegung.&lt;/li&gt;
&lt;li&gt;Intelligente Eigenschafts-Vererbung (Typ/F&#252;llung) basierend auf Start-/End-PMIs zur Vermeidung unn&#246;tiger UI-Abfragen.&lt;/li&gt;
&lt;li&gt;Filterung von PMIs: Inkompatible End-PMIs werden nach der Auswahl des Start-PMIs ignoriert und nicht gehighlighted.&lt;/li&gt;
&lt;li&gt;Mathematische Berechnung von 2D-L&#228;nge (XZ-Ebene) und Ausrichtungswinkel.&lt;/li&gt;
&lt;li&gt;Algorithmus-gest&#252;tzte Aufteilung der Strecke in Standard- und Sonderelemente.&lt;/li&gt;
&lt;li&gt;Generierung des Zauns als kaskadierender Verbund (Typ 2 Einbaubedingungen).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;a name=&quot;Warum%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Warum?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;p&gt;Effiziente Planung von Schutzz&#228;unen &#252;ber gr&#246;&#223;ere Distanzen, ohne dass der Nutzer jedes Element einzeln platzieren muss und unter Vermeidung von fehlerhaften Typen-Mischungen.&lt;/p&gt;
&lt;h4&gt;&lt;a name=&quot;Wozu%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Wozu?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;p&gt;Ein strukturierter, in den Szenegraphen integrierter Schutzzaun-Zug, mit dem offene Z&#252;ge geschlossen werden k&#246;nnen.&lt;/p&gt;</description>
<environment></environment>
<key id="139286">GEKO-16</key>
<summary>Schutzzaun im Layout schlie&#223;en/Schutzzaun zwischen zwei Punkten planen</summary>
<type id="12871" iconUrl="https://adesso-group-extern.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium">Task</type>
<parent id="119259">GEKO-6</parent>
<priority id="10001" iconUrl="https://adesso-group-extern.atlassian.net/images/icons/priorities/major.svg">Major</priority>
<status id="14794" iconUrl="https://adesso-group-extern.atlassian.net/" description="">To Do</status>
<statusCategory id="2" key="new" colorName="blue-gray"/>
<resolution id="-1">Unresolved</resolution>
<assignee accountid="712020:1a31ac71-0e6f-49af-83f9-0d630a810204">Marcus Hinz</assignee>
<reporter accountid="712020:0a2bb4c7-7da6-48f0-8264-d021465e5f0f">Lukas H&#246;rger</reporter>
<labels>
</labels>
<created>Wed, 8 Apr 2026 11:43:54 +0200</created>
<updated>Thu, 2 Jul 2026 14:28:25 +0200</updated>
<due></due>
<votes>0</votes>
<watches>0</watches>
<attachments>
</attachments>
<subtasks>
</subtasks>
<customfields>
<customfield id="customfield_10000" key="com.atlassian.jira.plugins.jira-development-integration-plugin:devsummarycf">
<customfieldname>Development</customfieldname>
<customfieldvalues>
</customfieldvalues>
</customfield>
<customfield id="customfield_10019" key="com.pyxis.greenhopper.jira:gh-lexo-rank">
<customfieldname>Rank</customfieldname>
<customfieldvalues>
<customfieldvalue>0|i5ji9k:</customfieldvalue>
</customfieldvalues>
</customfield>
<customfield id="customfield_13432" key="com.atlassian.jira.plugin.system.customfieldtypes:select">
<customfieldname>T-Shirt Size - Storypoint</customfieldname>
<customfieldvalues>
<customfieldvalue key="20365"><![CDATA[L]]></customfieldvalue>
</customfieldvalues>
</customfield>
<customfield id="customfield_13430" key="com.atlassian.jira.plugin.system.customfieldtypes:textarea">
<customfieldname> Definition of Ready - Formale Kriterien</customfieldname>
<customfieldvalues>
<customfieldvalue>&lt;h3&gt;&lt;a name=&quot;%F0%9F%93%98Funktionsbeschreibung%3A&quot;&gt;&lt;/a&gt;&lt;b&gt; Funktionsbeschreibung:&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Input:&lt;/b&gt; Klick-Events im 3D-Viewport und ggf. Auswahl-Events im modalen Overlay.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Verhalten / Ablauf:&lt;/b&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;Startpunkt (Klick 1):&lt;/b&gt; Nutzer setzt den ersten Punkt (Typ 1 Weltkoordinaten oder Typ 2 PMI).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Zeichnen &amp;amp; Filtern:&lt;/b&gt; Unmittelbar &lt;b&gt;nach dem 1. Klick&lt;/b&gt; (sofern dieser an einem PMI erfolgte) filtert die Engine den Szenegraphen. Anhand des Typs des Start-PMIs (z. B. Basic) werden alle inkompatiblen PMIs (z. B. Allround) f&#252;r den aktuellen Zeichen-Modus als ung&#252;ltig markiert. W&#228;hrend der Nutzer nun die Gummiband-Linie zum Endpunkt zieht, werden diese inkompatiblen PMIs konsequent nicht gehighlighted und k&#246;nnen nicht angeklickt werden.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Endpunkt (Klick 2):&lt;/b&gt; Nutzer setzt den zweiten Punkt.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Vererbungslogik &amp;amp; Konfiguration:&lt;/b&gt; Die Engine entscheidet anhand folgender Logik-Matrix, woher die Parameter (Typ/F&#252;llung) stammen:
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Start: Frei | Ende: Frei&lt;/b&gt; &#8594; Keine Vererbung. Das modale Auswahl-Overlay &#246;ffnet sich. Standard-Elemente werden nach Best&#228;tigung generiert.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Start: PMI | Ende: Frei&lt;/b&gt; &#8594; Vererbung der Properties vom Start-PMI. Das Overlay erscheint &lt;b&gt;nicht&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Start: Frei | Ende: PMI&lt;/b&gt; &#8594; Vererbung der Properties vom End-PMI. Das Overlay erscheint &lt;b&gt;nicht&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Start: PMI | Ende: PMI&lt;/b&gt; &#8594; Vererbung der Properties vom Start-PMI. (Typengleichheit ist durch das Filtern in Schritt 2 bereits sichergestellt). Das Overlay erscheint &lt;b&gt;nicht&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Geometrie-Berechnung:&lt;/b&gt; Y-Offset und Rotationen werden ignoriert. Absolute Strecke &#252;ber Pythagoras ({{L = &#8730;(&#916;x&#178; + &#916;z&#178;)}}), Winkel &#252;ber &lt;tt&gt;atan2(&#916;z, &#916;x)&lt;/tt&gt; (auf XZ-Ebene projiziert).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Element-Aufteilung:&lt;/b&gt; Die Logik &lt;tt&gt;berechnung_aufteilung_laenge&lt;/tt&gt; ermittelt die Rasterelemente &lt;em&gt;(siehe Algorithmus-Abschnitt unten)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Generierung:&lt;/b&gt; Elemente werden kaskadierend via PMI (Typ 2) eingebaut.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Validierung:&lt;/b&gt; Trigger der lokalen Validierungsengine.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;a name=&quot;Algorithmus%3AElementAufteilung%28%7B%7Bberechnungaufteilunglaenge%7D%7D%29&quot;&gt;&lt;/a&gt;Algorithmus: Element-Aufteilung (&lt;tt&gt;berechnung_aufteilung_laenge&lt;/tt&gt;)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;1. Grundkonfiguration:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Typ &quot;Basic&quot;:&lt;/b&gt; Der Elementabstand wird als Parameter &#252;bergeben; es gibt keinen Steherzusatz (0 mm).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Typ &quot;Allround&quot;:&lt;/b&gt; Fester Elementabstand von &lt;b&gt;11 mm&lt;/b&gt;, Steherzusatz von &lt;b&gt;51 mm&lt;/b&gt; pro Element.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;T&#252;ren:&lt;/b&gt; Ist eine T&#252;r in der Konfiguration enthalten, wird pauschal ein Ma&#223; von &lt;b&gt;1124 mm&lt;/b&gt; (zzgl. Elementabstand) von der Gesamtbreite abgezogen.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2. Standardelemente (Greedy):&lt;/b&gt; Der Raum wird systematisch mit den gr&#246;&#223;ten passenden Standardelementen gef&#252;llt (Priorit&#228;t: 1507, 1034, 776, 518 mm).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;3. Dead Zone Correction (Todeszone):&lt;/b&gt; Bleibt eine kritische Restbreite (zwischen 43 mm und Mindestbreite der F&#252;llung) &#252;brig, wird das kleinste bereits verplante Element downgradet (z. B. 776 mm -&amp;gt; 518 mm) oder gel&#246;scht, um Platz f&#252;r ein produzierbares Sonderelement zu schaffen.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;4. Sonderelement (Rasterung):&lt;/b&gt; Aus der Restbreite wird im &lt;b&gt;43-mm-Raster&lt;/b&gt; das finale Passst&#252;ck ermittelt. Restl&#252;cken &amp;lt; 43 mm werden als Toleranz ignoriert.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Pseudocode der Aufteilungslogik:&lt;/b&gt;&lt;/p&gt;
&lt;div class=&quot;preformatted panel&quot; style=&quot;border-width: 1px;&quot;&gt;&lt;div class=&quot;preformattedContent panelContent&quot;&gt;
&lt;pre&gt;FUNCTION calculate_width_distribution(type, has_door, gap_input, total_width, filling_type)
INITIALIZE results AS dictionary
// --- 1. Base Setup &amp;amp; Door Deduction ---
IF type == &quot;Basic&quot; THEN
gap = gap_input
post_addition = 0
ELSE IF type == &quot;Allround&quot; THEN
gap = 11
post_addition = 51
END IF
IF has_door == TRUE THEN
deduction = 1124 + gap
results[&quot;door_count&quot;] = 1
ELSE IF type != &quot;0&quot; THEN
deduction = 51
results[&quot;door_count&quot;] = 0
ELSE
deduction = 0
results[&quot;door_count&quot;] = 0
END IF
remaining_width = total_width - deduction
test_width = remaining_width
// --- 2. Distribute Standard Elements (Greedy Approach) ---
STANDARD_SIZES = [1507, 1034, 776, 518]
element_counts = {1507: 0, 1034: 0, 776: 0, 518: 0}
FOR EACH size IN STANDARD_SIZES DO
required_width = size + post_addition
WHILE test_width &amp;gt;= required_width DO
element_counts[size] = element_counts[size] + 1
remaining_width = test_width - required_width
test_width = remaining_width - gap
END WHILE
END FOR
// --- 3. Dead Zone Correction ---
min_special_element = (IF filling_type == 0 THEN 174 ELSE 131) + post_addition
IF test_width &amp;gt;= 43 AND test_width &amp;lt;= min_special_element THEN
smallest_assigned = FIND_SMALLEST_ASSIGNED_ELEMENT(element_counts)
next_smaller = FIND_NEXT_SMALLER_SIZE(STANDARD_SIZES, smallest_assigned)
IF smallest_assigned EXISTS THEN
element_counts[smallest_assigned] = element_counts[smallest_assigned] - 1
IF next_smaller EXISTS THEN
// Downgrade to next smaller standard element
element_counts[next_smaller] = element_counts[next_smaller] + 1
difference = smallest_assigned - next_smaller
remaining_width = remaining_width + difference
test_width = test_width + difference
ELSE
// Lowest element was 518, remove it completely
freed_space = post_addition + smallest_assigned + gap
remaining_width = remaining_width + freed_space
test_width = test_width + freed_space
END IF
END IF
END IF
SAVE element_counts TO results
test_width = test_width - post_addition
// --- 4. Special Element (Sonderelement) Calculation ---
special_element_width = 0
test_width_int = FLOOR(test_width)
IF test_width_int &amp;lt; 43 THEN
special_element_width = 0
ELSE IF test_width_int &amp;gt;= 518 AND test_width_int &amp;lt; 561 THEN
results[&quot;518_count&quot;] = results[&quot;518_count&quot;] + 1
remaining_width = remaining_width - 518 - gap - post_addition
ELSE IF test_width_int &amp;gt;= 776 AND test_width_int &amp;lt; 819 THEN
results[&quot;776_count&quot;] = results[&quot;776_count&quot;] + 1
remaining_width = remaining_width - 776 - gap - post_addition
ELSE IF test_width_int &amp;gt;= 131 AND test_width_int &amp;lt; 776 THEN
// Calculate based on a grid step of 43
calculated_size = ((test_width_int - 131) / 43) * 43 + 131
IF calculated_size == 131 AND min_special_element != 131 + post_addition THEN
special_element_width = 0 // Min size rule prevents a 131 element
ELSE
special_element_width = calculated_size
END IF
END IF
// --- 5. Final Remainder Output ---
results[&quot;special_element&quot;] = special_element_width
IF special_element_width &amp;gt; 0 THEN
remaining_width = remaining_width - special_element_width - gap - post_addition
END IF
results[&quot;final_remainder&quot;] = remaining_width
RETURN results
END FUNCTION
FUNCTION determine_parameters_and_generate(start_point, end_point)
// --- 1. Vererbungs-Weiche (Typ und F&#252;llung ermitteln) ---
IF start_point.is_pmi == TRUE THEN
// Vererbung vom Start-PMI (gilt f&#252;r PMI+Frei und PMI+PMI)
fence_type = start_point.parent.type
fence_filling = start_point.parent.filling
ELSE IF end_point.is_pmi == TRUE THEN
// R&#252;ckw&#228;rtsvererbung vom End-PMI (gilt f&#252;r Frei+PMI)
fence_type = end_point.parent.type
fence_filling = end_point.parent.filling
ELSE
// Beide Punkte frei (Frei+Frei) -&amp;gt; Modales UI-Overlay
ui_selection = SHOW_MODAL_OVERLAY()
IF ui_selection == CANCELLED THEN RETURN
fence_type = ui_selection.type
fence_filling = ui_selection.filling
END IF
// --- 2. Geometrie berechnen ---
total_width = CALCULATE_2D_DISTANCE(start_point, end_point)
angle = CALCULATE_2D_ANGLE(start_point, end_point)
// --- 3. Aufteilung berechnen (Kern-Algorithmus bleibt unver&#228;ndert) ---
distribution = calculate_width_distribution(fence_type, FALSE, 0, total_width, fence_filling)
// --- 4. Kaskadierende Platzierung triggern ---
PLACE_FENCE_ELEMENTS(distribution, start_point, angle)
END FUNCTION&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h4&gt;&lt;a name=&quot;NichtZiele%28OutofScope%29&quot;&gt;&lt;/a&gt;Nicht-Ziele (Out of Scope)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Live-Vorschau (Ghosting):&lt;/b&gt; Das Rendern einer 3D-Vorschau des Zauns w&#228;hrend der Mausbewegung.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Passst&#252;cke (Sonderbau):&lt;/b&gt; Generierung freier Sonder-Bauteile au&#223;erhalb des 43-mm-Rasters.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spezifikation des Overlays:&lt;/b&gt; Das UI des modalen Overlays wird separat spezifiziert.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a name=&quot;%F0%9F%93%91Akzeptanzkriterien%3A&quot;&gt;&lt;/a&gt;&lt;b&gt; Akzeptanzkriterien:&lt;/b&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Die Vererbungsmatrix wird strikt eingehalten: Das Konfigurations-Overlay &#246;ffnet sich &lt;em&gt;ausschlie&#223;lich&lt;/em&gt;, wenn sowohl Start- als auch Endpunkt frei im Raum (Typ 1) liegen.&lt;/li&gt;
&lt;li&gt;Ist der Startpunkt ein PMI, werden inkompatible PMIs im Viewer beim Hovern ignoriert und nicht als Snapping-Ziel angeboten.&lt;/li&gt;
&lt;li&gt;Startet der Zaun frei und endet an einem PMI, &#252;bernimmt der Zaun die Parameter des Ziel-Elements fehlerfrei (R&#252;ckw&#228;rtsvererbung).&lt;/li&gt;
&lt;li&gt;Geometrie wird flach auf der XZ-Ebene berechnet; H&#246;henunterschiede zwischen den Punkten f&#252;hren nicht zu schiefen Z&#228;unen.&lt;/li&gt;
&lt;li&gt;Kaskadierendes Verhalten: Bei Verschiebung des Eltern-Bauteils bewegt sich der gesamte Zaun-Verbund mit.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a name=&quot;%F0%9F%A7%AATestCases%3A&quot;&gt;&lt;/a&gt;&lt;b&gt; Test Cases:&lt;/b&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;R&#252;ckw&#228;rtsvererbung&amp;#93;&lt;/span&gt;&lt;/b&gt;: Startpunkt ins Freie klicken -&amp;gt; Endpunkt an PMI eines Basic-Zauns mit Polycarbonat-F&#252;llung snappen -&amp;gt; &lt;em&gt;Erwartetes Ergebnis:&lt;/em&gt; Kein Overlay &#246;ffnet sich. Es wird sofort ein Basic-Zaun mit Polycarbonat generiert, der an das Ziel snappt.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;PMI Typen-Filter&amp;#93;&lt;/span&gt;&lt;/b&gt;: Startpunkt an Allround-PMI klicken -&amp;gt; Maus &#252;ber ein Basic-PMI bewegen -&amp;gt; &lt;em&gt;Erwartetes Ergebnis:&lt;/em&gt; Das Basic-PMI wird nicht gehighlighted und kann nicht als Endpunkt ausgew&#228;hlt werden.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Overlay-Trigger&amp;#93;&lt;/span&gt;&lt;/b&gt;: Startpunkt frei klicken -&amp;gt; Endpunkt frei klicken -&amp;gt;&lt;em&gt;Erwartetes Ergebnis:&lt;/em&gt; Modales Overlay zur Parameterauswahl &#246;ffnet sich.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a name=&quot;%E2%9A%A0%EF%B8%8FEdgeCases%3A%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;&#9888;&#65039; Edge Cases:&lt;/b&gt;&#160;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Abbruch w&#228;hrend Zeichnen&amp;#93;&lt;/span&gt;&lt;/b&gt;: &lt;tt&gt;ESC&lt;/tt&gt; oder ein Klick au&#223;erhalb des Canvas bricht den Modus nach Klick 1 restlos ab.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Distanz zu kurz&amp;#93;&lt;/span&gt;&lt;/b&gt;: Ist die Gesamtdistanz kleiner als das schmalste verf&#252;gbare Rasterelement, f&#252;hrt der Abschluss im Overlay zu einem No-Op (keine Generierung).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Eltern-L&#246;schung&amp;#93;&lt;/span&gt;&lt;/b&gt;: Wird das Bauteil gel&#246;scht, an dessen PMI der Zaun beginnt, verliert das erste Zaunelement seine Typ 2 Bedingung, f&#228;llt auf Typ 1 zur&#252;ck und der Zaun bleibt stehen.&lt;/li&gt;
&lt;/ul&gt;
</customfieldvalue>
</customfieldvalues>
</customfield>
<customfield id="customfield_13429" key="com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes">
<customfieldname> DoR - Checkliste</customfieldname>
<customfieldvalues>
<customfieldvalue key="20347"><![CDATA[Ziel eindeutig formuliert (W-Fragen vollständig?)]]></customfieldvalue>
<customfieldvalue key="20349"><![CDATA[Akzeptanzkriterien definiert]]></customfieldvalue>
<customfieldvalue key="20350"><![CDATA[mind. ein Testfall dokumentiert]]></customfieldvalue>
<customfieldvalue key="20351"><![CDATA[Priorität gesetzt]]></customfieldvalue>
<customfieldvalue key="20352"><![CDATA[Edge Cases beschrieben]]></customfieldvalue>
<customfieldvalue key="20353"><![CDATA[TShirtAufwand geschätzt]]></customfieldvalue>
</customfieldvalues>
</customfield>
</customfields>
</item>
</channel>
</rss>
+3
View File
@@ -0,0 +1,3 @@
{
"pool": "privateDefault"
}
+15
View File
@@ -0,0 +1,15 @@
// ghostty-wincount <pid> Anzahl sichtbarer Normal-Fenster (Layer 0) des
// Prozesses, direkt vom Window-Server. Keine Apple-Events, kein TCC-Prompt.
import CoreGraphics
import Foundation
guard CommandLine.arguments.count == 2, let pid = Int32(CommandLine.arguments[1]) else {
FileHandle.standardError.write(Data("usage: ghostty-wincount <pid>\n".utf8))
exit(2)
}
let list = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: Any]] ?? []
let n = list.filter {
($0[kCGWindowOwnerPID as String] as? Int32) == pid &&
($0[kCGWindowLayer as String] as? Int) == 0
}.count
print(n)
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# pool-guard — SessionStart-Hook: prüft direkt nach dem claude-Start, ob die
# Session über claude-sync (Pool-Mechanismus) läuft. claude-sync schreibt die
# Sentinel-Datei in den Projekt-Root und hält sie offen; ein RAW-Start hat
# keine lebende Sentinel dahinter und wird beendet.
set -uo pipefail
sentinel="$CLAUDE_PROJECT_DIR/.ai-control-running"
fail() {
# claude im Elternbaum finden und beenden, dann Banner aufs Terminal.
p=$PPID
while [ -n "$p" ] && [ "$p" -gt 1 ]; do
case "$(ps -o comm= -p "$p")" in
*claude*) kill "$p"; break ;;
esac
p=$(ps -o ppid= -p "$p" | tr -d ' ')
done
sleep 0.3
{
printf '\n\033[1;5;97;41m POOL-GUARD: %s \033[0m\n\n' "$1"
printf '\033[1;31mDiese Session lief außerhalb des Pool-Mechanismus und wurde beendet.\nStart nur über die Ghostty-App bzw. claude-sync.\033[0m\n'
} >/dev/tty
exit 2
}
[ -f "$sentinel" ] || fail "keine Sentinel-Datei ($sentinel)"
pool=$(sed -n 's/^pool=//p' "$sentinel")
pid=$(sed -n 's/^pid=//p' "$sentinel")
[ "$pool" = "${CLAUDE_CONFIG_DIR:-}" ] || fail "Pool-Mismatch: Sentinel=$pool, Session=${CLAUDE_CONFIG_DIR:-<leer>}"
kill -0 "$pid" 2>/dev/null || fail "claude-sync (PID $pid) lebt nicht mehr"
exit 0
+3 -1
View File
@@ -8,4 +8,6 @@
- [cpq Storybook starten](cpq-storybook-start.md) — pnpm --filter @ad-cpq/common-frontend storybook (Port 6006)
- [Git nur Fast-Forward](feedback-git-nur-fast-forward.md) — kein rebase/merge/force; bei Divergenz melden statt auflösen
- [fire-visual Placement-Mode](fire-visual-placement-mode.md) — snap-to-point: DragPoints/DropPoints, processInput-Algorithmus, start_placement_mode-Command, PMI-DropPoints
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Engine kann's, cpq-GroupProcessor (translation-only) plättet die Rotation beim Replay; Punktdaten tragen die Orientierung schon
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Rotation ist im GroupProcessor-Replay angekommen (Edge.rot/computeLayout); fachliche 90°-Klärung läuft noch
- [cpq Configurator Modul-Registry](cpq-configurator-modul-registry.md) — GEKO-76/16: Registry in configurator/modules, Hook nach replay(), Heilung=Ableitung, Overrides=Events
- [Suche nur in Projektordnern](feedback-suche-nur-projektordner.md) — find/grep nie über ~ oder den ganzen Rechner, nur im konkreten erlaubten Verzeichnis
@@ -0,0 +1,19 @@
---
name: cpq-configurator-modul-registry
description: "GEKO-76/16-Konzept: Modul-Registry im cpq-Configurator — Injection über DefaultCommandProcessor, Hook nach replay(), Heilung = Ableitung, Overrides = Events"
metadata:
node_type: memory
type: project
originSessionId: 8d7f85b8-4927-4426-9e04-a8be7def3b5e
---
Konzept-Entscheidung (2026-07-03) für die cpq/geko-Abgrenzung der Schutzzaun-Tickets GEKO-76 (Steher-Logik) und GEKO-16 (Zaun zwischen zwei Punkten):
- **Registry-Ort:** `packages/frontend/src/configurator/` (neues Verzeichnis `modules/`), kein neues Package. cpq bleibt domänen-agnostisch; geko liefert Module (Steher, Zaun-Zug) von außen.
- **Injection:** Konstruktor des `DefaultCommandProcessor`; `Fire3D.vue` nimmt bereits `props.processor` entgegen — die geko-App baut den Processor mit ihren Modulen und reicht ihn durch.
- **Zentraler Hook:** nach `groups.replay(...)` in `DefaultCommandProcessor.replay()` — einziger Engpass für Live, Undo/Redo und Placement; Module können Live/Replay nicht auseinanderlaufen lassen.
- **Semantik:** Selbstheilung (Bauform AG, Höhe) ist reine Ableitung aus der Topologie (`occupiedPmis()`, `Edge.rot`) und landet nie im EventStore; manuelle Detailmenü-Overrides sind echte, undo-bare Events (Modul-eigene Event-Typen).
- **Mit aufzuräumen:** hartkodierte `.name-CS_EIN_*`-Selektoren im `DefaultCommandProcessor` → Placement-Konfiguration des Moduls; Löschen ist im Processor noch unbehandelt → Kaskaden-Hook von Anfang an als Modul-Vertrag entwerfen.
- Ticket-Quellen lokal: `~/claude-projects/robotunits/GEKO-76.xml`, `GEKO-16.xml`; DrawIO-Abgrenzung: `GEKO-76-steher-cpq-geko.drawio`.
Siehe [[cpq-verbau-architektur]], [[cpq-verbau-90grad-steher]].
+9 -17
View File
@@ -1,28 +1,20 @@
---
name: cpq-verbau-90grad-steher
description: "Ziel 90°-Anbau am Steher wie jetzt Links/Rechts — Engine kann es, cpq-GroupProcessor (translation-only) ist der Blocker"
description: "90°-Anbau am Steher: Rotation ist im GroupProcessor-Replay angekommen (Edge.rot, computeLayout) — fachliche 90°-Klärung läuft noch"
metadata:
node_type: memory
type: project
originSessionId: e3d54daa-eb88-474f-9e99-62bfe83821b2
---
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der jetzige Links/Rechts-Verbau.
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der Links/Rechts-Verbau.
**Wer ist der Blocker: wir (cpq), nicht fire-visual.**
**Stand 2026-07-03 (verifiziert im Code):** Der frühere Blocker — translation-only-Replay im cpq-`GroupProcessor` — ist behoben. `packages/frontend/src/configurator/model/GroupProcessor.ts`:
- `Edge` trägt `rot?: Quat` (relative Rotation pro Dock-Kante).
- `computeLayout` komponiert Rotationen über den ungerichteten Kantengraphen (Quaternionen, vorwärts `R_to = R_from ∘ edge.rot`, rückwärts invers); mit Identity-Rotationen reduziert es sich exakt auf den alten translation-only-Solver.
- `dockRotation` leitet `Edge.rot` aus den PMI-Frame-Orientierungen ab (`pmiRotation` liest `properties.rotation` als Euler ZYX Grad → Quat).
- Placement nutzt die `CS_EIN_VORNE`/`CS_EIN_HINTEN`-Selektoren (hart im `DefaultCommandProcessor` — Kandidat für Modul-Konfiguration).
- **fire-visual kann es.** Das Live-Snapping im PlacementMode setzt die Subject-Matrix als `bestMatrix · dragPointTransform⁻¹` — die Rotation des Drag-Punkts ist drin, die Engine richtet also den vollen Frame aus und dreht das Teil beim Snap schon korrekt um 90°. (Vorbehalt: aus der Placement-Matrix-Notiz abgeleitet, nicht aus frischem Quell-Read; bei Bedarf `PlacementMode`-Quelle prüfen, ob DropPoints den Rotations-Frame durchreichen.)
- **cpq verliert es beim Replay.** Nach dem Snap übernehmen wir das Engine-Ergebnis nicht, sondern merken nur „Punkt A trifft Punkt B" (`group-merge` mit zwei PMI-IDs) und rechnen die Anordnung im `GroupProcessor` selbst neu — rein translatorisch. Beim nächsten `setScene` wird die 90°-Drehung auf achsparallel zurückgeplättet.
Die fachliche Klärung der 90°-Frage (GEKO-Seite) läuft laut Marcus noch separat.
**Warum schwer — das Dock-Modell ist translation-only, einachsig, gemeinsame Orientierung. Links/Rechts geht nur, weil nie rotiert wird.** Zu ändernde Schichten:
1. `Edge` = `{from,to,fromPmi,toPmi}` kennt keine Rotation → relative Rotation pro Kante nötig.
2. `computePositions` dockt mit `pos(to)=pos(from)+pmi(from)pmi(to)` (nur Position) → Rotation entlang der Kanten komponieren.
3. `pmiLocal` liest nur `position.value`. **Die Orientierung steckt schon in den Daten** (`properties.rotation`), wird aber ignoriert — und Format passt nicht: 3 Euler-Grad (z. B. `CS_EIN_90_0_0``[0,-0,180]`), deklariert als „quaternion", während `transformMath` `[w,x,y,z]` erwartet.
4. Placement-Mode hartverdrahtet auf `.name-CS_LINKS``.name-CS_RECHTS`; Steher-Anschlüsse leben in der `CS_EIN_*`-Familie → neue Drag-/Drop-Selektoren + Open-End-Logik (`occupiedPmis`).
5. `buildDetachEvent`/`areConnected` nehmen einen 1-achsigen Lauf an (dominante Achse, Links→Rechts-Leseordnung) → 90°-Anbau macht den Verbau zum 2D-Baum, Annahmen brechen.
**Datenlage:** kein plain `CS_VORNE`/`CS_HINTEN` (0 Teile); `CS_LINKS`/`CS_RECHTS` nur auf 12 Teilen; Vorne/Hinten nur in `CS_EIN_*` (rotationscodiert: `CS_EIN_VORNE`, `CS_EIN_HINTEN`, `CS_EIN_STEHER_VORNE/HINTEN`, `CS_EIN_90_0_0` …).
**Machbar:** Erweiterung unseres Modells (Rotation konsumieren + entlang Kanten propagieren + Format-Fix), keine Nachgenerierung der Teile — die nötige Orientierung liefern die Punktdaten bereits.
Siehe [[cpq-verbau-architektur]], [[fire-visual-placement-mode]].
Siehe [[cpq-verbau-architektur]], [[fire-visual-placement-mode]], [[cpq-configurator-modul-registry]].
@@ -0,0 +1,14 @@
---
name: feedback-suche-nur-projektordner
description: "Suchen/find/grep nur innerhalb der erlaubten Projektverzeichnisse, nie über ~ oder den ganzen Rechner"
metadata:
node_type: memory
type: feedback
originSessionId: 8d7f85b8-4927-4426-9e04-a8be7def3b5e
---
Such-Befehle (find, grep, ls …) nur innerhalb der erlaubten Arbeitsverzeichnisse des jeweiligen Projekts ausführen — nicht über `/Users/marcus.hinz`, nicht über breite Pfad-Listen quer durch `~/projects`, nicht über den ganzen Rechner.
**Why:** Marcus erlaubt Zugriff gezielt pro Projektordner; breite Sweeps über Home verletzen diese Abgrenzung, auch wenn sie technisch funktionieren.
**How to apply:** Vor einem Such-Befehl den konkretesten bekannten Pfad wählen (z. B. das genannte Repo oder `~/claude-projects/robotunits`). Liegt das Ziel dort nicht, melden statt den Suchraum eigenmächtig auszuweiten. Gilt zusätzlich zu [[feedback-keine-intent-unterstellung]]s Grundregel „nur suchen, wenn angewiesen".