From b80f52cb502d7250822aa82eacb7b484dedbbe74 Mon Sep 17 00:00:00 2001
From: Lars
Date: Wed, 29 Jul 2026 08:17:39 +0200
Subject: [PATCH] =?UTF-8?q?feat(plan):=20Join-Gate=20im=20Gate-Designer=20?=
=?UTF-8?q?f=C3=BCr=20alle=20Archetypen?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Typ Join/Schaltknoten anlegbar, Detail ohne Verify, visuelle Kennzeichnung im Graph.
Co-authored-by: Cursor
---
.../src/components/GateDesignerNodePanel.jsx | 13 +++++-
frontend/src/components/GateGraphDesigner.jsx | 1 +
frontend/src/components/GateMapView.jsx | 1 +
frontend/src/components/RoadmapItemForm.jsx | 43 ++++++++++++++-----
frontend/src/constants/status.js | 12 ++++++
.../initiative/RoadmapItemDetailPage.jsx | 41 +++++++++++++-----
frontend/src/styles/components.css | 7 +++
7 files changed, 95 insertions(+), 23 deletions(-)
diff --git a/frontend/src/components/GateDesignerNodePanel.jsx b/frontend/src/components/GateDesignerNodePanel.jsx
index be454e7..ecddf72 100644
--- a/frontend/src/components/GateDesignerNodePanel.jsx
+++ b/frontend/src/components/GateDesignerNodePanel.jsx
@@ -3,6 +3,7 @@ import { gatePath } from '../utils/routes.js'
import {
MILESTONE_STATUS_LABELS,
ROADMAP_ITEM_TYPE_LABELS,
+ isJoinGateItem,
} from '../constants/status.js'
import { StatusBadge } from './StatusBadge.jsx'
@@ -25,6 +26,7 @@ export function GateDesignerNodePanel({
}) {
if (!item) return null
+ const joinGate = isJoinGateItem(item)
const graphItem = graphState?.items?.[item.id]
const progress = criteriaProgress?.[item.id]
@@ -72,8 +74,15 @@ export function GateDesignerNodePanel({
)}
- {item.goal_description && (
- {item.goal_description}
+ {joinGate && (
+
+ Schaltknoten — schaltet, wenn alle Eingänge (requires) erreicht sind. Keine Checkliste,
+ kein manuelles Verify.
+
+ )}
+
+ {item.goal_description && (
+ {item.goal_description}
)}
{graphItem?.parallel_group_keys?.length > 0 && (
diff --git a/frontend/src/components/GateGraphDesigner.jsx b/frontend/src/components/GateGraphDesigner.jsx
index bb2602b..09e23a2 100644
--- a/frontend/src/components/GateGraphDesigner.jsx
+++ b/frontend/src/components/GateGraphDesigner.jsx
@@ -610,6 +610,7 @@ export function GateGraphDesigner({
const nodeClass =
'gate-map-view__node gate-map-view__node--' +
(item.status || 'planned') +
+ (item.item_type === 'join_gate' ? ' gate-map-view__node--join_gate' : '') +
(isSelected ? ' gate-graph-designer__node--selected' : '') +
(draggingNodeId === node.id ? ' gate-graph-designer__node--dragging' : '')
return (
diff --git a/frontend/src/components/GateMapView.jsx b/frontend/src/components/GateMapView.jsx
index db164e7..a8cd291 100644
--- a/frontend/src/components/GateMapView.jsx
+++ b/frontend/src/components/GateMapView.jsx
@@ -171,6 +171,7 @@ export function GateMapView({ initiativeId, items, graphState: graphStateProp, e
const nodeClass =
'gate-map-view__node gate-map-view__node--' +
(item.status || 'planned') +
+ (item.item_type === 'join_gate' ? ' gate-map-view__node--join_gate' : '') +
(enforceBlocking && nodeGraph?.blocked ? ' gate-map-view__node--blocked' : '') +
(nodeGraph?.ready ? ' gate-map-view__node--ready' : '')
return (
diff --git a/frontend/src/components/RoadmapItemForm.jsx b/frontend/src/components/RoadmapItemForm.jsx
index 51cbf5c..27e860e 100644
--- a/frontend/src/components/RoadmapItemForm.jsx
+++ b/frontend/src/components/RoadmapItemForm.jsx
@@ -1,10 +1,11 @@
+import { useState } from 'react'
import {
MILESTONE_STATUSES,
MILESTONE_STATUS_LABELS,
+ ROADMAP_ITEM_TYPES_FOR_DESIGNER,
ROADMAP_ITEM_TYPE_LABELS,
} from '../constants/status.js'
-const ITEM_TYPES = ['milestone', 'review_gate', 'maturity_stage']
const EDITABLE_STATUSES = MILESTONE_STATUSES.filter(
(s) => !['reached', 'moved', 'discarded'].includes(s),
)
@@ -17,6 +18,9 @@ export function RoadmapItemForm({
submitLabel = 'Anlegen',
mode = 'create',
}) {
+ const [itemType, setItemType] = useState(initial.item_type || 'milestone')
+ const isJoinGate = itemType === 'join_gate'
+
async function handleSubmit(e) {
e.preventDefault()
const form = e.target
@@ -27,7 +31,7 @@ export function RoadmapItemForm({
item_type: form.item_type.value,
sequencing_mode: initial.sequencing_mode || 'sequential',
}
- if (mode === 'create') {
+ if (mode === 'create' && !isJoinGate) {
const initialCriterion = form.initial_criterion_title?.value?.trim()
if (initialCriterion) {
payload.initial_criterion_title = initialCriterion
@@ -46,13 +50,23 @@ export function RoadmapItemForm({
{mode === 'create' && (
)}
@@ -72,14 +88,20 @@ export function RoadmapItemForm({
name="goal_description"
rows={3}
defaultValue={initial.goal_description || ''}
- placeholder="Kurzer Kontext — prüfbare Akzeptanz legst du als Kriterien an."
+ placeholder={
+ isJoinGate
+ ? 'Konsolidierter Meilenstein — welche Pfade laufen hier zusammen?'
+ : 'Kurzer Kontext — prüfbare Akzeptanz legst du als Kriterien an.'
+ }
disabled={statusLocked}
/>
-
- Die Checkliste auf der Gate-Detailseite ist führend für Verify.
-
+ {!isJoinGate && (
+
+ Die Checkliste auf der Gate-Detailseite ist führend für Verify.
+
+ )}
- {mode === 'create' && (
+ {mode === 'create' && !isJoinGate && (
{mode === 'edit' && (