+GET /diagrams/{hash}/render?direction=LR
+ │
+ ▼
+ DiagramLayout { nodes[], edges[], width, height }
+ │
+ ▼
+ separateFlows(nodes) → mainNodes[] + errorSections[]
+ │ │
+ ▼ ▼
+ renderMainSection()renderErrorSection()
+ │ │
+ ▼ ▼
+ SVG groups with SVG groups offset below
+ ELK x/y coordinates main section by mainHeight + gap
+
+
+
+
\ No newline at end of file
diff --git a/.superpowers/brainstorm/10188-1774613058/interactions-detail.html b/.superpowers/brainstorm/10188-1774613058/interactions-detail.html
new file mode 100644
index 00000000..56b36425
--- /dev/null
+++ b/.superpowers/brainstorm/10188-1774613058/interactions-detail.html
@@ -0,0 +1,164 @@
+
Node Interactions & Config Badges
+
Hover toolbar, selection states, and active config indicators
+
+
+
+
Node States
+
+
+
+
+
+
+
+
+
Toolbar Actions
+
+
+
+
+
Icon
+
Action
+
Description
+
+
+
+
+
🔍
+
Inspect
+
Select node & open detail side-panel
+
+
+
T
+
Toggle Trace
+
Enable/disable capture of input+output for this processor
+
+
+
✎
+
Configure Tap
+
Open tap expression editor for this processor
+
+
+
⋯
+
More
+
Copy processor ID, jump to code, view in search
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.superpowers/brainstorm/10188-1774613058/node-interactions.html b/.superpowers/brainstorm/10188-1774613058/node-interactions.html
new file mode 100644
index 00000000..445e0bfb
--- /dev/null
+++ b/.superpowers/brainstorm/10188-1774613058/node-interactions.html
@@ -0,0 +1,119 @@
+
Node Interaction Model
+
What happens when you interact with a processor node on the diagram?
+
+
+
+
+
+
+
+
+
A: Click-Select + Right-Click Menu
+
Click to select a node (amber highlight ring). Right-click for context menu with tracing/tap/snapshot actions. Clean separation of concerns. Standard desktop UX.
+
+
+
+
+
+
+
+
+
+
B: Hover Floating Toolbar
+
Hover reveals a dark floating icon toolbar above the node. Click still selects. More discoverable than right-click, but can feel cluttered on dense diagrams.
+
+
+
\ No newline at end of file
diff --git a/.superpowers/brainstorm/10188-1774613058/node-style.html b/.superpowers/brainstorm/10188-1774613058/node-style.html
new file mode 100644
index 00000000..271e60ff
--- /dev/null
+++ b/.superpowers/brainstorm/10188-1774613058/node-style.html
@@ -0,0 +1,208 @@
+
Node Visual Style
+
Which processor node style fits our design system best? Think MuleSoft / TIBCO BW5 but adapted to our warm parchment theme.
+
+
+
+
+
+
+
+
+
A: Icon Sidebar Blocks
+
MuleSoft-style: colored icon strip on the left, label + detail on the right. Color encodes node type. Compound nodes (choice, split) use dashed containers.
+
+
+
+
+
+
+
+
+
+
B: Rounded Pills
+
Softer, more modern look with pill-shaped nodes and circular icons. Lighter feel. Compounds still use dashed containers.
+
+
+
+
+
+
+
+
+
+
C: Top-Bar Cards
+
TIBCO BW5-inspired: white cards with colored top accent bar. Clean, professional, card-like. Compound nodes get a full colored header bar with white title text.
+
+
+
\ No newline at end of file
diff --git a/.superpowers/brainstorm/10188-1774613058/waiting.html b/.superpowers/brainstorm/10188-1774613058/waiting.html
new file mode 100644
index 00000000..f92c257a
--- /dev/null
+++ b/.superpowers/brainstorm/10188-1774613058/waiting.html
@@ -0,0 +1,3 @@
+
+
Continuing in terminal...
+
\ No newline at end of file
diff --git a/.superpowers/brainstorm/14618-1774629192/.server-stopped b/.superpowers/brainstorm/14618-1774629192/.server-stopped
new file mode 100644
index 00000000..27a3aaad
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/.server-stopped
@@ -0,0 +1 @@
+{"reason":"idle timeout","timestamp":1774632733532}
diff --git a/.superpowers/brainstorm/14618-1774629192/.server.pid b/.superpowers/brainstorm/14618-1774629192/.server.pid
new file mode 100644
index 00000000..cc71cea6
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/.server.pid
@@ -0,0 +1 @@
+14618
diff --git a/.superpowers/brainstorm/14618-1774629192/detail-panel-tabs.html b/.superpowers/brainstorm/14618-1774629192/detail-panel-tabs.html
new file mode 100644
index 00000000..2938d77f
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/detail-panel-tabs.html
@@ -0,0 +1,287 @@
+
Detail Panel: Tab Designs
+
Bottom panel content when a processor node is selected
Independent per compound — outer loop at iteration 2, inner split at branch 1
+
Overlay updates per-compound — stepping the loop re-renders its children's execution data for that iteration
+
CHOICE shows which branch was taken — no stepper, just highlights the taken branch
+
Keyboard — when a compound is focused/hovered, left/right arrow keys step through iterations
+
Detail panel syncs — selecting a processor inside a loop shows that iteration's data
+
+
diff --git a/.superpowers/brainstorm/14618-1774629192/layout-overview.html b/.superpowers/brainstorm/14618-1774629192/layout-overview.html
new file mode 100644
index 00000000..ae786a80
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/layout-overview.html
@@ -0,0 +1,166 @@
+
Execution Overlay: Page Layout
+
How should the diagram + execution details be arranged?
+
+
+
+
+
+
+
+
+
+
+
DIAGRAM
+
+
+
from:jms
+
+
log
+
+
bean
+
+
to:http
+
+
+
100%
+
+
Loop 2/5
+
+
+
+
+
+
+ Input
+ Output
+ Headers
+ Error
+ Timeline
+
+
+
{"orderId": "ORD-1234",
+
"product": "Widget A",
+
"quantity": 5}
+
+
+
+
+
+
+
A: Top/Bottom Split (IDE Style)
+
Diagram on top, tabbed detail panel below. Resizable splitter between them. Maximizes diagram width. Tabs: Input, Output, Headers, Error, Timeline.
+
+
Pros
Full diagram width
Familiar IDE pattern
Detail panel always visible
+
Cons
Vertical space shared
Diagram shrinks on small screens
+
+
+
+
+
+
+
+
+
+
+
+
DIAGRAM
+
+
from:jms
+
+
log
+
+
bean
+
+
100%
+
+
+
+
+
+
log (processor-3)
+
COMPLETED - 12ms
+
+
+ Input
+ Output
+ Headers
+
+
+
+
{"orderId": "ORD-1234",
+
"product": "Widget A",
+
"quantity": 5,
+
"price": 29.99}
+
+
+
+
+
+
+
B: Left/Right Split
+
Diagram on left, collapsible detail panel on right. Slide-in when node selected. Diagram keeps full height.
+
+
Pros
Full diagram height
Panel can collapse
Good for wide screens
+
Cons
Steals diagram width
Tight on narrow screens
+
+
+
+
+
+
+
+
+
+
+
+
DIAGRAM
+
+
from:jms
+
+
log
+
+
bean
+
+
to:http
+
+
100%
+
+
+
+
+
+
+
Processors
+
+
from:jms - 2ms
+
log - 12ms
+
bean - FAILED
+
to:http - skipped
+
+
+
+
+
+ Input
+ Output
+ Headers
+
+
+
{"orderId": "ORD-1234",
+
"product": "Widget A"}
+
+
+
+
+
+
+
+
C: Top/Bottom with Processor List
+
Diagram on top, bottom split into processor list (left) + detail tabs (right). Clicking processor in list or diagram syncs selection. Most information density.
+
+
Pros
Processor list as navigation
Full diagram width
Maximum information density
+
Cons
More complex layout
May feel crowded
+
+
+
+
diff --git a/.superpowers/brainstorm/14618-1774629192/overlay-intensity.html b/.superpowers/brainstorm/14618-1774629192/overlay-intensity.html
new file mode 100644
index 00000000..43e3cd25
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/overlay-intensity.html
@@ -0,0 +1,190 @@
+
Execution Overlay: Visual Intensity Comparison
+
How strong should the overlay tinting be?
+
+
+
+
+
Current: Subtle (border only)
+
+
+
+
+ Completed
+
+
+
+
from:jms:orders
+
ENDPOINT
+
+
2ms
+
+
+
+
+ Failed
+
+
+
+
bean:validate
+
BEAN
+
+
120ms
+
!
+
+
+
+
+ Skipped
+
+
+
+
to:http:api
+
TO
+
+
+
+
+
+
+
+
+
+
Proposed: Tinted backgrounds
+
+
+
+
+ Completed
+
+
+
+
from:jms:orders
+
ENDPOINT
+
+
2ms
+
+
+
+
+ Failed
+
+
+
+
bean:validate
+
FAILED
+
+
120ms
+
!
+
+
+
+
+ Skipped
+
+
+
+
to:http:api
+
TO
+
+
+
+
+
+
+
+
+
Full Flow Comparison
+
Same route, tinted version — see how it reads at a glance
+
+
+
Tinted overlay on a full route
+
+
+
+
+
+
+
+
+
from:jms:orders
+
ENDPOINT
+
+
+
2ms
+
+
+
+
+
+
+
+
+
+
log:incoming
+
LOG
+
+
+
5ms
+
+
+
+
+
+
+
+
+
+
setHeader:type
+
SET_HEADER
+
+
+
1ms
+
+
+
+
+
+
+
+
+
+
bean:validate
+
FAILED
+
+
+
120ms
+
!
+
+
+
+
+
+
+
+
+
+
to:http:api
+
TO
+
+
+
+
+
+
+
+
+
+
+
+
to:jms:result
+
TO
+
+
+
+
+
+
+ Note: Edges between executed nodes turn green. Edges leading to skipped nodes become dashed gray.
+
+
+
diff --git a/.superpowers/brainstorm/14618-1774629192/overlay-with-markers.html b/.superpowers/brainstorm/14618-1774629192/overlay-with-markers.html
new file mode 100644
index 00000000..5d832e6c
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/overlay-with-markers.html
@@ -0,0 +1,159 @@
+
Execution Overlay: Success + Error Markers
+
Every executed node gets a status badge — green check or red exclamation
+
+
+
Full route with status markers
+
+
+
+
+
+
+
+
+
from:jms:orders
+
ENDPOINT
+
+
+
2ms
+
+
✓
+
+
+
+
+
+
+
+
+
+
log:incoming
+
LOG
+
+
+
5ms
+
✓
+
+
+
+
+
+
+
+
+
+
setHeader:type
+
SET_HEADER
+
+
+
1ms
+
✓
+
+
+
+
+
+
+
+
+
+
bean:validate
+
FAILED
+
+
+
120ms
+
+
!
+
+
+
+
+
+
+
+
+
+
to:http:api
+
TO
+
+
+
+
+
+
+
+
+
+
+
+
to:jms:result
+
TO
+
+
+
+
+
+
+
+
Node State Legend
+
+
+
+
+
+
✓
+
5ms
+
+
+
Completed
+
Green tint + border + check badge + duration
+
+
+
+
+
+
+
!
+
120ms
+
+
+
Failed
+
Red tint + border + ! badge + duration
+
+
+
+
+
+
+
!
+
↴
+
85ms
+
+
+
Sub-route Failure
+
Same as failed + drill-down arrow
+
+
+
+
+
+
+
+
+
Skipped
+
35% opacity, no badge, no duration
+
+
+
+
+
Edge States
+
+
+
+
Traversed — green, solid
+
+
+
+
Not traversed — gray, dashed
+
+
diff --git a/.superpowers/brainstorm/14618-1774629192/waiting.html b/.superpowers/brainstorm/14618-1774629192/waiting.html
new file mode 100644
index 00000000..ef076525
--- /dev/null
+++ b/.superpowers/brainstorm/14618-1774629192/waiting.html
@@ -0,0 +1,3 @@
+
+
Continuing in terminal...
+
diff --git a/.superpowers/brainstorm/2048-1774541143/content/appconfig-detail-new-sections.html b/.superpowers/brainstorm/2048-1774541143/content/appconfig-detail-new-sections.html
new file mode 100644
index 00000000..877b9891
--- /dev/null
+++ b/.superpowers/brainstorm/2048-1774541143/content/appconfig-detail-new-sections.html
@@ -0,0 +1,181 @@
+
AppConfigDetailPage — New Sections
+
Taps overview, route recording map, and compress success toggle added to existing config page
+
+
+
AppConfigDetailPage — Full Layout (scrollable)
+
+
+
+
+ ←
+ order-service
+ v14 · Updated 3 min ago
+
+ ✎
+
+
+
+
+
+
Logging
+
+
+
Log Forwarding Level
+ INFO
+
+
+
+
+
+
+
Observability
+
+
+
Engine Level
+ REGULAR
+
+
+
Payload Capture
+ BOTH
+
+
+
Metrics
+ ON
+
+
+
Sampling Rate
+ 1.0
+
+
+
Compress Success
+ OFF
+
+
+
+
+
+
+
Traced Processors
+
2 processors with custom capture modes
+
+
+
+
Processor ID
+
Capture Mode
+
+
+
+
+
unmarshal1
+
BOTH
+
+
+
toDatabase
+
INPUT
+
+
+
+
+
+
+
+
+
Data Extraction Taps
+ 3 taps · manage on route pages
+
+
+
+
+
Attribute
+
Processor
+
Expression
+
Language
+
Enabled
+
+
+
+
+
orderId
+
unmarshal1
+
${body.orderId}
+
simple
+
✓
+
+
+
customerId
+
unmarshal1
+
${body.customer.id}
+
simple
+
✓
+
+
+
orderTotal
+
enrichPrice
+
$.total
+
jsonpath
+
✗
+
+
+
+
+
+
+
+
+
Route Recording
+ 4 of 5 routes recording
+
+
+
+
+
Route
+
Recording
+
+
+
+
+
processOrder
+
+
+
+
+
+
+
+
processPayment
+
+
+
+
+
+
+
+
sendNotification
+
+
+
+
+
+
+
+
handleRefund
+
+
+
+
+
+
+
+
healthCheck
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/brainstorm/2048-1774541143/content/appconfig-final-layout.html b/.superpowers/brainstorm/2048-1774541143/content/appconfig-final-layout.html
new file mode 100644
index 00000000..b35590a2
--- /dev/null
+++ b/.superpowers/brainstorm/2048-1774541143/content/appconfig-final-layout.html
@@ -0,0 +1,149 @@
+
AppConfigDetailPage — Final Layout
+
Three clean sections: Settings, Traces & Taps, Route Recording
}>
+ {children}
+
+ );
+}
+
+export const router = createBrowserRouter([
+ { path: '/login', element: },
+ { path: '/oidc/callback', element: },
+ {
+ element: ,
+ children: [
+ {
+ element: ,
+ children: [
+ // Default redirect
+ { index: true, element: },
+
+ // Exchanges tab
+ { path: 'exchanges', element: },
+ { path: 'exchanges/:appId', element: },
+ { path: 'exchanges/:appId/:routeId', element: },
+ { path: 'exchanges/:appId/:routeId/:exchangeId', element: },
+
+ // Dashboard tab
+ { path: 'dashboard', element: },
+ { path: 'dashboard/:appId', element: },
+ { path: 'dashboard/:appId/:routeId', element: },
+
+ // Runtime tab
+ { path: 'runtime', element: },
+ { path: 'runtime/:appId', element: },
+ { path: 'runtime/:appId/:instanceId', element: },
+
+ // Legacy redirects (sidebar uses /apps/... and /agents/... paths)
+ { path: 'apps', element: },
+ { path: 'apps/:appId', element: },
+ { path: 'apps/:appId/:routeId', element: },
+ { path: 'agents', element: },
+ { path: 'agents/:appId', element: },
+ { path: 'agents/:appId/:instanceId', element: },
+
+ // Old exchange detail redirect
+ { path: 'exchanges-old/:id', element: },
+
+ // Admin (unchanged)
+ {
+ path: 'admin',
+ element: ,
+ children: [
+ { index: true, element: },
+ { path: 'rbac', element: },
+ { path: 'audit', element: },
+ { path: 'oidc', element: },
+ { path: 'appconfig', element: },
+ { path: 'database', element: },
+ { path: 'opensearch', element: },
+ ],
+ },
+ { path: 'api-docs', element: },
+ ],
+ },
+ ],
+ },
+]);
+
+// Legacy redirect components — translate old sidebar paths to current tab
+// (useParams is already imported at the top of this file)
+
+function LegacyAppRedirect() {
+ const { appId, routeId } = useParams<{ appId: string; routeId?: string }>();
+ const path = routeId ? `/exchanges/${appId}/${routeId}` : `/exchanges/${appId}`;
+ return ;
+}
+
+function LegacyAgentRedirect() {
+ const { appId, instanceId } = useParams<{ appId: string; instanceId?: string }>();
+ const path = instanceId ? `/runtime/${appId}/${instanceId}` : `/runtime/${appId}`;
+ return ;
+}
+```
+
+The `useParams` import from `react-router` is already at the top alongside the other router imports. The legacy redirects ensure the Sidebar's hardcoded `/apps/...` and `/agents/...` Links still work (they redirect to the Exchanges tab by default).
+
+- [ ] **Step 2: Verify build**
+
+Run: `cd ui && npx tsc --noEmit`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add ui/src/router.tsx
+git commit -m "feat(ui): restructure router for tab-based navigation with legacy redirects"
+```
+
+---
+
+## Task 9: Update LayoutShell
+
+Wire ContentTabs, ScopeTrail, sidebar interception, and remove agents from sidebar data.
+
+**Files:**
+- Modify: `ui/src/components/LayoutShell.tsx`
+
+- [ ] **Step 1: Update imports**
+
+Add these imports to the top of `LayoutShell.tsx`:
+
+```typescript
+import { ContentTabs } from './ContentTabs';
+import { ScopeTrail } from './ScopeTrail';
+import { useScope } from '../hooks/useScope';
+```
+
+- [ ] **Step 2: Remove agents from sidebar data**
+
+In the `sidebarApps` useMemo (around line 106), change the agents mapping to always return an empty array:
+
+Replace:
+```typescript
+ agents: (app.agents || []).map((a: any) => ({
+ id: a.id,
+ name: a.name,
+ status: a.status as 'live' | 'stale' | 'dead',
+ tps: a.tps,
+ })),
+```
+
+With:
+```typescript
+ agents: [],
+```
+
+- [ ] **Step 3: Add scope + sidebar interception to LayoutContent**
+
+At the top of the `LayoutContent` function (after existing hooks around line 92), add:
+
+```typescript
+ const { scope, setTab, clearScope } = useScope();
+```
+
+Add the sidebar click interceptor function (before the return statement):
+
+```typescript
+ // Intercept Sidebar's internal navigation to re-route through current tab
+ const handleSidebarClick = useCallback((e: React.MouseEvent) => {
+ const anchor = (e.target as HTMLElement).closest('a[href]');
+ if (!anchor) return;
+ const href = anchor.getAttribute('href') || '';
+
+ // Intercept /apps/:appId and /apps/:appId/:routeId links
+ const appMatch = href.match(/^\/apps\/([^/]+)(?:\/(.+))?$/);
+ if (appMatch) {
+ e.preventDefault();
+ const [, sAppId, sRouteId] = appMatch;
+ navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`);
+ return;
+ }
+
+ // Intercept /agents/* links — redirect to runtime tab
+ const agentMatch = href.match(/^\/agents\/([^/]+)(?:\/(.+))?$/);
+ if (agentMatch) {
+ e.preventDefault();
+ const [, sAppId, sInstanceId] = agentMatch;
+ navigate(sInstanceId ? `/runtime/${sAppId}/${sInstanceId}` : `/runtime/${sAppId}`);
+ }
+ }, [navigate, scope.tab]);
+```
+
+- [ ] **Step 4: Replace breadcrumbs with ScopeTrail**
+
+Replace the existing `breadcrumb` useMemo block (lines 168-188) with:
+
+```typescript
+ // Breadcrumb is now the ScopeTrail — built from scope, not URL path
+ // Keep the old breadcrumb generation for admin pages only
+ const isAdminPage = location.pathname.startsWith('/admin');
+ const breadcrumb = useMemo(() => {
+ if (!isAdminPage) return []; // ScopeTrail handles non-admin breadcrumbs
+ const LABELS: Record = {
+ admin: 'Admin',
+ rbac: 'Users & Roles',
+ audit: 'Audit Log',
+ oidc: 'OIDC',
+ database: 'Database',
+ opensearch: 'OpenSearch',
+ appconfig: 'App Config',
+ };
+ const parts = location.pathname.split('/').filter(Boolean);
+ return parts.map((part, i) => ({
+ label: LABELS[part] ?? part,
+ ...(i < parts.length - 1 ? { href: '/' + parts.slice(0, i + 1).join('/') } : {}),
+ }));
+ }, [location.pathname, isAdminPage]);
+```
+
+- [ ] **Step 5: Update CommandPalette submit handler**
+
+Replace the `handlePaletteSubmit` callback (around line 202) so it uses the Exchanges tab path:
+
+```typescript
+ const handlePaletteSubmit = useCallback((query: string) => {
+ // Full-text search: navigate to Exchanges tab with text param
+ const baseParts = [`/exchanges`];
+ if (scope.appId) baseParts.push(scope.appId);
+ if (scope.routeId) baseParts.push(scope.routeId);
+ navigate(`${baseParts.join('/')}?text=${encodeURIComponent(query)}`);
+ }, [navigate, scope.appId, scope.routeId]);
+```
+
+- [ ] **Step 6: Update the search result paths in buildSearchData**
+
+In `buildSearchData` function, update the `path` values for apps and routes:
+
+Replace:
+```typescript
+ path: `/apps/${app.appId}`,
+```
+With:
+```typescript
+ path: `/exchanges/${app.appId}`,
+```
+
+Replace:
+```typescript
+ path: `/apps/${app.appId}/${route.routeId}`,
+```
+With:
+```typescript
+ path: `/exchanges/${app.appId}/${route.routeId}`,
+```
+
+Replace:
+```typescript
+ path: `/agents/${agent.application}/${agent.id}`,
+```
+With:
+```typescript
+ path: `/runtime/${agent.application}/${agent.id}`,
+```
+
+- [ ] **Step 7: Update exchange search result paths**
+
+In the `searchData` useMemo (around line 132), update the exchange path:
+
+Replace:
+```typescript
+ path: `/exchanges/${e.executionId}`,
+```
+
+With a path that includes the app and route for proper 3-column view:
+
+```typescript
+ path: `/exchanges/${e.applicationName ?? ''}/${e.routeId}/${e.executionId}`,
+```
+
+Do the same for `attributeItems` path.
+
+- [ ] **Step 8: Update the JSX return**
+
+Replace the return block of `LayoutContent` with:
+
+```typescript
+ return (
+
+
+
+ }
+ >
+
+ setPaletteOpen(false)}
+ onOpen={() => setPaletteOpen(true)}
+ onSelect={handlePaletteSelect}
+ onSubmit={handlePaletteSubmit}
+ onQueryChange={setPaletteQuery}
+ data={searchData}
+ />
+
+ {/* Content tabs + scope trail — only for main content, not admin */}
+ {!isAdminPage && (
+ <>
+
+
+ navigate(path)} />
+
+ >
+ )}
+
+
+
+
+
+ );
+```
+
+- [ ] **Step 9: Verify build**
+
+Run: `cd ui && npx tsc --noEmit`
+
+Fix any type errors. Then run the dev server:
+
+Run: `cd ui && npm run dev`
+
+Visually verify:
+- Tab bar appears below TopBar with Exchanges | Dashboard | Runtime
+- Scope trail shows "All Applications" by default
+- Clicking an app in sidebar scopes to that app and stays on current tab
+- Clicking a route transitions to 3-column layout (Exchanges tab)
+- Clicking Dashboard or Runtime tabs shows the correct content
+- Admin pages still work without tabs/scope trail
+
+- [ ] **Step 10: Commit**
+
+```bash
+git add ui/src/components/LayoutShell.tsx
+git commit -m "feat(ui): integrate ContentTabs, ScopeTrail, and sidebar scope interception"
+```
+
+---
+
+## Task 10: Final cleanup and verification
+
+**Files:**
+- Modify: `ui/src/pages/Dashboard/Dashboard.tsx` (update inspect link path)
+
+- [ ] **Step 1: Update Dashboard inspect link**
+
+In `Dashboard.tsx`, the inspect button navigates to `/exchanges/:id` (the old exchange detail page). Update it to stay in the current scope. Find the inspect column render (around line 347):
+
+Replace:
+```typescript
+ navigate(`/exchanges/${row.executionId}`)
+```
+
+With:
+```typescript
+ navigate(`/exchanges/${row.applicationName}/${row.routeId}/${row.executionId}`)
+```
+
+This navigates to the 3-column view with the exchange pre-selected.
+
+- [ ] **Step 2: Update Dashboard "Open full details" link**
+
+In the detail panel section (around line 465):
+
+Replace:
+```typescript
+ onClick={() => navigate(`/exchanges/${detail.executionId}`)}
+```
+
+With:
+```typescript
+ onClick={() => navigate(`/exchanges/${detail.applicationName}/${detail.routeId}/${detail.executionId}`)}
+```
+
+- [ ] **Step 3: Verify all navigation flows**
+
+Run the dev server and verify:
+
+```bash
+cd ui && npm run dev
+```
+
+Verification checklist:
+1. `/exchanges` — Shows full-width exchange table with KPI strip
+2. Click app in sidebar — URL updates to `/exchanges/:appId`, table filters
+3. Click route in sidebar — URL updates to `/exchanges/:appId/:routeId`, 3-column layout appears
+4. Click exchange in left list — Right panel shows exchange header + diagram + details
+5. Click "Dashboard" tab — URL changes to `/dashboard/:appId/:routeId` (scope preserved)
+6. Dashboard tab shows RoutesMetrics (no route) or RouteDetail (with route)
+7. Click "Runtime" tab — URL changes to `/runtime`, shows AgentHealth
+8. Click agent in Runtime content — Shows AgentInstance detail
+9. Scope trail segments are clickable and navigate correctly
+10. Cmd+K search works, results navigate to correct new URLs
+11. Admin pages (gear icon or /admin) still work with breadcrumbs, no tabs
+12. Sidebar shows apps with routes only, no agents section
+
+- [ ] **Step 4: Verify production build**
+
+Run: `cd ui && npm run build`
+Expected: Clean build with no errors
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add -A
+git commit -m "feat(ui): complete navigation redesign - tab-based layout with scope filtering
+
+Redesigns navigation from page-based routing to scope-based model:
+- Three content tabs: Exchanges, Dashboard, Runtime
+- Sidebar simplified to app/route hierarchy (scope filter)
+- Scope trail replaces breadcrumbs
+- Exchanges tab: full-width table or 3-column layout with diagram
+- Legacy URL redirects for backward compatibility"
+```
+
+---
+
+## Known Limitations and Future Work
+
+1. **Sidebar design system update needed:** The click interception via event delegation is a workaround. A proper `onNavigate` prop should be added to the design system's `Sidebar` component.
+
+2. **Dashboard tab content:** The analytics/dashboard content is deferred per spec. Currently wraps existing RoutesMetrics and RouteDetail pages.
+
+3. **ExchangeDetail page:** The old full-page ExchangeDetail at `/exchanges/:id` is replaced by the inline 3-column view. The ExchangeDetail component file is not deleted — it may be useful for reference. Clean up when confident the new view covers all use cases.
+
+4. **Exchange header:** Uses inline styles for brevity. Extract to CSS module if it grows.
+
+5. **KPI hero per tab:** Currently only the Exchanges tab has a KPI strip (from Dashboard). The Dashboard and Runtime tabs will get their own KPI strips in future iterations.
+
+6. **Application log and replay:** These features from ExchangeDetail are accessible through the ExecutionDiagram's detail panel tabs but not directly in the exchange header. A future iteration could add log/replay buttons.
diff --git a/openapi.json b/openapi.json
new file mode 100644
index 00000000..2f1f050e
--- /dev/null
+++ b/openapi.json
@@ -0,0 +1 @@
+{"openapi":"3.1.0","info":{"title":"Cameleer3 Server API","version":"1.0"},"servers":[{"url":"/api/v1","description":"Relative"}],"security":[{"bearer":[]}],"tags":[{"name":"Agent Events","description":"Agent lifecycle event log"},{"name":"Database Admin","description":"Database monitoring and management (ADMIN only)"},{"name":"Threshold Admin","description":"Monitoring threshold configuration (ADMIN only)"},{"name":"Agent Commands","description":"Command push endpoints for agent communication"},{"name":"User Admin","description":"User management (ADMIN only)"},{"name":"Agent Management","description":"Agent registration and lifecycle endpoints"},{"name":"Authentication","description":"Login and token refresh endpoints"},{"name":"Role Admin","description":"Role management (ADMIN only)"},{"name":"RBAC Stats","description":"RBAC statistics (ADMIN only)"},{"name":"OIDC Config Admin","description":"OIDC provider configuration (ADMIN only)"},{"name":"Route Metrics","description":"Route performance metrics"},{"name":"Search","description":"Transaction search endpoints"},{"name":"Agent SSE","description":"Server-Sent Events endpoint for agent communication"},{"name":"Ingestion","description":"Data ingestion endpoints"},{"name":"Audit Log","description":"Audit log viewer (ADMIN only)"},{"name":"Application Logs","description":"Query application logs stored in OpenSearch"},{"name":"Group Admin","description":"Group management (ADMIN only)"},{"name":"Diagrams","description":"Diagram rendering endpoints"},{"name":"OpenSearch Admin","description":"OpenSearch monitoring and management (ADMIN only)"},{"name":"Application Config","description":"Per-application observability configuration"},{"name":"Detail","description":"Execution detail and processor snapshot endpoints"},{"name":"Route Catalog","description":"Route catalog and discovery"}],"paths":{"/config/{application}":{"get":{"tags":["Application Config"],"summary":"Get application config","description":"Returns the current configuration for an application. Returns defaults if none stored.","operationId":"getConfig","parameters":[{"name":"application","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Config returned","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApplicationConfig"}}}}}},"put":{"tags":["Application Config"],"summary":"Update application config","description":"Saves config and pushes CONFIG_UPDATE to all LIVE agents of this application","operationId":"updateConfig","parameters":[{"name":"application","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationConfig"}}},"required":true},"responses":{"200":{"description":"Config saved and pushed","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApplicationConfig"}}}}}}},"/admin/users/{userId}":{"get":{"tags":["User Admin"],"summary":"Get user by ID with RBAC detail","operationId":"getUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDetail"}}}},"404":{"description":"User not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDetail"}}}}}},"put":{"tags":["User Admin"],"summary":"Update user display name or email","operationId":"updateUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"200":{"description":"User updated"},"404":{"description":"User not found"}}},"delete":{"tags":["User Admin"],"summary":"Delete user","operationId":"deleteUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"User deleted"}}}},"/admin/thresholds":{"get":{"tags":["Threshold Admin"],"summary":"Get current threshold configuration","operationId":"getThresholds","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ThresholdConfig"}}}}}},"put":{"tags":["Threshold Admin"],"summary":"Update threshold configuration","operationId":"updateThresholds","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThresholdConfigRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ThresholdConfig"}}}}}}},"/admin/roles/{id}":{"get":{"tags":["Role Admin"],"summary":"Get role by ID with effective principals","operationId":"getRole","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Role found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RoleDetail"}}}},"404":{"description":"Role not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RoleDetail"}}}}}},"put":{"tags":["Role Admin"],"summary":"Update a custom role","operationId":"updateRole","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRoleRequest"}}},"required":true},"responses":{"200":{"description":"Role updated"},"403":{"description":"Cannot modify system role"},"404":{"description":"Role not found"}}},"delete":{"tags":["Role Admin"],"summary":"Delete a custom role","operationId":"deleteRole","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Role deleted"},"403":{"description":"Cannot delete system role"},"404":{"description":"Role not found"}}}},"/admin/oidc":{"get":{"tags":["OIDC Config Admin"],"summary":"Get OIDC configuration","operationId":"getConfig_1","responses":{"200":{"description":"Current OIDC configuration (client_secret masked)","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OidcAdminConfigResponse"}}}}}},"put":{"tags":["OIDC Config Admin"],"summary":"Save OIDC configuration","operationId":"saveConfig","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OidcAdminConfigRequest"}}},"required":true},"responses":{"200":{"description":"Configuration saved","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OidcAdminConfigResponse"}}}},"400":{"description":"Invalid configuration","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["OIDC Config Admin"],"summary":"Delete OIDC configuration","operationId":"deleteConfig","responses":{"204":{"description":"Configuration deleted"}}}},"/admin/groups/{id}":{"get":{"tags":["Group Admin"],"summary":"Get group by ID with effective roles","operationId":"getGroup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Group found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GroupDetail"}}}},"404":{"description":"Group not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/GroupDetail"}}}}}},"put":{"tags":["Group Admin"],"summary":"Update group name or parent","operationId":"updateGroup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupRequest"}}},"required":true},"responses":{"200":{"description":"Group updated"},"404":{"description":"Group not found"},"409":{"description":"Cycle detected in group hierarchy"}}},"delete":{"tags":["Group Admin"],"summary":"Delete group","operationId":"deleteGroup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Group deleted"},"404":{"description":"Group not found"}}}},"/search/executions":{"get":{"tags":["Search"],"summary":"Search executions with basic filters","operationId":"searchGet","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"timeFrom","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"timeTo","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"correlationId","in":"query","required":false,"schema":{"type":"string"}},{"name":"text","in":"query","required":false,"schema":{"type":"string"}},{"name":"routeId","in":"query","required":false,"schema":{"type":"string"}},{"name":"agentId","in":"query","required":false,"schema":{"type":"string"}},{"name":"processorType","in":"query","required":false,"schema":{"type":"string"}},{"name":"application","in":"query","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}},{"name":"sortField","in":"query","required":false,"schema":{"type":"string"}},{"name":"sortDir","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SearchResultExecutionSummary"}}}}}},"post":{"tags":["Search"],"summary":"Advanced search with all filters","operationId":"searchPost","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SearchResultExecutionSummary"}}}}}}},"/data/metrics":{"post":{"tags":["Ingestion"],"summary":"Ingest agent metrics","description":"Accepts an array of MetricsSnapshot objects","operationId":"ingestMetrics","requestBody":{"content":{"application/json":{"schema":{"type":"string"}}},"required":true},"responses":{"202":{"description":"Data accepted for processing"},"400":{"description":"Invalid payload"},"503":{"description":"Buffer full, retry later"}}}},"/data/logs":{"post":{"tags":["Ingestion"],"summary":"Ingest application log entries","description":"Accepts a batch of log entries from an agent. Entries are indexed in OpenSearch.","operationId":"ingestLogs","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogBatch"}}},"required":true},"responses":{"202":{"description":"Logs accepted for indexing"}}}},"/data/executions":{"post":{"tags":["Ingestion"],"summary":"Ingest route execution data","description":"Accepts a single RouteExecution or an array of RouteExecutions","operationId":"ingestExecutions","requestBody":{"content":{"application/json":{"schema":{"type":"string"}}},"required":true},"responses":{"202":{"description":"Data accepted for processing"}}}},"/data/diagrams":{"post":{"tags":["Ingestion"],"summary":"Ingest route diagram data","description":"Accepts a single RouteGraph or an array of RouteGraphs","operationId":"ingestDiagrams","requestBody":{"content":{"application/json":{"schema":{"type":"string"}}},"required":true},"responses":{"202":{"description":"Data accepted for processing"}}}},"/config/{application}/test-expression":{"post":{"tags":["Application Config"],"summary":"Test a tap expression against sample data via a live agent","operationId":"testExpression","parameters":[{"name":"application","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestExpressionRequest"}}},"required":true},"responses":{"200":{"description":"Expression evaluated successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TestExpressionResponse"}}}},"404":{"description":"No live agent available for this application","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TestExpressionResponse"}}}},"504":{"description":"Agent did not respond in time","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TestExpressionResponse"}}}}}}},"/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh access token","operationId":"refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Token refreshed","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"Invalid refresh token","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/auth/oidc/callback":{"post":{"tags":["Authentication"],"summary":"Exchange OIDC authorization code for JWTs","operationId":"callback","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallbackRequest"}}},"required":true},"responses":{"200":{"description":"Authentication successful","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"OIDC authentication failed","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Account not provisioned","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"OIDC not configured or disabled","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}}}}},"/auth/login":{"post":{"tags":["Authentication"],"summary":"Login with local credentials","operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Login successful","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"Invalid credentials","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/agents/{id}/refresh":{"post":{"tags":["Agent Management"],"summary":"Refresh access token","description":"Issues a new access JWT from a valid refresh token","operationId":"refresh_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRefreshRequest"}}},"required":true},"responses":{"200":{"description":"New access token issued","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AgentRefreshResponse"}}}},"401":{"description":"Invalid or expired refresh token","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AgentRefreshResponse"}}}},"404":{"description":"Agent not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AgentRefreshResponse"}}}}}}},"/agents/{id}/heartbeat":{"post":{"tags":["Agent Management"],"summary":"Agent heartbeat ping","description":"Updates the agent's last heartbeat timestamp","operationId":"heartbeat","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Heartbeat accepted"},"404":{"description":"Agent not registered"}}}},"/agents/{id}/commands":{"post":{"tags":["Agent Commands"],"summary":"Send command to a specific agent","description":"Sends a command to the specified agent via SSE","operationId":"sendCommand","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandRequest"}}},"required":true},"responses":{"202":{"description":"Command accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandSingleResponse"}}}},"400":{"description":"Invalid command payload","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandSingleResponse"}}}},"404":{"description":"Agent not registered","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandSingleResponse"}}}}}}},"/agents/{id}/commands/{commandId}/ack":{"post":{"tags":["Agent Commands"],"summary":"Acknowledge command receipt","description":"Agent acknowledges that it has received and processed a command, with result status and message","operationId":"acknowledgeCommand","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"commandId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandAckRequest"}}}},"responses":{"200":{"description":"Command acknowledged"},"404":{"description":"Command not found"}}}},"/agents/register":{"post":{"tags":["Agent Management"],"summary":"Register an agent","description":"Registers a new agent or re-registers an existing one. Requires bootstrap token in Authorization header.","operationId":"register","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRegistrationRequest"}}},"required":true},"responses":{"200":{"description":"Agent registered successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AgentRegistrationResponse"}}}},"400":{"description":"Invalid registration payload","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bootstrap token","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AgentRegistrationResponse"}}}}}}},"/agents/groups/{group}/commands":{"post":{"tags":["Agent Commands"],"summary":"Send command to all agents in a group","description":"Sends a command to all LIVE agents in the specified group","operationId":"sendGroupCommand","parameters":[{"name":"group","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandRequest"}}},"required":true},"responses":{"202":{"description":"Commands accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandBroadcastResponse"}}}},"400":{"description":"Invalid command payload","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandBroadcastResponse"}}}}}}},"/agents/commands":{"post":{"tags":["Agent Commands"],"summary":"Broadcast command to all live agents","description":"Sends a command to all agents currently in LIVE state","operationId":"broadcastCommand","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandRequest"}}},"required":true},"responses":{"202":{"description":"Commands accepted","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandBroadcastResponse"}}}},"400":{"description":"Invalid command payload","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CommandBroadcastResponse"}}}}}}},"/admin/users":{"get":{"tags":["User Admin"],"summary":"List all users with RBAC detail","operationId":"listUsers","responses":{"200":{"description":"User list returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserDetail"}}}}}}},"post":{"tags":["User Admin"],"summary":"Create a local user","operationId":"createUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"200":{"description":"User created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserDetail"}}}}}}},"/admin/users/{userId}/roles/{roleId}":{"post":{"tags":["User Admin"],"summary":"Assign a role to a user","operationId":"assignRoleToUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"roleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Role assigned"},"404":{"description":"User or role not found"}}},"delete":{"tags":["User Admin"],"summary":"Remove a role from a user","operationId":"removeRoleFromUser","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"roleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Role removed"}}}},"/admin/users/{userId}/password":{"post":{"tags":["User Admin"],"summary":"Reset user password","operationId":"resetPassword","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordRequest"}}},"required":true},"responses":{"204":{"description":"Password reset"}}}},"/admin/users/{userId}/groups/{groupId}":{"post":{"tags":["User Admin"],"summary":"Add a user to a group","operationId":"addUserToGroup","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"User added to group"}}},"delete":{"tags":["User Admin"],"summary":"Remove a user from a group","operationId":"removeUserFromGroup","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"User removed from group"}}}},"/admin/roles":{"get":{"tags":["Role Admin"],"summary":"List all roles (system and custom)","operationId":"listRoles","responses":{"200":{"description":"Role list returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RoleDetail"}}}}}}},"post":{"tags":["Role Admin"],"summary":"Create a custom role","operationId":"createRole","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRoleRequest"}}},"required":true},"responses":{"200":{"description":"Role created","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string","format":"uuid"}}}}}}}},"/admin/oidc/test":{"post":{"tags":["OIDC Config Admin"],"summary":"Test OIDC provider connectivity","operationId":"testConnection","responses":{"200":{"description":"Provider reachable","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OidcTestResult"}}}},"400":{"description":"Provider unreachable or misconfigured","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/admin/groups":{"get":{"tags":["Group Admin"],"summary":"List all groups with hierarchy and effective roles","operationId":"listGroups","responses":{"200":{"description":"Group list returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GroupDetail"}}}}}}},"post":{"tags":["Group Admin"],"summary":"Create a new group","operationId":"createGroup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroupRequest"}}},"required":true},"responses":{"200":{"description":"Group created","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string","format":"uuid"}}}}}}}},"/admin/groups/{id}/roles/{roleId}":{"post":{"tags":["Group Admin"],"summary":"Assign a role to a group","operationId":"assignRoleToGroup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"roleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Role assigned to group"},"404":{"description":"Group not found"}}},"delete":{"tags":["Group Admin"],"summary":"Remove a role from a group","operationId":"removeRoleFromGroup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"roleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Role removed from group"},"404":{"description":"Group not found"}}}},"/admin/database/queries/{pid}/kill":{"post":{"tags":["Database Admin"],"summary":"Terminate a query by PID","operationId":"killQuery","parameters":[{"name":"pid","in":"path","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK"}}}},"/search/stats":{"get":{"tags":["Search"],"summary":"Aggregate execution stats (P99 latency, active count)","operationId":"stats","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"routeId","in":"query","required":false,"schema":{"type":"string"}},{"name":"application","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ExecutionStats"}}}}}}},"/search/stats/timeseries":{"get":{"tags":["Search"],"summary":"Bucketed time-series stats over a time window","operationId":"timeseries","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"buckets","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":24}},{"name":"routeId","in":"query","required":false,"schema":{"type":"string"}},{"name":"application","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StatsTimeseries"}}}}}}},"/routes/metrics":{"get":{"tags":["Route Metrics"],"summary":"Get route metrics","description":"Returns aggregated performance metrics per route for the given time window","operationId":"getMetrics","parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"appId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Metrics returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RouteMetrics"}}}}}}}},"/routes/metrics/processors":{"get":{"tags":["Route Metrics"],"summary":"Get processor metrics","description":"Returns aggregated performance metrics per processor for the given route and time window","operationId":"getProcessorMetrics","parameters":[{"name":"routeId","in":"query","required":true,"schema":{"type":"string"}},{"name":"appId","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Metrics returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProcessorMetrics"}}}}}}}},"/routes/catalog":{"get":{"tags":["Route Catalog"],"summary":"Get route catalog","description":"Returns all applications with their routes, agents, and health status","operationId":"getCatalog","parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Catalog returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppCatalogEntry"}}}}}}}},"/logs":{"get":{"tags":["Application Logs"],"summary":"Search application log entries","description":"Returns log entries for a given application, optionally filtered by agent, level, time range, and text query","operationId":"searchLogs","parameters":[{"name":"application","in":"query","required":true,"schema":{"type":"string"}},{"name":"agentId","in":"query","required":false,"schema":{"type":"string"}},{"name":"level","in":"query","required":false,"schema":{"type":"string"}},{"name":"query","in":"query","required":false,"schema":{"type":"string"}},{"name":"exchangeId","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":200}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LogEntryResponse"}}}}}}}},"/executions/{executionId}":{"get":{"tags":["Detail"],"summary":"Get execution detail with nested processor tree","operationId":"getDetail","parameters":[{"name":"executionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Execution detail found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ExecutionDetail"}}}},"404":{"description":"Execution not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ExecutionDetail"}}}}}}},"/executions/{executionId}/processors/{index}/snapshot":{"get":{"tags":["Detail"],"summary":"Get exchange snapshot for a specific processor","operationId":"getProcessorSnapshot","parameters":[{"name":"executionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"index","in":"path","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Snapshot data","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}},"404":{"description":"Snapshot not found","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/diagrams":{"get":{"tags":["Diagrams"],"summary":"Find diagram by application and route ID","description":"Resolves application to agent IDs and finds the latest diagram for the route","operationId":"findByApplicationAndRoute","parameters":[{"name":"application","in":"query","required":true,"schema":{"type":"string"}},{"name":"routeId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Diagram layout returned","content":{"*/*":{"schema":{"$ref":"#/components/schemas/DiagramLayout"}}}},"404":{"description":"No diagram found for the given application and route","content":{"*/*":{"schema":{"$ref":"#/components/schemas/DiagramLayout"}}}}}}},"/diagrams/{contentHash}/render":{"get":{"tags":["Diagrams"],"summary":"Render a route diagram","description":"Returns SVG (default) or JSON layout based on Accept header","operationId":"renderDiagram","parameters":[{"name":"contentHash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Diagram rendered successfully","content":{"image/svg+xml":{"schema":{"type":"string"}},"application/json":{"schema":{"$ref":"#/components/schemas/DiagramLayout"}}}},"404":{"description":"Diagram not found","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/config":{"get":{"tags":["Application Config"],"summary":"List all application configs","description":"Returns stored configurations for all applications","operationId":"listConfigs","responses":{"200":{"description":"Configs returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApplicationConfig"}}}}}}}},"/auth/oidc/config":{"get":{"tags":["Authentication"],"summary":"Get OIDC config for SPA login flow","operationId":"getConfig_2","responses":{"200":{"description":"OIDC configuration","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OidcPublicConfigResponse"}}}},"404":{"description":"OIDC not configured or disabled","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OidcPublicConfigResponse"}}}},"500":{"description":"Failed to retrieve OIDC provider metadata","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/agents":{"get":{"tags":["Agent Management"],"summary":"List all agents","description":"Returns all registered agents with runtime metrics, optionally filtered by status and/or application","operationId":"listAgents","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"application","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Agent list returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentInstanceResponse"}}}}},"400":{"description":"Invalid status filter","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/agents/{id}/events":{"get":{"tags":["Agent SSE"],"summary":"Open SSE event stream","description":"Opens a Server-Sent Events stream for the specified agent. Commands (config-update, deep-trace, replay) are pushed as events. Ping keepalive comments sent every 15 seconds.","operationId":"events","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Last-Event-ID","in":"header","description":"Last received event ID (no replay, acknowledged only)","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"SSE stream opened","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/SseEmitter"}}}},"404":{"description":"Agent not registered","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/SseEmitter"}}}}}}},"/agents/{agentId}/metrics":{"get":{"tags":["agent-metrics-controller"],"operationId":"getMetrics_1","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"names","in":"query","required":true,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"buckets","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":60}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AgentMetricsResponse"}}}}}}},"/agents/events-log":{"get":{"tags":["Agent Events"],"summary":"Query agent events","description":"Returns agent lifecycle events, optionally filtered by app and/or agent ID","operationId":"getEvents","parameters":[{"name":"appId","in":"query","required":false,"schema":{"type":"string"}},{"name":"agentId","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}}],"responses":{"200":{"description":"Events returned","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentEventResponse"}}}}}}}},"/admin/rbac/stats":{"get":{"tags":["RBAC Stats"],"summary":"Get RBAC statistics for the dashboard","operationId":"getStats","responses":{"200":{"description":"RBAC stats returned","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RbacStats"}}}}}}},"/admin/opensearch/status":{"get":{"tags":["OpenSearch Admin"],"summary":"Get OpenSearch cluster status and version","operationId":"getStatus","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OpenSearchStatusResponse"}}}}}}},"/admin/opensearch/pipeline":{"get":{"tags":["OpenSearch Admin"],"summary":"Get indexing pipeline statistics","operationId":"getPipeline","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PipelineStatsResponse"}}}}}}},"/admin/opensearch/performance":{"get":{"tags":["OpenSearch Admin"],"summary":"Get OpenSearch performance metrics","operationId":"getPerformance","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PerformanceResponse"}}}}}}},"/admin/opensearch/indices":{"get":{"tags":["OpenSearch Admin"],"summary":"Get OpenSearch indices with pagination","operationId":"getIndices","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":20}},{"name":"search","in":"query","required":false,"schema":{"type":"string","default":""}},{"name":"prefix","in":"query","required":false,"schema":{"type":"string","default":"executions"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IndicesPageResponse"}}}}}}},"/admin/database/tables":{"get":{"tags":["Database Admin"],"summary":"Get table sizes and row counts","operationId":"getTables","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TableSizeResponse"}}}}}}}},"/admin/database/status":{"get":{"tags":["Database Admin"],"summary":"Get database connection status and version","operationId":"getStatus_1","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/DatabaseStatusResponse"}}}}}}},"/admin/database/queries":{"get":{"tags":["Database Admin"],"summary":"Get active queries","operationId":"getQueries","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ActiveQueryResponse"}}}}}}}},"/admin/database/pool":{"get":{"tags":["Database Admin"],"summary":"Get HikariCP connection pool stats","operationId":"getPool","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ConnectionPoolResponse"}}}}}}},"/admin/database/metrics-pipeline":{"get":{"tags":["Database Admin"],"summary":"Get metrics ingestion pipeline diagnostics","operationId":"getMetricsPipeline","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"object"}}}}}}}},"/admin/audit":{"get":{"tags":["Audit Log"],"summary":"Search audit log entries with pagination","operationId":"getAuditLog","parameters":[{"name":"username","in":"query","required":false,"schema":{"type":"string"}},{"name":"category","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"timestamp"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":25}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuditLogPageResponse"}}}}}}},"/admin/opensearch/indices/{name}":{"delete":{"tags":["OpenSearch Admin"],"summary":"Delete an OpenSearch index","operationId":"deleteIndex","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}}},"components":{"schemas":{"ApplicationConfig":{"type":"object","properties":{"application":{"type":"string"},"version":{"type":"integer","format":"int32"},"updatedAt":{"type":"string","format":"date-time"},"engineLevel":{"type":"string"},"payloadCaptureMode":{"type":"string"},"metricsEnabled":{"type":"boolean"},"samplingRate":{"type":"number","format":"double"},"tracedProcessors":{"type":"object","additionalProperties":{"type":"string"}},"logForwardingLevel":{"type":"string"},"taps":{"type":"array","items":{"$ref":"#/components/schemas/TapDefinition"}},"tapVersion":{"type":"integer","format":"int32"},"routeRecording":{"type":"object","additionalProperties":{"type":"boolean"}},"compressSuccess":{"type":"boolean"}}},"TapDefinition":{"type":"object","properties":{"tapId":{"type":"string"},"processorId":{"type":"string"},"target":{"type":"string"},"expression":{"type":"string"},"language":{"type":"string"},"attributeName":{"type":"string"},"attributeType":{"type":"string"},"enabled":{"type":"boolean"},"version":{"type":"integer","format":"int32"}}},"UpdateUserRequest":{"type":"object","properties":{"displayName":{"type":"string"},"email":{"type":"string"}}},"DatabaseThresholdsRequest":{"type":"object","description":"Database monitoring thresholds","properties":{"connectionPoolWarning":{"type":"integer","format":"int32","description":"Connection pool usage warning threshold (percentage)","maximum":100,"minimum":0},"connectionPoolCritical":{"type":"integer","format":"int32","description":"Connection pool usage critical threshold (percentage)","maximum":100,"minimum":0},"queryDurationWarning":{"type":"number","format":"double","description":"Query duration warning threshold (seconds)"},"queryDurationCritical":{"type":"number","format":"double","description":"Query duration critical threshold (seconds)"}}},"OpenSearchThresholdsRequest":{"type":"object","description":"OpenSearch monitoring thresholds","properties":{"clusterHealthWarning":{"type":"string","description":"Cluster health warning threshold (GREEN, YELLOW, RED)","minLength":1},"clusterHealthCritical":{"type":"string","description":"Cluster health critical threshold (GREEN, YELLOW, RED)","minLength":1},"queueDepthWarning":{"type":"integer","format":"int32","description":"Queue depth warning threshold","minimum":0},"queueDepthCritical":{"type":"integer","format":"int32","description":"Queue depth critical threshold","minimum":0},"jvmHeapWarning":{"type":"integer","format":"int32","description":"JVM heap usage warning threshold (percentage)","maximum":100,"minimum":0},"jvmHeapCritical":{"type":"integer","format":"int32","description":"JVM heap usage critical threshold (percentage)","maximum":100,"minimum":0},"failedDocsWarning":{"type":"integer","format":"int32","description":"Failed document count warning threshold","minimum":0},"failedDocsCritical":{"type":"integer","format":"int32","description":"Failed document count critical threshold","minimum":0}}},"ThresholdConfigRequest":{"type":"object","description":"Threshold configuration for admin monitoring","properties":{"database":{"$ref":"#/components/schemas/DatabaseThresholdsRequest"},"opensearch":{"$ref":"#/components/schemas/OpenSearchThresholdsRequest"}},"required":["database","opensearch"]},"DatabaseThresholds":{"type":"object","properties":{"connectionPoolWarning":{"type":"integer","format":"int32"},"connectionPoolCritical":{"type":"integer","format":"int32"},"queryDurationWarning":{"type":"number","format":"double"},"queryDurationCritical":{"type":"number","format":"double"}}},"OpenSearchThresholds":{"type":"object","properties":{"clusterHealthWarning":{"type":"string"},"clusterHealthCritical":{"type":"string"},"queueDepthWarning":{"type":"integer","format":"int32"},"queueDepthCritical":{"type":"integer","format":"int32"},"jvmHeapWarning":{"type":"integer","format":"int32"},"jvmHeapCritical":{"type":"integer","format":"int32"},"failedDocsWarning":{"type":"integer","format":"int32"},"failedDocsCritical":{"type":"integer","format":"int32"}}},"ThresholdConfig":{"type":"object","properties":{"database":{"$ref":"#/components/schemas/DatabaseThresholds"},"opensearch":{"$ref":"#/components/schemas/OpenSearchThresholds"}}},"UpdateRoleRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"scope":{"type":"string"}}},"OidcAdminConfigRequest":{"type":"object","description":"OIDC configuration update request","properties":{"enabled":{"type":"boolean"},"issuerUri":{"type":"string"},"clientId":{"type":"string"},"clientSecret":{"type":"string"},"rolesClaim":{"type":"string"},"defaultRoles":{"type":"array","items":{"type":"string"}},"autoSignup":{"type":"boolean"},"displayNameClaim":{"type":"string"}}},"ErrorResponse":{"type":"object","description":"Error response","properties":{"message":{"type":"string"}},"required":["message"]},"OidcAdminConfigResponse":{"type":"object","description":"OIDC configuration for admin management","properties":{"configured":{"type":"boolean"},"enabled":{"type":"boolean"},"issuerUri":{"type":"string"},"clientId":{"type":"string"},"clientSecretSet":{"type":"boolean"},"rolesClaim":{"type":"string"},"defaultRoles":{"type":"array","items":{"type":"string"}},"autoSignup":{"type":"boolean"},"displayNameClaim":{"type":"string"}}},"UpdateGroupRequest":{"type":"object","properties":{"name":{"type":"string"},"parentGroupId":{"type":"string","format":"uuid"}}},"SearchRequest":{"type":"object","properties":{"status":{"type":"string"},"timeFrom":{"type":"string","format":"date-time"},"timeTo":{"type":"string","format":"date-time"},"durationMin":{"type":"integer","format":"int64"},"durationMax":{"type":"integer","format":"int64"},"correlationId":{"type":"string"},"text":{"type":"string"},"textInBody":{"type":"string"},"textInHeaders":{"type":"string"},"textInErrors":{"type":"string"},"routeId":{"type":"string"},"agentId":{"type":"string"},"processorType":{"type":"string"},"application":{"type":"string"},"agentIds":{"type":"array","items":{"type":"string"}},"offset":{"type":"integer","format":"int32"},"limit":{"type":"integer","format":"int32"},"sortField":{"type":"string"},"sortDir":{"type":"string"}}},"ExecutionSummary":{"type":"object","properties":{"executionId":{"type":"string"},"routeId":{"type":"string"},"agentId":{"type":"string"},"applicationName":{"type":"string"},"status":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"durationMs":{"type":"integer","format":"int64"},"correlationId":{"type":"string"},"errorMessage":{"type":"string"},"diagramContentHash":{"type":"string"},"highlight":{"type":"string"},"attributes":{"type":"object","additionalProperties":{"type":"string"}}},"required":["agentId","applicationName","attributes","correlationId","diagramContentHash","durationMs","endTime","errorMessage","executionId","highlight","routeId","startTime","status"]},"SearchResultExecutionSummary":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExecutionSummary"}},"total":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int32"},"limit":{"type":"integer","format":"int32"}},"required":["data","limit","offset","total"]},"LogBatch":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/LogEntry"}}}},"LogEntry":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"level":{"type":"string"},"loggerName":{"type":"string"},"message":{"type":"string"},"threadName":{"type":"string"},"stackTrace":{"type":"string"},"mdc":{"type":"object","additionalProperties":{"type":"string"}}}},"TestExpressionRequest":{"type":"object","properties":{"expression":{"type":"string"},"language":{"type":"string"},"body":{"type":"string"},"target":{"type":"string"}}},"TestExpressionResponse":{"type":"object","properties":{"result":{"type":"string"},"error":{"type":"string"}}},"RefreshRequest":{"type":"object","properties":{"refreshToken":{"type":"string"}}},"AuthTokenResponse":{"type":"object","description":"JWT token pair","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"},"displayName":{"type":"string"},"idToken":{"type":"string","description":"OIDC id_token for end-session logout (only present after OIDC login)"}},"required":["accessToken","displayName","refreshToken"]},"CallbackRequest":{"type":"object","properties":{"code":{"type":"string"},"redirectUri":{"type":"string"}}},"LoginRequest":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"AgentRefreshRequest":{"type":"object","description":"Agent token refresh request","properties":{"refreshToken":{"type":"string"}},"required":["refreshToken"]},"AgentRefreshResponse":{"type":"object","description":"Refreshed access and refresh tokens","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"}},"required":["accessToken","refreshToken"]},"CommandRequest":{"type":"object","description":"Command to send to agent(s)","properties":{"type":{"type":"string","description":"Command type: config-update, deep-trace, or replay"},"payload":{"type":"object","description":"Command payload JSON"}},"required":["type"]},"CommandSingleResponse":{"type":"object","description":"Result of sending a command to a single agent","properties":{"commandId":{"type":"string"},"status":{"type":"string"}},"required":["commandId","status"]},"CommandAckRequest":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"data":{"type":"string"}}},"AgentRegistrationRequest":{"type":"object","description":"Agent registration payload","properties":{"agentId":{"type":"string"},"name":{"type":"string"},"application":{"type":"string","default":"default"},"version":{"type":"string"},"routeIds":{"type":"array","items":{"type":"string"}},"capabilities":{"type":"object","additionalProperties":{"type":"object"}}},"required":["agentId","name"]},"AgentRegistrationResponse":{"type":"object","description":"Agent registration result with JWT tokens and SSE endpoint","properties":{"agentId":{"type":"string"},"sseEndpoint":{"type":"string"},"heartbeatIntervalMs":{"type":"integer","format":"int64"},"serverPublicKey":{"type":"string"},"accessToken":{"type":"string"},"refreshToken":{"type":"string"}},"required":["accessToken","agentId","refreshToken","serverPublicKey","sseEndpoint"]},"CommandBroadcastResponse":{"type":"object","description":"Result of broadcasting a command to multiple agents","properties":{"commandIds":{"type":"array","items":{"type":"string"}},"targetCount":{"type":"integer","format":"int32"}},"required":["commandIds"]},"CreateUserRequest":{"type":"object","properties":{"username":{"type":"string"},"displayName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}},"GroupSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"RoleSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"system":{"type":"boolean"},"source":{"type":"string"}}},"UserDetail":{"type":"object","properties":{"userId":{"type":"string"},"provider":{"type":"string"},"email":{"type":"string"},"displayName":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"directRoles":{"type":"array","items":{"$ref":"#/components/schemas/RoleSummary"}},"directGroups":{"type":"array","items":{"$ref":"#/components/schemas/GroupSummary"}},"effectiveRoles":{"type":"array","items":{"$ref":"#/components/schemas/RoleSummary"}},"effectiveGroups":{"type":"array","items":{"$ref":"#/components/schemas/GroupSummary"}}}},"SetPasswordRequest":{"type":"object","properties":{"password":{"type":"string","minLength":1}}},"CreateRoleRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"scope":{"type":"string"}}},"OidcTestResult":{"type":"object","description":"OIDC provider connectivity test result","properties":{"status":{"type":"string"},"authorizationEndpoint":{"type":"string"}},"required":["authorizationEndpoint","status"]},"CreateGroupRequest":{"type":"object","properties":{"name":{"type":"string"},"parentGroupId":{"type":"string","format":"uuid"}}},"ExecutionStats":{"type":"object","properties":{"totalCount":{"type":"integer","format":"int64"},"failedCount":{"type":"integer","format":"int64"},"avgDurationMs":{"type":"integer","format":"int64"},"p99LatencyMs":{"type":"integer","format":"int64"},"activeCount":{"type":"integer","format":"int64"},"totalToday":{"type":"integer","format":"int64"},"prevTotalCount":{"type":"integer","format":"int64"},"prevFailedCount":{"type":"integer","format":"int64"},"prevAvgDurationMs":{"type":"integer","format":"int64"},"prevP99LatencyMs":{"type":"integer","format":"int64"}},"required":["activeCount","avgDurationMs","failedCount","p99LatencyMs","prevAvgDurationMs","prevFailedCount","prevP99LatencyMs","prevTotalCount","totalCount","totalToday"]},"StatsTimeseries":{"type":"object","properties":{"buckets":{"type":"array","items":{"$ref":"#/components/schemas/TimeseriesBucket"}}},"required":["buckets"]},"TimeseriesBucket":{"type":"object","properties":{"time":{"type":"string","format":"date-time"},"totalCount":{"type":"integer","format":"int64"},"failedCount":{"type":"integer","format":"int64"},"avgDurationMs":{"type":"integer","format":"int64"},"p99DurationMs":{"type":"integer","format":"int64"},"activeCount":{"type":"integer","format":"int64"}},"required":["activeCount","avgDurationMs","failedCount","p99DurationMs","time","totalCount"]},"RouteMetrics":{"type":"object","description":"Aggregated route performance metrics","properties":{"routeId":{"type":"string"},"appId":{"type":"string"},"exchangeCount":{"type":"integer","format":"int64"},"successRate":{"type":"number","format":"double"},"avgDurationMs":{"type":"number","format":"double"},"p99DurationMs":{"type":"number","format":"double"},"errorRate":{"type":"number","format":"double"},"throughputPerSec":{"type":"number","format":"double"},"sparkline":{"type":"array","items":{"type":"number","format":"double"}}},"required":["appId","avgDurationMs","errorRate","exchangeCount","p99DurationMs","routeId","sparkline","successRate","throughputPerSec"]},"ProcessorMetrics":{"type":"object","properties":{"processorId":{"type":"string"},"processorType":{"type":"string"},"routeId":{"type":"string"},"appId":{"type":"string"},"totalCount":{"type":"integer","format":"int64"},"failedCount":{"type":"integer","format":"int64"},"avgDurationMs":{"type":"number","format":"double"},"p99DurationMs":{"type":"number","format":"double"},"errorRate":{"type":"number","format":"double"}},"required":["appId","avgDurationMs","errorRate","failedCount","p99DurationMs","processorId","processorType","routeId","totalCount"]},"AgentSummary":{"type":"object","description":"Summary of an agent instance for sidebar display","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"tps":{"type":"number","format":"double"}},"required":["id","name","status","tps"]},"AppCatalogEntry":{"type":"object","description":"Application catalog entry with routes and agents","properties":{"appId":{"type":"string"},"routes":{"type":"array","items":{"$ref":"#/components/schemas/RouteSummary"}},"agents":{"type":"array","items":{"$ref":"#/components/schemas/AgentSummary"}},"agentCount":{"type":"integer","format":"int32"},"health":{"type":"string"},"exchangeCount":{"type":"integer","format":"int64"}},"required":["agentCount","agents","appId","exchangeCount","health","routes"]},"RouteSummary":{"type":"object","description":"Summary of a route within an application","properties":{"routeId":{"type":"string"},"exchangeCount":{"type":"integer","format":"int64"},"lastSeen":{"type":"string","format":"date-time"}},"required":["exchangeCount","lastSeen","routeId"]},"LogEntryResponse":{"type":"object","description":"Application log entry from OpenSearch","properties":{"timestamp":{"type":"string","description":"Log timestamp (ISO-8601)"},"level":{"type":"string","description":"Log level (INFO, WARN, ERROR, DEBUG)"},"loggerName":{"type":"string","description":"Logger name"},"message":{"type":"string","description":"Log message"},"threadName":{"type":"string","description":"Thread name"},"stackTrace":{"type":"string","description":"Stack trace (if present)"}}},"ExecutionDetail":{"type":"object","properties":{"executionId":{"type":"string"},"routeId":{"type":"string"},"agentId":{"type":"string"},"applicationName":{"type":"string"},"status":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"durationMs":{"type":"integer","format":"int64"},"correlationId":{"type":"string"},"exchangeId":{"type":"string"},"errorMessage":{"type":"string"},"errorStackTrace":{"type":"string"},"diagramContentHash":{"type":"string"},"processors":{"type":"array","items":{"$ref":"#/components/schemas/ProcessorNode"}},"inputBody":{"type":"string"},"outputBody":{"type":"string"},"inputHeaders":{"type":"string"},"outputHeaders":{"type":"string"},"attributes":{"type":"object","additionalProperties":{"type":"string"}}},"required":["agentId","applicationName","attributes","correlationId","diagramContentHash","durationMs","endTime","errorMessage","errorStackTrace","exchangeId","executionId","inputBody","inputHeaders","outputBody","outputHeaders","processors","routeId","startTime","status"]},"ProcessorNode":{"type":"object","properties":{"processorId":{"type":"string"},"processorType":{"type":"string"},"status":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"durationMs":{"type":"integer","format":"int64"},"diagramNodeId":{"type":"string"},"errorMessage":{"type":"string"},"errorStackTrace":{"type":"string"},"attributes":{"type":"object","additionalProperties":{"type":"string"}},"children":{"type":"array","items":{"$ref":"#/components/schemas/ProcessorNode"}}},"required":["attributes","children","diagramNodeId","durationMs","endTime","errorMessage","errorStackTrace","processorId","processorType","startTime","status"]},"DiagramLayout":{"type":"object","properties":{"width":{"type":"number","format":"double"},"height":{"type":"number","format":"double"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/PositionedNode"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/PositionedEdge"}}}},"PositionedEdge":{"type":"object","properties":{"sourceId":{"type":"string"},"targetId":{"type":"string"},"label":{"type":"string"},"points":{"type":"array","items":{"type":"array","items":{"type":"number","format":"double"}}}}},"PositionedNode":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"x":{"type":"number","format":"double"},"y":{"type":"number","format":"double"},"width":{"type":"number","format":"double"},"height":{"type":"number","format":"double"}}},"OidcPublicConfigResponse":{"type":"object","description":"OIDC configuration for SPA login flow","properties":{"issuer":{"type":"string"},"clientId":{"type":"string"},"authorizationEndpoint":{"type":"string"},"endSessionEndpoint":{"type":"string","description":"Present if the provider supports RP-initiated logout"}},"required":["authorizationEndpoint","clientId","issuer"]},"AgentInstanceResponse":{"type":"object","description":"Agent instance summary with runtime metrics","properties":{"id":{"type":"string"},"name":{"type":"string"},"application":{"type":"string"},"status":{"type":"string"},"routeIds":{"type":"array","items":{"type":"string"}},"registeredAt":{"type":"string","format":"date-time"},"lastHeartbeat":{"type":"string","format":"date-time"},"version":{"type":"string"},"capabilities":{"type":"object","additionalProperties":{"type":"object"}},"tps":{"type":"number","format":"double"},"errorRate":{"type":"number","format":"double"},"activeRoutes":{"type":"integer","format":"int32"},"totalRoutes":{"type":"integer","format":"int32"},"uptimeSeconds":{"type":"integer","format":"int64"}},"required":["activeRoutes","application","capabilities","errorRate","id","lastHeartbeat","name","registeredAt","routeIds","status","totalRoutes","tps","uptimeSeconds","version"]},"SseEmitter":{"type":"object","properties":{"timeout":{"type":"integer","format":"int64"}}},"AgentMetricsResponse":{"type":"object","properties":{"metrics":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/MetricBucket"}}}},"required":["metrics"]},"MetricBucket":{"type":"object","properties":{"time":{"type":"string","format":"date-time"},"value":{"type":"number","format":"double"}},"required":["time","value"]},"AgentEventResponse":{"type":"object","description":"Agent lifecycle event","properties":{"id":{"type":"integer","format":"int64"},"agentId":{"type":"string"},"appId":{"type":"string"},"eventType":{"type":"string"},"detail":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"required":["agentId","appId","detail","eventType","id","timestamp"]},"RoleDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"scope":{"type":"string"},"system":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"assignedGroups":{"type":"array","items":{"$ref":"#/components/schemas/GroupSummary"}},"directUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserSummary"}},"effectivePrincipals":{"type":"array","items":{"$ref":"#/components/schemas/UserSummary"}}}},"UserSummary":{"type":"object","properties":{"userId":{"type":"string"},"displayName":{"type":"string"},"provider":{"type":"string"}}},"RbacStats":{"type":"object","properties":{"userCount":{"type":"integer","format":"int32"},"activeUserCount":{"type":"integer","format":"int32"},"groupCount":{"type":"integer","format":"int32"},"maxGroupDepth":{"type":"integer","format":"int32"},"roleCount":{"type":"integer","format":"int32"}}},"OpenSearchStatusResponse":{"type":"object","description":"OpenSearch cluster status","properties":{"reachable":{"type":"boolean","description":"Whether the cluster is reachable"},"clusterHealth":{"type":"string","description":"Cluster health status (GREEN, YELLOW, RED)"},"version":{"type":"string","description":"OpenSearch version"},"nodeCount":{"type":"integer","format":"int32","description":"Number of nodes in the cluster"},"host":{"type":"string","description":"OpenSearch host"}}},"PipelineStatsResponse":{"type":"object","description":"Search indexing pipeline statistics","properties":{"queueDepth":{"type":"integer","format":"int32","description":"Current queue depth"},"maxQueueSize":{"type":"integer","format":"int32","description":"Maximum queue size"},"failedCount":{"type":"integer","format":"int64","description":"Number of failed indexing operations"},"indexedCount":{"type":"integer","format":"int64","description":"Number of successfully indexed documents"},"debounceMs":{"type":"integer","format":"int64","description":"Debounce interval in milliseconds"},"indexingRate":{"type":"number","format":"double","description":"Current indexing rate (docs/sec)"},"lastIndexedAt":{"type":"string","format":"date-time","description":"Timestamp of last indexed document"}}},"PerformanceResponse":{"type":"object","description":"OpenSearch performance metrics","properties":{"queryCacheHitRate":{"type":"number","format":"double","description":"Query cache hit rate (0.0-1.0)"},"requestCacheHitRate":{"type":"number","format":"double","description":"Request cache hit rate (0.0-1.0)"},"searchLatencyMs":{"type":"number","format":"double","description":"Average search latency in milliseconds"},"indexingLatencyMs":{"type":"number","format":"double","description":"Average indexing latency in milliseconds"},"jvmHeapUsedBytes":{"type":"integer","format":"int64","description":"JVM heap used in bytes"},"jvmHeapMaxBytes":{"type":"integer","format":"int64","description":"JVM heap max in bytes"}}},"IndexInfoResponse":{"type":"object","description":"OpenSearch index information","properties":{"name":{"type":"string","description":"Index name"},"docCount":{"type":"integer","format":"int64","description":"Document count"},"size":{"type":"string","description":"Human-readable index size"},"sizeBytes":{"type":"integer","format":"int64","description":"Index size in bytes"},"health":{"type":"string","description":"Index health status"},"primaryShards":{"type":"integer","format":"int32","description":"Number of primary shards"},"replicaShards":{"type":"integer","format":"int32","description":"Number of replica shards"}}},"IndicesPageResponse":{"type":"object","description":"Paginated list of OpenSearch indices","properties":{"indices":{"type":"array","description":"Index list for current page","items":{"$ref":"#/components/schemas/IndexInfoResponse"}},"totalIndices":{"type":"integer","format":"int64","description":"Total number of indices"},"totalDocs":{"type":"integer","format":"int64","description":"Total document count across all indices"},"totalSize":{"type":"string","description":"Human-readable total size"},"page":{"type":"integer","format":"int32","description":"Current page number (0-based)"},"pageSize":{"type":"integer","format":"int32","description":"Page size"},"totalPages":{"type":"integer","format":"int32","description":"Total number of pages"}}},"GroupDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"parentGroupId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"directRoles":{"type":"array","items":{"$ref":"#/components/schemas/RoleSummary"}},"effectiveRoles":{"type":"array","items":{"$ref":"#/components/schemas/RoleSummary"}},"members":{"type":"array","items":{"$ref":"#/components/schemas/UserSummary"}},"childGroups":{"type":"array","items":{"$ref":"#/components/schemas/GroupSummary"}}}},"TableSizeResponse":{"type":"object","description":"Table size and row count information","properties":{"tableName":{"type":"string","description":"Table name"},"rowCount":{"type":"integer","format":"int64","description":"Approximate row count"},"dataSize":{"type":"string","description":"Human-readable data size"},"indexSize":{"type":"string","description":"Human-readable index size"},"dataSizeBytes":{"type":"integer","format":"int64","description":"Data size in bytes"},"indexSizeBytes":{"type":"integer","format":"int64","description":"Index size in bytes"}}},"DatabaseStatusResponse":{"type":"object","description":"Database connection and version status","properties":{"connected":{"type":"boolean","description":"Whether the database is reachable"},"version":{"type":"string","description":"PostgreSQL version string"},"host":{"type":"string","description":"Database host"},"schema":{"type":"string","description":"Current schema search path"},"timescaleDb":{"type":"boolean","description":"Whether TimescaleDB extension is available"}}},"ActiveQueryResponse":{"type":"object","description":"Currently running database query","properties":{"pid":{"type":"integer","format":"int32","description":"Backend process ID"},"durationSeconds":{"type":"number","format":"double","description":"Query duration in seconds"},"state":{"type":"string","description":"Backend state (active, idle, etc.)"},"query":{"type":"string","description":"SQL query text"}}},"ConnectionPoolResponse":{"type":"object","description":"HikariCP connection pool statistics","properties":{"activeConnections":{"type":"integer","format":"int32","description":"Number of currently active connections"},"idleConnections":{"type":"integer","format":"int32","description":"Number of idle connections"},"pendingThreads":{"type":"integer","format":"int32","description":"Number of threads waiting for a connection"},"maxWaitMs":{"type":"integer","format":"int64","description":"Maximum wait time in milliseconds"},"maxPoolSize":{"type":"integer","format":"int32","description":"Maximum pool size"}}},"AuditLogPageResponse":{"type":"object","description":"Paginated audit log entries","properties":{"items":{"type":"array","description":"Audit log entries","items":{"$ref":"#/components/schemas/AuditRecord"}},"totalCount":{"type":"integer","format":"int64","description":"Total number of matching entries"},"page":{"type":"integer","format":"int32","description":"Current page number (0-based)"},"pageSize":{"type":"integer","format":"int32","description":"Page size"},"totalPages":{"type":"integer","format":"int32","description":"Total number of pages"}}},"AuditRecord":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"timestamp":{"type":"string","format":"date-time"},"username":{"type":"string"},"action":{"type":"string"},"category":{"type":"string","enum":["INFRA","AUTH","USER_MGMT","CONFIG","RBAC","AGENT"]},"target":{"type":"string"},"detail":{"type":"object","additionalProperties":{"type":"object"}},"result":{"type":"string","enum":["SUCCESS","FAILURE"]},"ipAddress":{"type":"string"},"userAgent":{"type":"string"}}}},"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}}}
\ No newline at end of file
diff --git a/org/eclipse/elk/alg/layered/options/.LayeredOptions.java._trace b/org/eclipse/elk/alg/layered/options/.LayeredOptions.java._trace
new file mode 100644
index 00000000..144082a5
Binary files /dev/null and b/org/eclipse/elk/alg/layered/options/.LayeredOptions.java._trace differ
diff --git a/org/eclipse/elk/core/options/CoreOptions.java b/org/eclipse/elk/core/options/CoreOptions.java
new file mode 100644
index 00000000..fa48310b
--- /dev/null
+++ b/org/eclipse/elk/core/options/CoreOptions.java
@@ -0,0 +1,2661 @@
+/**
+ * Copyright (c) 2015, 2020 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.elk.core.options;
+
+import java.util.EnumSet;
+import org.eclipse.elk.core.data.ILayoutMetaDataProvider;
+import org.eclipse.elk.core.data.LayoutAlgorithmData;
+import org.eclipse.elk.core.data.LayoutCategoryData;
+import org.eclipse.elk.core.data.LayoutOptionData;
+import org.eclipse.elk.core.labels.ILabelManager;
+import org.eclipse.elk.core.math.ElkMargin;
+import org.eclipse.elk.core.math.ElkPadding;
+import org.eclipse.elk.core.math.KVector;
+import org.eclipse.elk.core.math.KVectorChain;
+import org.eclipse.elk.core.util.BoxLayoutProvider;
+import org.eclipse.elk.core.util.ExclusiveBounds;
+import org.eclipse.elk.core.util.IndividualSpacings;
+import org.eclipse.elk.graph.properties.IProperty;
+import org.eclipse.elk.graph.properties.Property;
+
+/**
+ * Core definitions of the Eclipse Layout Kernel.
+ */
+@SuppressWarnings("all")
+public class CoreOptions implements ILayoutMetaDataProvider {
+ /**
+ * Select a specific layout algorithm.
+ */
+ public static final IProperty ALGORITHM = new Property(
+ "org.eclipse.elk.algorithm");
+
+ /**
+ * Meta data associated with the selected algorithm.
+ */
+ public static final IProperty RESOLVED_ALGORITHM = new Property(
+ "org.eclipse.elk.resolvedAlgorithm");
+
+ /**
+ * Default value for {@link #ALIGNMENT}.
+ */
+ private static final Alignment ALIGNMENT_DEFAULT = Alignment.AUTOMATIC;
+
+ /**
+ * Alignment of the selected node relative to other nodes;
+ * the exact meaning depends on the used algorithm.
+ */
+ public static final IProperty ALIGNMENT = new Property(
+ "org.eclipse.elk.alignment",
+ ALIGNMENT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Lower bound value for {@link #ASPECT_RATIO}.
+ */
+ private static final Comparable super Double> ASPECT_RATIO_LOWER_BOUND = ExclusiveBounds.greaterThan(0);
+
+ /**
+ * The desired aspect ratio of the drawing, that is the quotient of width by height.
+ */
+ public static final IProperty ASPECT_RATIO = new Property(
+ "org.eclipse.elk.aspectRatio",
+ null,
+ ASPECT_RATIO_LOWER_BOUND,
+ null);
+
+ /**
+ * A fixed list of bend points for the edge. This is used by the 'Fixed Layout' algorithm to
+ * specify a pre-defined routing for an edge. The vector chain must include the source point,
+ * any bend points, and the target point, so it must have at least two points.
+ */
+ public static final IProperty BEND_POINTS = new Property(
+ "org.eclipse.elk.bendPoints");
+
+ /**
+ * Default value for {@link #CONTENT_ALIGNMENT}.
+ */
+ private static final EnumSet CONTENT_ALIGNMENT_DEFAULT = ContentAlignment.topLeft();
+
+ /**
+ * Specifies how the content of a node are aligned. Each node can individually control the alignment of its
+ * contents. I.e. if a node should be aligned top left in its parent node, the parent node should specify that
+ * option.
+ */
+ public static final IProperty> CONTENT_ALIGNMENT = new Property>(
+ "org.eclipse.elk.contentAlignment",
+ CONTENT_ALIGNMENT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #DEBUG_MODE}.
+ */
+ private static final boolean DEBUG_MODE_DEFAULT = false;
+
+ /**
+ * Whether additional debug information shall be generated.
+ */
+ public static final IProperty DEBUG_MODE = new Property(
+ "org.eclipse.elk.debugMode",
+ DEBUG_MODE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #DIRECTION}.
+ */
+ private static final Direction DIRECTION_DEFAULT = Direction.UNDEFINED;
+
+ /**
+ * Overall direction of edges: horizontal (right / left) or
+ * vertical (down / up).
+ */
+ public static final IProperty DIRECTION = new Property(
+ "org.eclipse.elk.direction",
+ DIRECTION_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #EDGE_ROUTING}.
+ */
+ private static final EdgeRouting EDGE_ROUTING_DEFAULT = EdgeRouting.UNDEFINED;
+
+ /**
+ * What kind of edge routing style should be applied for the content of a parent node.
+ * Algorithms may also set this option to single edges in order to mark them as splines.
+ * The bend point list of edges with this option set to SPLINES must be interpreted as control
+ * points for a piecewise cubic spline.
+ */
+ public static final IProperty EDGE_ROUTING = new Property(
+ "org.eclipse.elk.edgeRouting",
+ EDGE_ROUTING_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #EXPAND_NODES}.
+ */
+ private static final boolean EXPAND_NODES_DEFAULT = false;
+
+ /**
+ * If active, nodes are expanded to fill the area of their parent.
+ */
+ public static final IProperty EXPAND_NODES = new Property(
+ "org.eclipse.elk.expandNodes",
+ EXPAND_NODES_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #HIERARCHY_HANDLING}.
+ */
+ private static final HierarchyHandling HIERARCHY_HANDLING_DEFAULT = HierarchyHandling.INHERIT;
+
+ /**
+ * Determines whether separate layout runs are triggered for different compound nodes in a
+ * hierarchical graph. Setting a node's hierarchy handling to `INCLUDE_CHILDREN` will lay
+ * out that node and all of its descendants in a single layout run, until a descendant is
+ * encountered which has its hierarchy handling set to `SEPARATE_CHILDREN`. In general,
+ * `SEPARATE_CHILDREN` will ensure that a new layout run is triggered for a node with that
+ * setting. Including multiple levels of hierarchy in a single layout run may allow
+ * cross-hierarchical edges to be laid out properly. If the root node is set to `INHERIT`
+ * (or not set at all), the default behavior is `SEPARATE_CHILDREN`.
+ */
+ public static final IProperty HIERARCHY_HANDLING = new Property(
+ "org.eclipse.elk.hierarchyHandling",
+ HIERARCHY_HANDLING_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #PADDING}.
+ */
+ private static final ElkPadding PADDING_DEFAULT = new ElkPadding(12);
+
+ /**
+ * The padding to be left to a parent element's border when placing child elements. This can
+ * also serve as an output option of a layout algorithm if node size calculation is setup
+ * appropriately.
+ */
+ public static final IProperty PADDING = new Property(
+ "org.eclipse.elk.padding",
+ PADDING_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #INTERACTIVE}.
+ */
+ private static final boolean INTERACTIVE_DEFAULT = false;
+
+ /**
+ * Whether the algorithm should be run in interactive mode for the content of a parent node.
+ * What this means exactly depends on how the specific algorithm interprets this option.
+ * Usually in the interactive mode algorithms try to modify the current layout as little as
+ * possible.
+ */
+ public static final IProperty INTERACTIVE = new Property(
+ "org.eclipse.elk.interactive",
+ INTERACTIVE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #INTERACTIVE_LAYOUT}.
+ */
+ private static final boolean INTERACTIVE_LAYOUT_DEFAULT = false;
+
+ /**
+ * Whether the graph should be changeable interactively and by setting constraints
+ */
+ public static final IProperty INTERACTIVE_LAYOUT = new Property(
+ "org.eclipse.elk.interactiveLayout",
+ INTERACTIVE_LAYOUT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #OMIT_NODE_MICRO_LAYOUT}.
+ */
+ private static final boolean OMIT_NODE_MICRO_LAYOUT_DEFAULT = false;
+
+ /**
+ * Node micro layout comprises the computation of node dimensions (if requested), the placement of ports
+ * and their labels, and the placement of node labels.
+ * The functionality is implemented independent of any specific layout algorithm and shouldn't have any
+ * negative impact on the layout algorithm's performance itself. Yet, if any unforeseen behavior occurs,
+ * this option allows to deactivate the micro layout.
+ */
+ public static final IProperty OMIT_NODE_MICRO_LAYOUT = new Property(
+ "org.eclipse.elk.omitNodeMicroLayout",
+ OMIT_NODE_MICRO_LAYOUT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #PORT_CONSTRAINTS}.
+ */
+ private static final PortConstraints PORT_CONSTRAINTS_DEFAULT = PortConstraints.UNDEFINED;
+
+ /**
+ * Defines constraints of the position of the ports of a node.
+ */
+ public static final IProperty PORT_CONSTRAINTS = new Property(
+ "org.eclipse.elk.portConstraints",
+ PORT_CONSTRAINTS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * The position of a node, port, or label. This is used by the 'Fixed Layout' algorithm to
+ * specify a pre-defined position.
+ */
+ public static final IProperty POSITION = new Property(
+ "org.eclipse.elk.position");
+
+ /**
+ * Defines the priority of an object; its meaning depends on the specific layout algorithm
+ * and the context where it is used.
+ */
+ public static final IProperty PRIORITY = new Property(
+ "org.eclipse.elk.priority");
+
+ /**
+ * Seed used for pseudo-random number generators to control the layout algorithm. If the
+ * value is 0, the seed shall be determined pseudo-randomly (e.g. from the system time).
+ */
+ public static final IProperty RANDOM_SEED = new Property(
+ "org.eclipse.elk.randomSeed");
+
+ /**
+ * Whether each connected component should be processed separately.
+ */
+ public static final IProperty SEPARATE_CONNECTED_COMPONENTS = new Property(
+ "org.eclipse.elk.separateConnectedComponents");
+
+ /**
+ * Default value for {@link #JUNCTION_POINTS}.
+ */
+ private static final KVectorChain JUNCTION_POINTS_DEFAULT = new KVectorChain();
+
+ /**
+ * This option is not used as option, but as output of the layout algorithms. It is
+ * attached to edges and determines the points where junction symbols should be drawn in
+ * order to represent hyperedges with orthogonal routing. Whether such points are computed
+ * depends on the chosen layout algorithm and edge routing style. The points are put into
+ * the vector chain with no specific order.
+ */
+ public static final IProperty JUNCTION_POINTS = new Property(
+ "org.eclipse.elk.junctionPoints",
+ JUNCTION_POINTS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #COMMENT_BOX}.
+ */
+ private static final boolean COMMENT_BOX_DEFAULT = false;
+
+ /**
+ * Whether the node should be regarded as a comment box instead of a regular node. In that
+ * case its placement should be similar to how labels are handled. Any edges incident to a
+ * comment box specify to which graph elements the comment is related.
+ */
+ public static final IProperty COMMENT_BOX = new Property(
+ "org.eclipse.elk.commentBox",
+ COMMENT_BOX_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #HYPERNODE}.
+ */
+ private static final boolean HYPERNODE_DEFAULT = false;
+
+ /**
+ * Whether the node should be handled as a hypernode.
+ */
+ public static final IProperty HYPERNODE = new Property(
+ "org.eclipse.elk.hypernode",
+ HYPERNODE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Label managers can shorten labels upon a layout algorithm's request.
+ */
+ public static final IProperty LABEL_MANAGER = new Property(
+ "org.eclipse.elk.labelManager");
+
+ /**
+ * Default value for {@link #SOFTWRAPPING_FUZZINESS}.
+ */
+ private static final double SOFTWRAPPING_FUZZINESS_DEFAULT = 0.0;
+
+ /**
+ * Determines the amount of fuzziness to be used when performing softwrapping on labels.
+ * The value expresses the percent of overhang that is permitted for each line.
+ * If the next line would take up less space than this threshold, it is appended to the
+ * current line instead of being placed in a new line.
+ */
+ public static final IProperty SOFTWRAPPING_FUZZINESS = new Property(
+ "org.eclipse.elk.softwrappingFuzziness",
+ SOFTWRAPPING_FUZZINESS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #MARGINS}.
+ */
+ private static final ElkMargin MARGINS_DEFAULT = new ElkMargin();
+
+ /**
+ * Margins define additional space around the actual bounds of a graph element. For instance,
+ * ports or labels being placed on the outside of a node's border might introduce such a
+ * margin. The margin is used to guarantee non-overlap of other graph elements with those
+ * ports or labels.
+ */
+ public static final IProperty MARGINS = new Property(
+ "org.eclipse.elk.margins",
+ MARGINS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #NO_LAYOUT}.
+ */
+ private static final boolean NO_LAYOUT_DEFAULT = false;
+
+ /**
+ * No layout is done for the associated element. This is used to mark parts of a diagram to
+ * avoid their inclusion in the layout graph, or to mark parts of the layout graph to
+ * prevent layout engines from processing them. If you wish to exclude the contents of a
+ * compound node from automatic layout, while the node itself is still considered on its own
+ * layer, use the 'Fixed Layout' algorithm for that node.
+ */
+ public static final IProperty NO_LAYOUT = new Property(
+ "org.eclipse.elk.noLayout",
+ NO_LAYOUT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #SCALE_FACTOR}.
+ */
+ private static final double SCALE_FACTOR_DEFAULT = 1;
+
+ /**
+ * Lower bound value for {@link #SCALE_FACTOR}.
+ */
+ private static final Comparable super Double> SCALE_FACTOR_LOWER_BOUND = ExclusiveBounds.greaterThan(0);
+
+ /**
+ * The scaling factor to be applied to the corresponding node in recursive layout. It causes
+ * the corresponding node's size to be adjusted, and its ports and labels to be sized and
+ * placed accordingly after the layout of that node has been determined (and before the node
+ * itself and its siblings are arranged). The scaling is not reverted afterwards, so the
+ * resulting layout graph contains the adjusted size and position data. This option is
+ * currently not supported if 'Layout Hierarchy' is set.
+ */
+ public static final IProperty SCALE_FACTOR = new Property(
+ "org.eclipse.elk.scaleFactor",
+ SCALE_FACTOR_DEFAULT,
+ SCALE_FACTOR_LOWER_BOUND,
+ null);
+
+ /**
+ * The width of the area occupied by the laid out children of a node.
+ */
+ public static final IProperty CHILD_AREA_WIDTH = new Property(
+ "org.eclipse.elk.childAreaWidth");
+
+ /**
+ * The height of the area occupied by the laid out children of a node.
+ */
+ public static final IProperty CHILD_AREA_HEIGHT = new Property(
+ "org.eclipse.elk.childAreaHeight");
+
+ /**
+ * Default value for {@link #TOPDOWN_LAYOUT}.
+ */
+ private static final boolean TOPDOWN_LAYOUT_DEFAULT = false;
+
+ /**
+ * Turns topdown layout on and off. If this option is enabled, hierarchical layout will be computed first for
+ * the root node and then for its children recursively. Layouts are then scaled down to fit the area provided by
+ * their parents. Graphs must follow a certain structure for topdown layout to work properly.
+ * {@link TopdownNodeTypes.PARALLEL_NODE} nodes must have children of type
+ * {@link TopdownNodeTypes.HIERARCHICAL_NODE} and must define {@link topdown.hierarchicalNodeWidth} and
+ * {@link topdown.hierarchicalNodeAspectRatio} for their children. Furthermore they need to be laid out using an
+ * algorithm that is a {@link TopdownLayoutProvider}. Hierarchical nodes can also be parents of other hierarchical
+ * nodes and can optionally use a {@link TopdownSizeApproximator} to dynamically set sizes during topdown layout.
+ * In this case {@link topdown.hierarchicalNodeWidth} and {@link topdown.hierarchicalNodeAspectRatio} should be set
+ * on the node itself rather than the parent. The values are then used by the size approximator as base values.
+ * Hierarchical nodes require the layout option {@link nodeSize.fixedGraphSize} to be true to prevent the algorithm
+ * used there from resizing the hierarchical node. This option is not supported if 'Hierarchy Handling' is set to
+ * 'INCLUDE_CHILDREN'
+ */
+ public static final IProperty TOPDOWN_LAYOUT = new Property(
+ "org.eclipse.elk.topdownLayout",
+ TOPDOWN_LAYOUT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #ANIMATE}.
+ */
+ private static final boolean ANIMATE_DEFAULT = true;
+
+ /**
+ * Whether the shift from the old layout to the new computed layout shall be animated.
+ */
+ public static final IProperty ANIMATE = new Property(
+ "org.eclipse.elk.animate",
+ ANIMATE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #ANIM_TIME_FACTOR}.
+ */
+ private static final int ANIM_TIME_FACTOR_DEFAULT = 100;
+
+ /**
+ * Lower bound value for {@link #ANIM_TIME_FACTOR}.
+ */
+ private static final Comparable super Integer> ANIM_TIME_FACTOR_LOWER_BOUND = Integer.valueOf(0);
+
+ /**
+ * Factor for computation of animation time. The higher the value, the longer the animation
+ * time. If the value is 0, the resulting time is always equal to the minimum defined by
+ * 'Minimal Animation Time'.
+ */
+ public static final IProperty ANIM_TIME_FACTOR = new Property(
+ "org.eclipse.elk.animTimeFactor",
+ ANIM_TIME_FACTOR_DEFAULT,
+ ANIM_TIME_FACTOR_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #LAYOUT_ANCESTORS}.
+ */
+ private static final boolean LAYOUT_ANCESTORS_DEFAULT = false;
+
+ /**
+ * Whether the hierarchy levels on the path from the selected element to the root of the
+ * diagram shall be included in the layout process.
+ */
+ public static final IProperty LAYOUT_ANCESTORS = new Property(
+ "org.eclipse.elk.layoutAncestors",
+ LAYOUT_ANCESTORS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #MAX_ANIM_TIME}.
+ */
+ private static final int MAX_ANIM_TIME_DEFAULT = 4000;
+
+ /**
+ * Lower bound value for {@link #MAX_ANIM_TIME}.
+ */
+ private static final Comparable super Integer> MAX_ANIM_TIME_LOWER_BOUND = Integer.valueOf(0);
+
+ /**
+ * The maximal time for animations, in milliseconds.
+ */
+ public static final IProperty MAX_ANIM_TIME = new Property(
+ "org.eclipse.elk.maxAnimTime",
+ MAX_ANIM_TIME_DEFAULT,
+ MAX_ANIM_TIME_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #MIN_ANIM_TIME}.
+ */
+ private static final int MIN_ANIM_TIME_DEFAULT = 400;
+
+ /**
+ * Lower bound value for {@link #MIN_ANIM_TIME}.
+ */
+ private static final Comparable super Integer> MIN_ANIM_TIME_LOWER_BOUND = Integer.valueOf(0);
+
+ /**
+ * The minimal time for animations, in milliseconds.
+ */
+ public static final IProperty MIN_ANIM_TIME = new Property(
+ "org.eclipse.elk.minAnimTime",
+ MIN_ANIM_TIME_DEFAULT,
+ MIN_ANIM_TIME_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #PROGRESS_BAR}.
+ */
+ private static final boolean PROGRESS_BAR_DEFAULT = false;
+
+ /**
+ * Whether a progress bar shall be displayed during layout computations.
+ */
+ public static final IProperty PROGRESS_BAR = new Property(
+ "org.eclipse.elk.progressBar",
+ PROGRESS_BAR_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #VALIDATE_GRAPH}.
+ */
+ private static final boolean VALIDATE_GRAPH_DEFAULT = false;
+
+ /**
+ * Whether the graph shall be validated before any layout algorithm is applied. If this
+ * option is enabled and at least one error is found, the layout process is aborted and a message
+ * is shown to the user.
+ */
+ public static final IProperty VALIDATE_GRAPH = new Property(
+ "org.eclipse.elk.validateGraph",
+ VALIDATE_GRAPH_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #VALIDATE_OPTIONS}.
+ */
+ private static final boolean VALIDATE_OPTIONS_DEFAULT = true;
+
+ /**
+ * Whether layout options shall be validated before any layout algorithm is applied. If this
+ * option is enabled and at least one error is found, the layout process is aborted and a message
+ * is shown to the user.
+ */
+ public static final IProperty VALIDATE_OPTIONS = new Property(
+ "org.eclipse.elk.validateOptions",
+ VALIDATE_OPTIONS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #ZOOM_TO_FIT}.
+ */
+ private static final boolean ZOOM_TO_FIT_DEFAULT = false;
+
+ /**
+ * Whether the zoom level shall be set to view the whole diagram after layout.
+ */
+ public static final IProperty ZOOM_TO_FIT = new Property(
+ "org.eclipse.elk.zoomToFit",
+ ZOOM_TO_FIT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #BOX_PACKING_MODE}.
+ */
+ private static final BoxLayoutProvider.PackingMode BOX_PACKING_MODE_DEFAULT = BoxLayoutProvider.PackingMode.SIMPLE;
+
+ /**
+ * Configures the packing mode used by the {@link BoxLayoutProvider}.
+ * If SIMPLE is not required (neither priorities are used nor the interactive mode),
+ * GROUP_DEC can improve the packing and decrease the area.
+ * GROUP_MIXED and GROUP_INC may, in very specific scenarios, work better.
+ */
+ public static final IProperty BOX_PACKING_MODE = new Property(
+ "org.eclipse.elk.box.packingMode",
+ BOX_PACKING_MODE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #JSON_SHAPE_COORDS}.
+ */
+ private static final ShapeCoords JSON_SHAPE_COORDS_DEFAULT = ShapeCoords.INHERIT;
+
+ /**
+ * For layouts transferred into JSON graphs, specify the coordinate system
+ * to be used for nodes, ports, and labels of nodes and ports.
+ */
+ public static final IProperty JSON_SHAPE_COORDS = new Property(
+ "org.eclipse.elk.json.shapeCoords",
+ JSON_SHAPE_COORDS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #JSON_EDGE_COORDS}.
+ */
+ private static final EdgeCoords JSON_EDGE_COORDS_DEFAULT = EdgeCoords.INHERIT;
+
+ /**
+ * For layouts transferred into JSON graphs, specify the coordinate system
+ * to be used for edge route points and edge labels.
+ */
+ public static final IProperty JSON_EDGE_COORDS = new Property(
+ "org.eclipse.elk.json.edgeCoords",
+ JSON_EDGE_COORDS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_COMMENT_COMMENT}.
+ */
+ private static final double SPACING_COMMENT_COMMENT_DEFAULT = 10;
+
+ /**
+ * Lower bound value for {@link #SPACING_COMMENT_COMMENT}.
+ */
+ private static final Comparable super Double> SPACING_COMMENT_COMMENT_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between a comment box and other comment boxes connected to the same node.
+ * The space left between comment boxes of different nodes is controlled by the node-node spacing.
+ */
+ public static final IProperty SPACING_COMMENT_COMMENT = new Property(
+ "org.eclipse.elk.spacing.commentComment",
+ SPACING_COMMENT_COMMENT_DEFAULT,
+ SPACING_COMMENT_COMMENT_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_COMMENT_NODE}.
+ */
+ private static final double SPACING_COMMENT_NODE_DEFAULT = 10;
+
+ /**
+ * Lower bound value for {@link #SPACING_COMMENT_NODE}.
+ */
+ private static final Comparable super Double> SPACING_COMMENT_NODE_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between a node and its connected comment boxes. The space left between a node
+ * and the comments of another node is controlled by the node-node spacing.
+ */
+ public static final IProperty SPACING_COMMENT_NODE = new Property(
+ "org.eclipse.elk.spacing.commentNode",
+ SPACING_COMMENT_NODE_DEFAULT,
+ SPACING_COMMENT_NODE_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_COMPONENT_COMPONENT}.
+ */
+ private static final double SPACING_COMPONENT_COMPONENT_DEFAULT = 20f;
+
+ /**
+ * Lower bound value for {@link #SPACING_COMPONENT_COMPONENT}.
+ */
+ private static final Comparable super Double> SPACING_COMPONENT_COMPONENT_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between pairs of connected components.
+ * This option is only relevant if 'separateConnectedComponents' is activated.
+ */
+ public static final IProperty SPACING_COMPONENT_COMPONENT = new Property(
+ "org.eclipse.elk.spacing.componentComponent",
+ SPACING_COMPONENT_COMPONENT_DEFAULT,
+ SPACING_COMPONENT_COMPONENT_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_EDGE_EDGE}.
+ */
+ private static final double SPACING_EDGE_EDGE_DEFAULT = 10;
+
+ /**
+ * Lower bound value for {@link #SPACING_EDGE_EDGE}.
+ */
+ private static final Comparable super Double> SPACING_EDGE_EDGE_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between any two edges. Note that while this can somewhat easily be satisfied
+ * for the segments of orthogonally drawn edges, it is harder for general polylines or splines.
+ */
+ public static final IProperty SPACING_EDGE_EDGE = new Property(
+ "org.eclipse.elk.spacing.edgeEdge",
+ SPACING_EDGE_EDGE_DEFAULT,
+ SPACING_EDGE_EDGE_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_EDGE_LABEL}.
+ */
+ private static final double SPACING_EDGE_LABEL_DEFAULT = 2;
+
+ /**
+ * Lower bound value for {@link #SPACING_EDGE_LABEL}.
+ */
+ private static final Comparable super Double> SPACING_EDGE_LABEL_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * The minimal distance to be preserved between a label and the edge it is associated with.
+ * Note that the placement of a label is influenced by the 'edgelabels.placement' option.
+ */
+ public static final IProperty SPACING_EDGE_LABEL = new Property(
+ "org.eclipse.elk.spacing.edgeLabel",
+ SPACING_EDGE_LABEL_DEFAULT,
+ SPACING_EDGE_LABEL_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_EDGE_NODE}.
+ */
+ private static final double SPACING_EDGE_NODE_DEFAULT = 10;
+
+ /**
+ * Lower bound value for {@link #SPACING_EDGE_NODE}.
+ */
+ private static final Comparable super Double> SPACING_EDGE_NODE_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between nodes and edges.
+ */
+ public static final IProperty SPACING_EDGE_NODE = new Property(
+ "org.eclipse.elk.spacing.edgeNode",
+ SPACING_EDGE_NODE_DEFAULT,
+ SPACING_EDGE_NODE_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_LABEL_LABEL}.
+ */
+ private static final double SPACING_LABEL_LABEL_DEFAULT = 0;
+
+ /**
+ * Lower bound value for {@link #SPACING_LABEL_LABEL}.
+ */
+ private static final Comparable super Double> SPACING_LABEL_LABEL_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Determines the amount of space to be left between two labels
+ * of the same graph element.
+ */
+ public static final IProperty SPACING_LABEL_LABEL = new Property(
+ "org.eclipse.elk.spacing.labelLabel",
+ SPACING_LABEL_LABEL_DEFAULT,
+ SPACING_LABEL_LABEL_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_LABEL_NODE}.
+ */
+ private static final double SPACING_LABEL_NODE_DEFAULT = 5;
+
+ /**
+ * Lower bound value for {@link #SPACING_LABEL_NODE}.
+ */
+ private static final Comparable super Double> SPACING_LABEL_NODE_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between labels and the border of node they are associated with.
+ * Note that the placement of a label is influenced by the 'nodelabels.placement' option.
+ */
+ public static final IProperty SPACING_LABEL_NODE = new Property(
+ "org.eclipse.elk.spacing.labelNode",
+ SPACING_LABEL_NODE_DEFAULT,
+ SPACING_LABEL_NODE_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_LABEL_PORT_HORIZONTAL}.
+ */
+ private static final double SPACING_LABEL_PORT_HORIZONTAL_DEFAULT = 1;
+
+ /**
+ * Horizontal spacing to be preserved between labels and the ports they are associated with.
+ * Note that the placement of a label is influenced by the 'portlabels.placement' option.
+ */
+ public static final IProperty SPACING_LABEL_PORT_HORIZONTAL = new Property(
+ "org.eclipse.elk.spacing.labelPortHorizontal",
+ SPACING_LABEL_PORT_HORIZONTAL_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_LABEL_PORT_VERTICAL}.
+ */
+ private static final double SPACING_LABEL_PORT_VERTICAL_DEFAULT = 1;
+
+ /**
+ * Vertical spacing to be preserved between labels and the ports they are associated with.
+ * Note that the placement of a label is influenced by the 'portlabels.placement' option.
+ */
+ public static final IProperty SPACING_LABEL_PORT_VERTICAL = new Property(
+ "org.eclipse.elk.spacing.labelPortVertical",
+ SPACING_LABEL_PORT_VERTICAL_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_NODE_NODE}.
+ */
+ private static final double SPACING_NODE_NODE_DEFAULT = 20;
+
+ /**
+ * Lower bound value for {@link #SPACING_NODE_NODE}.
+ */
+ private static final Comparable super Double> SPACING_NODE_NODE_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * The minimal distance to be preserved between each two nodes.
+ */
+ public static final IProperty SPACING_NODE_NODE = new Property(
+ "org.eclipse.elk.spacing.nodeNode",
+ SPACING_NODE_NODE_DEFAULT,
+ SPACING_NODE_NODE_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_NODE_SELF_LOOP}.
+ */
+ private static final double SPACING_NODE_SELF_LOOP_DEFAULT = 10;
+
+ /**
+ * Lower bound value for {@link #SPACING_NODE_SELF_LOOP}.
+ */
+ private static final Comparable super Double> SPACING_NODE_SELF_LOOP_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing to be preserved between a node and its self loops.
+ */
+ public static final IProperty SPACING_NODE_SELF_LOOP = new Property(
+ "org.eclipse.elk.spacing.nodeSelfLoop",
+ SPACING_NODE_SELF_LOOP_DEFAULT,
+ SPACING_NODE_SELF_LOOP_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #SPACING_PORT_PORT}.
+ */
+ private static final double SPACING_PORT_PORT_DEFAULT = 10;
+
+ /**
+ * Lower bound value for {@link #SPACING_PORT_PORT}.
+ */
+ private static final Comparable super Double> SPACING_PORT_PORT_LOWER_BOUND = Double.valueOf(0.0);
+
+ /**
+ * Spacing between pairs of ports of the same node.
+ */
+ public static final IProperty SPACING_PORT_PORT = new Property(
+ "org.eclipse.elk.spacing.portPort",
+ SPACING_PORT_PORT_DEFAULT,
+ SPACING_PORT_PORT_LOWER_BOUND,
+ null);
+
+ /**
+ * Allows to specify individual spacing values for graph elements that shall be different from
+ * the value specified for the element's parent.
+ */
+ public static final IProperty SPACING_INDIVIDUAL = new Property(
+ "org.eclipse.elk.spacing.individual");
+
+ /**
+ * Default value for {@link #SPACING_PORTS_SURROUNDING}.
+ */
+ private static final ElkMargin SPACING_PORTS_SURROUNDING_DEFAULT = new ElkMargin(0);
+
+ /**
+ * Additional space around the sets of ports on each node side. For each side of a node,
+ * this option can reserve additional space before and after the ports on each side. For
+ * example, a top spacing of 20 makes sure that the first port on the western and eastern
+ * side is 20 units away from the northern border.
+ */
+ public static final IProperty SPACING_PORTS_SURROUNDING = new Property(
+ "org.eclipse.elk.spacing.portsSurrounding",
+ SPACING_PORTS_SURROUNDING_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Partition to which the node belongs. This requires Layout Partitioning to be active. Nodes with lower
+ * partition IDs will appear to the left of nodes with higher partition IDs (assuming a left-to-right layout
+ * direction).
+ */
+ public static final IProperty PARTITIONING_PARTITION = new Property(
+ "org.eclipse.elk.partitioning.partition");
+
+ /**
+ * Default value for {@link #PARTITIONING_ACTIVATE}.
+ */
+ private static final Boolean PARTITIONING_ACTIVATE_DEFAULT = Boolean.valueOf(false);
+
+ /**
+ * Whether to activate partitioned layout. This will allow to group nodes through the Layout Partition option.
+ * a pair of nodes with different partition indices is then placed such that the node with lower index is
+ * placed to the left of the other node (with left-to-right layout direction). Depending on the layout
+ * algorithm, this may only be guaranteed to work if all nodes have a layout partition configured, or at least
+ * if edges that cross partitions are not part of a partition-crossing cycle.
+ */
+ public static final IProperty PARTITIONING_ACTIVATE = new Property(
+ "org.eclipse.elk.partitioning.activate",
+ PARTITIONING_ACTIVATE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #NODE_LABELS_PADDING}.
+ */
+ private static final ElkPadding NODE_LABELS_PADDING_DEFAULT = new ElkPadding(5);
+
+ /**
+ * Define padding for node labels that are placed inside of a node.
+ */
+ public static final IProperty NODE_LABELS_PADDING = new Property(
+ "org.eclipse.elk.nodeLabels.padding",
+ NODE_LABELS_PADDING_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #NODE_LABELS_PLACEMENT}.
+ */
+ private static final EnumSet NODE_LABELS_PLACEMENT_DEFAULT = NodeLabelPlacement.fixed();
+
+ /**
+ * Hints for where node labels are to be placed; if empty, the node label's position is not
+ * modified.
+ */
+ public static final IProperty> NODE_LABELS_PLACEMENT = new Property>(
+ "org.eclipse.elk.nodeLabels.placement",
+ NODE_LABELS_PLACEMENT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #PORT_ALIGNMENT_DEFAULT}.
+ */
+ private static final PortAlignment PORT_ALIGNMENT_DEFAULT_DEFAULT = PortAlignment.DISTRIBUTED;
+
+ /**
+ * Defines the default port distribution for a node. May be overridden for each side individually.
+ */
+ public static final IProperty PORT_ALIGNMENT_DEFAULT = new Property(
+ "org.eclipse.elk.portAlignment.default",
+ PORT_ALIGNMENT_DEFAULT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Defines how ports on the northern side are placed, overriding the node's general port alignment.
+ */
+ public static final IProperty PORT_ALIGNMENT_NORTH = new Property(
+ "org.eclipse.elk.portAlignment.north");
+
+ /**
+ * Defines how ports on the southern side are placed, overriding the node's general port alignment.
+ */
+ public static final IProperty PORT_ALIGNMENT_SOUTH = new Property(
+ "org.eclipse.elk.portAlignment.south");
+
+ /**
+ * Defines how ports on the western side are placed, overriding the node's general port alignment.
+ */
+ public static final IProperty PORT_ALIGNMENT_WEST = new Property(
+ "org.eclipse.elk.portAlignment.west");
+
+ /**
+ * Defines how ports on the eastern side are placed, overriding the node's general port alignment.
+ */
+ public static final IProperty PORT_ALIGNMENT_EAST = new Property(
+ "org.eclipse.elk.portAlignment.east");
+
+ /**
+ * Default value for {@link #NODE_SIZE_CONSTRAINTS}.
+ */
+ private static final EnumSet NODE_SIZE_CONSTRAINTS_DEFAULT = EnumSet.noneOf(SizeConstraint.class);
+
+ /**
+ * What should be taken into account when calculating a node's size. Empty size constraints
+ * specify that a node's size is already fixed and should not be changed.
+ */
+ public static final IProperty> NODE_SIZE_CONSTRAINTS = new Property>(
+ "org.eclipse.elk.nodeSize.constraints",
+ NODE_SIZE_CONSTRAINTS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #NODE_SIZE_OPTIONS}.
+ */
+ private static final EnumSet NODE_SIZE_OPTIONS_DEFAULT = EnumSet.of(SizeOptions.DEFAULT_MINIMUM_SIZE);
+
+ /**
+ * Options modifying the behavior of the size constraints set on a node. Each member of the
+ * set specifies something that should be taken into account when calculating node sizes.
+ * The empty set corresponds to no further modifications.
+ */
+ public static final IProperty> NODE_SIZE_OPTIONS = new Property>(
+ "org.eclipse.elk.nodeSize.options",
+ NODE_SIZE_OPTIONS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #NODE_SIZE_MINIMUM}.
+ */
+ private static final KVector NODE_SIZE_MINIMUM_DEFAULT = new KVector(0, 0);
+
+ /**
+ * The minimal size to which a node can be reduced.
+ */
+ public static final IProperty NODE_SIZE_MINIMUM = new Property(
+ "org.eclipse.elk.nodeSize.minimum",
+ NODE_SIZE_MINIMUM_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #NODE_SIZE_FIXED_GRAPH_SIZE}.
+ */
+ private static final boolean NODE_SIZE_FIXED_GRAPH_SIZE_DEFAULT = false;
+
+ /**
+ * By default, the fixed layout provider will enlarge a graph until it is large enough to contain
+ * its children. If this option is set, it won't do so.
+ */
+ public static final IProperty NODE_SIZE_FIXED_GRAPH_SIZE = new Property(
+ "org.eclipse.elk.nodeSize.fixedGraphSize",
+ NODE_SIZE_FIXED_GRAPH_SIZE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #EDGE_LABELS_PLACEMENT}.
+ */
+ private static final EdgeLabelPlacement EDGE_LABELS_PLACEMENT_DEFAULT = EdgeLabelPlacement.CENTER;
+
+ /**
+ * Gives a hint on where to put edge labels.
+ */
+ public static final IProperty EDGE_LABELS_PLACEMENT = new Property(
+ "org.eclipse.elk.edgeLabels.placement",
+ EDGE_LABELS_PLACEMENT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #EDGE_LABELS_INLINE}.
+ */
+ private static final boolean EDGE_LABELS_INLINE_DEFAULT = false;
+
+ /**
+ * If true, an edge label is placed directly on its edge. May only apply to center edge labels.
+ * This kind of label placement is only advisable if the label's rendering is such that it is not
+ * crossed by its edge and thus stays legible.
+ */
+ public static final IProperty EDGE_LABELS_INLINE = new Property(
+ "org.eclipse.elk.edgeLabels.inline",
+ EDGE_LABELS_INLINE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Font name used for a label.
+ */
+ public static final IProperty FONT_NAME = new Property(
+ "org.eclipse.elk.font.name");
+
+ /**
+ * Lower bound value for {@link #FONT_SIZE}.
+ */
+ private static final Comparable super Integer> FONT_SIZE_LOWER_BOUND = Integer.valueOf(1);
+
+ /**
+ * Font size used for a label.
+ */
+ public static final IProperty FONT_SIZE = new Property(
+ "org.eclipse.elk.font.size",
+ null,
+ FONT_SIZE_LOWER_BOUND,
+ null);
+
+ /**
+ * The offset to the port position where connections shall be attached.
+ */
+ public static final IProperty PORT_ANCHOR = new Property(
+ "org.eclipse.elk.port.anchor");
+
+ /**
+ * The index of a port in the fixed order around a node. The order is assumed as clockwise,
+ * starting with the leftmost port on the top side. This option must be set if 'Port
+ * Constraints' is set to FIXED_ORDER and no specific positions are given for the ports.
+ * Additionally, the option 'Port Side' must be defined in this case.
+ */
+ public static final IProperty PORT_INDEX = new Property(
+ "org.eclipse.elk.port.index");
+
+ /**
+ * Default value for {@link #PORT_SIDE}.
+ */
+ private static final PortSide PORT_SIDE_DEFAULT = PortSide.UNDEFINED;
+
+ /**
+ * The side of a node on which a port is situated. This option must be set if 'Port
+ * Constraints' is set to FIXED_SIDE or FIXED_ORDER and no specific positions are given
+ * for the ports.
+ */
+ public static final IProperty PORT_SIDE = new Property(
+ "org.eclipse.elk.port.side",
+ PORT_SIDE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * The offset of ports on the node border. With a positive offset the port is moved outside
+ * of the node, while with a negative offset the port is moved towards the inside. An offset
+ * of 0 means that the port is placed directly on the node border, i.e.
+ * if the port side is north, the port's south border touches the nodes's north border;
+ * if the port side is east, the port's west border touches the nodes's east border;
+ * if the port side is south, the port's north border touches the node's south border;
+ * if the port side is west, the port's east border touches the node's west border.
+ */
+ public static final IProperty PORT_BORDER_OFFSET = new Property(
+ "org.eclipse.elk.port.borderOffset");
+
+ /**
+ * Default value for {@link #PORT_LABELS_PLACEMENT}.
+ */
+ private static final EnumSet PORT_LABELS_PLACEMENT_DEFAULT = PortLabelPlacement.outside();
+
+ /**
+ * Decides on a placement method for port labels; if empty, the node label's position is not
+ * modified.
+ */
+ public static final IProperty> PORT_LABELS_PLACEMENT = new Property>(
+ "org.eclipse.elk.portLabels.placement",
+ PORT_LABELS_PLACEMENT_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE}.
+ */
+ private static final boolean PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE_DEFAULT = false;
+
+ /**
+ * Use 'portLabels.placement': NEXT_TO_PORT_OF_POSSIBLE.
+ */
+ @Deprecated
+ public static final IProperty PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE = new Property(
+ "org.eclipse.elk.portLabels.nextToPortIfPossible",
+ PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #PORT_LABELS_TREAT_AS_GROUP}.
+ */
+ private static final boolean PORT_LABELS_TREAT_AS_GROUP_DEFAULT = true;
+
+ /**
+ * If this option is true (default), the labels of a port will be treated as a group when
+ * it comes to centering them next to their port. If this option is false, only the first label will
+ * be centered next to the port, with the others being placed below. This only applies to labels of
+ * eastern and western ports and will have no effect if labels are not placed next to their port.
+ */
+ public static final IProperty PORT_LABELS_TREAT_AS_GROUP = new Property(
+ "org.eclipse.elk.portLabels.treatAsGroup",
+ PORT_LABELS_TREAT_AS_GROUP_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_SIZE_CATEGORIES}.
+ */
+ private static final int TOPDOWN_SIZE_CATEGORIES_DEFAULT = 3;
+
+ /**
+ * Lower bound value for {@link #TOPDOWN_SIZE_CATEGORIES}.
+ */
+ private static final Comparable super Integer> TOPDOWN_SIZE_CATEGORIES_LOWER_BOUND = Integer.valueOf(1);
+
+ /**
+ * Defines the number of categories to use for the FIXED_INTEGER_RATIO_BOXES size approximator.
+ */
+ public static final IProperty TOPDOWN_SIZE_CATEGORIES = new Property(
+ "org.eclipse.elk.topdown.sizeCategories",
+ TOPDOWN_SIZE_CATEGORIES_DEFAULT,
+ TOPDOWN_SIZE_CATEGORIES_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT}.
+ */
+ private static final int TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT_DEFAULT = 4;
+
+ /**
+ * Lower bound value for {@link #TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT}.
+ */
+ private static final Comparable super Integer> TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT_LOWER_BOUND = Integer.valueOf(1);
+
+ /**
+ * When determining the graph size for the size categorisation, this value determines how many times a node
+ * containing children is weighted more than a simple node. For example setting this value to four would
+ * result in a graph containing a simple node and a hierarchical node to be counted as having a size of five.
+ */
+ public static final IProperty TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT = new Property(
+ "org.eclipse.elk.topdown.sizeCategoriesHierarchicalNodeWeight",
+ TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT_DEFAULT,
+ TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_SCALE_FACTOR}.
+ */
+ private static final double TOPDOWN_SCALE_FACTOR_DEFAULT = 1;
+
+ /**
+ * Lower bound value for {@link #TOPDOWN_SCALE_FACTOR}.
+ */
+ private static final Comparable super Double> TOPDOWN_SCALE_FACTOR_LOWER_BOUND = ExclusiveBounds.greaterThan(0);
+
+ /**
+ * The scaling factor to be applied to the nodes laid out within the node in recursive topdown
+ * layout. The difference to 'Scale Factor' is that the node itself is not scaled. This value has to be set on
+ * hierarchical nodes.
+ */
+ public static final IProperty TOPDOWN_SCALE_FACTOR = new Property(
+ "org.eclipse.elk.topdown.scaleFactor",
+ TOPDOWN_SCALE_FACTOR_DEFAULT,
+ TOPDOWN_SCALE_FACTOR_LOWER_BOUND,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_SIZE_APPROXIMATOR}.
+ */
+ private static final ITopdownSizeApproximator TOPDOWN_SIZE_APPROXIMATOR_DEFAULT = null;
+
+ /**
+ * The size approximator to be used to set sizes of hierarchical nodes during topdown layout. The default
+ * value is null, which results in nodes keeping whatever size is defined for them e.g. through parent
+ * parallel node or by manually setting the size.
+ */
+ public static final IProperty TOPDOWN_SIZE_APPROXIMATOR = new Property(
+ "org.eclipse.elk.topdown.sizeApproximator",
+ TOPDOWN_SIZE_APPROXIMATOR_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_HIERARCHICAL_NODE_WIDTH}.
+ */
+ private static final double TOPDOWN_HIERARCHICAL_NODE_WIDTH_DEFAULT = 150;
+
+ /**
+ * The fixed size of a hierarchical node when using topdown layout. If this value is set on a parallel
+ * node it applies to its children, when set on a hierarchical node it applies to the node itself.
+ */
+ public static final IProperty TOPDOWN_HIERARCHICAL_NODE_WIDTH = new Property(
+ "org.eclipse.elk.topdown.hierarchicalNodeWidth",
+ TOPDOWN_HIERARCHICAL_NODE_WIDTH_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_HIERARCHICAL_NODE_ASPECT_RATIO}.
+ */
+ private static final double TOPDOWN_HIERARCHICAL_NODE_ASPECT_RATIO_DEFAULT = 1.414;
+
+ /**
+ * The fixed aspect ratio of a hierarchical node when using topdown layout. Default is 1/sqrt(2). If this
+ * value is set on a parallel node it applies to its children, when set on a hierarchical node it applies to
+ * the node itself.
+ */
+ public static final IProperty TOPDOWN_HIERARCHICAL_NODE_ASPECT_RATIO = new Property(
+ "org.eclipse.elk.topdown.hierarchicalNodeAspectRatio",
+ TOPDOWN_HIERARCHICAL_NODE_ASPECT_RATIO_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_NODE_TYPE}.
+ */
+ private static final TopdownNodeTypes TOPDOWN_NODE_TYPE_DEFAULT = null;
+
+ /**
+ * The different node types used for topdown layout. If the node type is set
+ * to {@link TopdownNodeTypes.PARALLEL_NODE} the algorithm must be set to a {@link TopdownLayoutProvider} such
+ * as {@link TopdownPacking}. The {@link nodeSize.fixedGraphSize} option is technically only required for
+ * hierarchical nodes.
+ */
+ public static final IProperty TOPDOWN_NODE_TYPE = new Property(
+ "org.eclipse.elk.topdown.nodeType",
+ TOPDOWN_NODE_TYPE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #TOPDOWN_SCALE_CAP}.
+ */
+ private static final double TOPDOWN_SCALE_CAP_DEFAULT = 1;
+
+ /**
+ * Determines the upper limit for the topdown scale factor. The default value is 1.0 which ensures
+ * that nested children never end up appearing larger than their parents in terms of unit sizes such
+ * as the font size. If the limit is larger, nodes will fully utilize the available space, but it is
+ * counteriniuitive for inner nodes to have a larger scale than outer nodes.
+ */
+ public static final IProperty TOPDOWN_SCALE_CAP = new Property(
+ "org.eclipse.elk.topdown.scaleCap",
+ TOPDOWN_SCALE_CAP_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #INSIDE_SELF_LOOPS_ACTIVATE}.
+ */
+ private static final boolean INSIDE_SELF_LOOPS_ACTIVATE_DEFAULT = false;
+
+ /**
+ * Whether this node allows to route self loops inside of it instead of around it. If set to true,
+ * this will make the node a compound node if it isn't already, and will require the layout algorithm
+ * to support compound nodes with hierarchical ports.
+ */
+ public static final IProperty INSIDE_SELF_LOOPS_ACTIVATE = new Property(
+ "org.eclipse.elk.insideSelfLoops.activate",
+ INSIDE_SELF_LOOPS_ACTIVATE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #INSIDE_SELF_LOOPS_YO}.
+ */
+ private static final boolean INSIDE_SELF_LOOPS_YO_DEFAULT = false;
+
+ /**
+ * Whether a self loop should be routed inside a node instead of around that node.
+ */
+ public static final IProperty INSIDE_SELF_LOOPS_YO = new Property(
+ "org.eclipse.elk.insideSelfLoops.yo",
+ INSIDE_SELF_LOOPS_YO_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #EDGE_THICKNESS}.
+ */
+ private static final double EDGE_THICKNESS_DEFAULT = 1;
+
+ /**
+ * The thickness of an edge. This is a hint on the line width used to draw an edge, possibly
+ * requiring more space to be reserved for it.
+ */
+ public static final IProperty EDGE_THICKNESS = new Property(
+ "org.eclipse.elk.edge.thickness",
+ EDGE_THICKNESS_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Default value for {@link #EDGE_TYPE}.
+ */
+ private static final EdgeType EDGE_TYPE_DEFAULT = EdgeType.NONE;
+
+ /**
+ * The type of an edge. This is usually used for UML class diagrams, where associations must
+ * be handled differently from generalizations.
+ */
+ public static final IProperty EDGE_TYPE = new Property(
+ "org.eclipse.elk.edge.type",
+ EDGE_TYPE_DEFAULT,
+ null,
+ null);
+
+ /**
+ * Required value for dependency between {@link #PARTITIONING_PARTITION} and {@link #PARTITIONING_ACTIVATE}.
+ */
+ private static final Boolean PARTITIONING_PARTITION_DEP_PARTITIONING_ACTIVATE_0 = Boolean.valueOf(true);
+
+ /**
+ * Required value for dependency between {@link #TOPDOWN_SIZE_CATEGORIES} and {@link #TOPDOWN_SIZE_APPROXIMATOR}.
+ */
+ private static final ITopdownSizeApproximator TOPDOWN_SIZE_CATEGORIES_DEP_TOPDOWN_SIZE_APPROXIMATOR_0 = TopdownSizeApproximator.FIXED_INTEGER_RATIO_BOXES;
+
+ /**
+ * Required value for dependency between {@link #TOPDOWN_SCALE_FACTOR} and {@link #TOPDOWN_NODE_TYPE}.
+ */
+ private static final TopdownNodeTypes TOPDOWN_SCALE_FACTOR_DEP_TOPDOWN_NODE_TYPE_0 = TopdownNodeTypes.HIERARCHICAL_NODE;
+
+ /**
+ * Required value for dependency between {@link #TOPDOWN_SIZE_APPROXIMATOR} and {@link #TOPDOWN_NODE_TYPE}.
+ */
+ private static final TopdownNodeTypes TOPDOWN_SIZE_APPROXIMATOR_DEP_TOPDOWN_NODE_TYPE_0 = TopdownNodeTypes.HIERARCHICAL_NODE;
+
+ /**
+ * Required value for dependency between {@link #TOPDOWN_SCALE_CAP} and {@link #TOPDOWN_NODE_TYPE}.
+ */
+ private static final TopdownNodeTypes TOPDOWN_SCALE_CAP_DEP_TOPDOWN_NODE_TYPE_0 = TopdownNodeTypes.HIERARCHICAL_NODE;
+
+ public void apply(final org.eclipse.elk.core.data.ILayoutMetaDataProvider.Registry registry) {
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.algorithm")
+ .group("")
+ .name("Layout Algorithm")
+ .description("Select a specific layout algorithm.")
+ .type(LayoutOptionData.Type.STRING)
+ .optionClass(String.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.resolvedAlgorithm")
+ .group("")
+ .name("Resolved Layout Algorithm")
+ .description("Meta data associated with the selected algorithm.")
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(LayoutAlgorithmData.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.alignment")
+ .group("")
+ .name("Alignment")
+ .description("Alignment of the selected node relative to other nodes; the exact meaning depends on the used algorithm.")
+ .defaultValue(ALIGNMENT_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(Alignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.aspectRatio")
+ .group("")
+ .name("Aspect Ratio")
+ .description("The desired aspect ratio of the drawing, that is the quotient of width by height.")
+ .lowerBound(ASPECT_RATIO_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.bendPoints")
+ .group("")
+ .name("Bend Points")
+ .description("A fixed list of bend points for the edge. This is used by the \'Fixed Layout\' algorithm to specify a pre-defined routing for an edge. The vector chain must include the source point, any bend points, and the target point, so it must have at least two points.")
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(KVectorChain.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.EDGES))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.contentAlignment")
+ .group("")
+ .name("Content Alignment")
+ .description("Specifies how the content of a node are aligned. Each node can individually control the alignment of its contents. I.e. if a node should be aligned top left in its parent node, the parent node should specify that option.")
+ .defaultValue(CONTENT_ALIGNMENT_DEFAULT)
+ .type(LayoutOptionData.Type.ENUMSET)
+ .optionClass(ContentAlignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.debugMode")
+ .group("")
+ .name("Debug Mode")
+ .description("Whether additional debug information shall be generated.")
+ .defaultValue(DEBUG_MODE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.direction")
+ .group("")
+ .name("Direction")
+ .description("Overall direction of edges: horizontal (right / left) or vertical (down / up).")
+ .defaultValue(DIRECTION_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(Direction.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.edgeRouting")
+ .group("")
+ .name("Edge Routing")
+ .description("What kind of edge routing style should be applied for the content of a parent node. Algorithms may also set this option to single edges in order to mark them as splines. The bend point list of edges with this option set to SPLINES must be interpreted as control points for a piecewise cubic spline.")
+ .defaultValue(EDGE_ROUTING_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(EdgeRouting.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.expandNodes")
+ .group("")
+ .name("Expand Nodes")
+ .description("If active, nodes are expanded to fill the area of their parent.")
+ .defaultValue(EXPAND_NODES_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.hierarchyHandling")
+ .group("")
+ .name("Hierarchy Handling")
+ .description("Determines whether separate layout runs are triggered for different compound nodes in a hierarchical graph. Setting a node\'s hierarchy handling to `INCLUDE_CHILDREN` will lay out that node and all of its descendants in a single layout run, until a descendant is encountered which has its hierarchy handling set to `SEPARATE_CHILDREN`. In general, `SEPARATE_CHILDREN` will ensure that a new layout run is triggered for a node with that setting. Including multiple levels of hierarchy in a single layout run may allow cross-hierarchical edges to be laid out properly. If the root node is set to `INHERIT` (or not set at all), the default behavior is `SEPARATE_CHILDREN`.")
+ .defaultValue(HIERARCHY_HANDLING_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(HierarchyHandling.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.padding")
+ .group("")
+ .name("Padding")
+ .description("The padding to be left to a parent element\'s border when placing child elements. This can also serve as an output option of a layout algorithm if node size calculation is setup appropriately.")
+ .defaultValue(PADDING_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(ElkPadding.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.interactive")
+ .group("")
+ .name("Interactive")
+ .description("Whether the algorithm should be run in interactive mode for the content of a parent node. What this means exactly depends on how the specific algorithm interprets this option. Usually in the interactive mode algorithms try to modify the current layout as little as possible.")
+ .defaultValue(INTERACTIVE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.interactiveLayout")
+ .group("")
+ .name("interactive Layout")
+ .description("Whether the graph should be changeable interactively and by setting constraints")
+ .defaultValue(INTERACTIVE_LAYOUT_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.omitNodeMicroLayout")
+ .group("")
+ .name("Omit Node Micro Layout")
+ .description("Node micro layout comprises the computation of node dimensions (if requested), the placement of ports and their labels, and the placement of node labels. The functionality is implemented independent of any specific layout algorithm and shouldn\'t have any negative impact on the layout algorithm\'s performance itself. Yet, if any unforeseen behavior occurs, this option allows to deactivate the micro layout.")
+ .defaultValue(OMIT_NODE_MICRO_LAYOUT_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portConstraints")
+ .group("")
+ .name("Port Constraints")
+ .description("Defines constraints of the position of the ports of a node.")
+ .defaultValue(PORT_CONSTRAINTS_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortConstraints.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.position")
+ .group("")
+ .name("Position")
+ .description("The position of a node, port, or label. This is used by the \'Fixed Layout\' algorithm to specify a pre-defined position.")
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(KVector.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES, LayoutOptionData.Target.PORTS, LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.priority")
+ .group("")
+ .name("Priority")
+ .description("Defines the priority of an object; its meaning depends on the specific layout algorithm and the context where it is used.")
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES, LayoutOptionData.Target.EDGES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.randomSeed")
+ .group("")
+ .name("Randomization Seed")
+ .description("Seed used for pseudo-random number generators to control the layout algorithm. If the value is 0, the seed shall be determined pseudo-randomly (e.g. from the system time).")
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.separateConnectedComponents")
+ .group("")
+ .name("Separate Connected Components")
+ .description("Whether each connected component should be processed separately.")
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.junctionPoints")
+ .group("")
+ .name("Junction Points")
+ .description("This option is not used as option, but as output of the layout algorithms. It is attached to edges and determines the points where junction symbols should be drawn in order to represent hyperedges with orthogonal routing. Whether such points are computed depends on the chosen layout algorithm and edge routing style. The points are put into the vector chain with no specific order.")
+ .defaultValue(JUNCTION_POINTS_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(KVectorChain.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.EDGES))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.commentBox")
+ .group("")
+ .name("Comment Box")
+ .description("Whether the node should be regarded as a comment box instead of a regular node. In that case its placement should be similar to how labels are handled. Any edges incident to a comment box specify to which graph elements the comment is related.")
+ .defaultValue(COMMENT_BOX_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.hypernode")
+ .group("")
+ .name("Hypernode")
+ .description("Whether the node should be handled as a hypernode.")
+ .defaultValue(HYPERNODE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.labelManager")
+ .group("")
+ .name("Label Manager")
+ .description("Label managers can shorten labels upon a layout algorithm\'s request.")
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(ILabelManager.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.softwrappingFuzziness")
+ .group("")
+ .name("Softwrapping Fuzziness")
+ .description("Determines the amount of fuzziness to be used when performing softwrapping on labels. The value expresses the percent of overhang that is permitted for each line. If the next line would take up less space than this threshold, it is appended to the current line instead of being placed in a new line.")
+ .defaultValue(SOFTWRAPPING_FUZZINESS_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.margins")
+ .group("")
+ .name("Margins")
+ .description("Margins define additional space around the actual bounds of a graph element. For instance, ports or labels being placed on the outside of a node\'s border might introduce such a margin. The margin is used to guarantee non-overlap of other graph elements with those ports or labels.")
+ .defaultValue(MARGINS_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(ElkMargin.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.noLayout")
+ .group("")
+ .name("No Layout")
+ .description("No layout is done for the associated element. This is used to mark parts of a diagram to avoid their inclusion in the layout graph, or to mark parts of the layout graph to prevent layout engines from processing them. If you wish to exclude the contents of a compound node from automatic layout, while the node itself is still considered on its own layer, use the \'Fixed Layout\' algorithm for that node.")
+ .defaultValue(NO_LAYOUT_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES, LayoutOptionData.Target.EDGES, LayoutOptionData.Target.PORTS, LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.scaleFactor")
+ .group("")
+ .name("Scale Factor")
+ .description("The scaling factor to be applied to the corresponding node in recursive layout. It causes the corresponding node\'s size to be adjusted, and its ports and labels to be sized and placed accordingly after the layout of that node has been determined (and before the node itself and its siblings are arranged). The scaling is not reverted afterwards, so the resulting layout graph contains the adjusted size and position data. This option is currently not supported if \'Layout Hierarchy\' is set.")
+ .defaultValue(SCALE_FACTOR_DEFAULT)
+ .lowerBound(SCALE_FACTOR_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.childAreaWidth")
+ .group("")
+ .name("Child Area Width")
+ .description("The width of the area occupied by the laid out children of a node.")
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.childAreaHeight")
+ .group("")
+ .name("Child Area Height")
+ .description("The height of the area occupied by the laid out children of a node.")
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdownLayout")
+ .group("")
+ .name("Topdown Layout")
+ .description("Turns topdown layout on and off. If this option is enabled, hierarchical layout will be computed first for the root node and then for its children recursively. Layouts are then scaled down to fit the area provided by their parents. Graphs must follow a certain structure for topdown layout to work properly. {@link TopdownNodeTypes.PARALLEL_NODE} nodes must have children of type {@link TopdownNodeTypes.HIERARCHICAL_NODE} and must define {@link topdown.hierarchicalNodeWidth} and {@link topdown.hierarchicalNodeAspectRatio} for their children. Furthermore they need to be laid out using an algorithm that is a {@link TopdownLayoutProvider}. Hierarchical nodes can also be parents of other hierarchical nodes and can optionally use a {@link TopdownSizeApproximator} to dynamically set sizes during topdown layout. In this case {@link topdown.hierarchicalNodeWidth} and {@link topdown.hierarchicalNodeAspectRatio} should be set on the node itself rather than the parent. The values are then used by the size approximator as base values. Hierarchical nodes require the layout option {@link nodeSize.fixedGraphSize} to be true to prevent the algorithm used there from resizing the hierarchical node. This option is not supported if \'Hierarchy Handling\' is set to \'INCLUDE_CHILDREN\'")
+ .defaultValue(TOPDOWN_LAYOUT_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdownLayout",
+ "org.eclipse.elk.topdown.nodeType",
+ null
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.animate")
+ .group("")
+ .name("Animate")
+ .description("Whether the shift from the old layout to the new computed layout shall be animated.")
+ .defaultValue(ANIMATE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.animTimeFactor")
+ .group("")
+ .name("Animation Time Factor")
+ .description("Factor for computation of animation time. The higher the value, the longer the animation time. If the value is 0, the resulting time is always equal to the minimum defined by \'Minimal Animation Time\'.")
+ .defaultValue(ANIM_TIME_FACTOR_DEFAULT)
+ .lowerBound(ANIM_TIME_FACTOR_LOWER_BOUND)
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.layoutAncestors")
+ .group("")
+ .name("Layout Ancestors")
+ .description("Whether the hierarchy levels on the path from the selected element to the root of the diagram shall be included in the layout process.")
+ .defaultValue(LAYOUT_ANCESTORS_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.maxAnimTime")
+ .group("")
+ .name("Maximal Animation Time")
+ .description("The maximal time for animations, in milliseconds.")
+ .defaultValue(MAX_ANIM_TIME_DEFAULT)
+ .lowerBound(MAX_ANIM_TIME_LOWER_BOUND)
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.minAnimTime")
+ .group("")
+ .name("Minimal Animation Time")
+ .description("The minimal time for animations, in milliseconds.")
+ .defaultValue(MIN_ANIM_TIME_DEFAULT)
+ .lowerBound(MIN_ANIM_TIME_LOWER_BOUND)
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.progressBar")
+ .group("")
+ .name("Progress Bar")
+ .description("Whether a progress bar shall be displayed during layout computations.")
+ .defaultValue(PROGRESS_BAR_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.validateGraph")
+ .group("")
+ .name("Validate Graph")
+ .description("Whether the graph shall be validated before any layout algorithm is applied. If this option is enabled and at least one error is found, the layout process is aborted and a message is shown to the user.")
+ .defaultValue(VALIDATE_GRAPH_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.validateOptions")
+ .group("")
+ .name("Validate Options")
+ .description("Whether layout options shall be validated before any layout algorithm is applied. If this option is enabled and at least one error is found, the layout process is aborted and a message is shown to the user.")
+ .defaultValue(VALIDATE_OPTIONS_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.zoomToFit")
+ .group("")
+ .name("Zoom to Fit")
+ .description("Whether the zoom level shall be set to view the whole diagram after layout.")
+ .defaultValue(ZOOM_TO_FIT_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.box.packingMode")
+ .group("box")
+ .name("Box Layout Mode")
+ .description("Configures the packing mode used by the {@link BoxLayoutProvider}. If SIMPLE is not required (neither priorities are used nor the interactive mode), GROUP_DEC can improve the packing and decrease the area. GROUP_MIXED and GROUP_INC may, in very specific scenarios, work better.")
+ .defaultValue(BOX_PACKING_MODE_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(BoxLayoutProvider.PackingMode.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.json.shapeCoords")
+ .group("json")
+ .name("Shape Coords")
+ .description("For layouts transferred into JSON graphs, specify the coordinate system to be used for nodes, ports, and labels of nodes and ports.")
+ .defaultValue(JSON_SHAPE_COORDS_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(ShapeCoords.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.json.edgeCoords")
+ .group("json")
+ .name("Edge Coords")
+ .description("For layouts transferred into JSON graphs, specify the coordinate system to be used for edge route points and edge labels.")
+ .defaultValue(JSON_EDGE_COORDS_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(EdgeCoords.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.commentComment")
+ .group("spacing")
+ .name("Comment Comment Spacing")
+ .description("Spacing to be preserved between a comment box and other comment boxes connected to the same node. The space left between comment boxes of different nodes is controlled by the node-node spacing.")
+ .defaultValue(SPACING_COMMENT_COMMENT_DEFAULT)
+ .lowerBound(SPACING_COMMENT_COMMENT_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.commentNode")
+ .group("spacing")
+ .name("Comment Node Spacing")
+ .description("Spacing to be preserved between a node and its connected comment boxes. The space left between a node and the comments of another node is controlled by the node-node spacing.")
+ .defaultValue(SPACING_COMMENT_NODE_DEFAULT)
+ .lowerBound(SPACING_COMMENT_NODE_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.componentComponent")
+ .group("spacing")
+ .name("Components Spacing")
+ .description("Spacing to be preserved between pairs of connected components. This option is only relevant if \'separateConnectedComponents\' is activated.")
+ .defaultValue(SPACING_COMPONENT_COMPONENT_DEFAULT)
+ .lowerBound(SPACING_COMPONENT_COMPONENT_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.edgeEdge")
+ .group("spacing")
+ .name("Edge Spacing")
+ .description("Spacing to be preserved between any two edges. Note that while this can somewhat easily be satisfied for the segments of orthogonally drawn edges, it is harder for general polylines or splines.")
+ .defaultValue(SPACING_EDGE_EDGE_DEFAULT)
+ .lowerBound(SPACING_EDGE_EDGE_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.edgeLabel")
+ .group("spacing")
+ .name("Edge Label Spacing")
+ .description("The minimal distance to be preserved between a label and the edge it is associated with. Note that the placement of a label is influenced by the \'edgelabels.placement\' option.")
+ .defaultValue(SPACING_EDGE_LABEL_DEFAULT)
+ .lowerBound(SPACING_EDGE_LABEL_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.edgeNode")
+ .group("spacing")
+ .name("Edge Node Spacing")
+ .description("Spacing to be preserved between nodes and edges.")
+ .defaultValue(SPACING_EDGE_NODE_DEFAULT)
+ .lowerBound(SPACING_EDGE_NODE_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.labelLabel")
+ .group("spacing")
+ .name("Label Spacing")
+ .description("Determines the amount of space to be left between two labels of the same graph element.")
+ .defaultValue(SPACING_LABEL_LABEL_DEFAULT)
+ .lowerBound(SPACING_LABEL_LABEL_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.labelNode")
+ .group("spacing")
+ .name("Label Node Spacing")
+ .description("Spacing to be preserved between labels and the border of node they are associated with. Note that the placement of a label is influenced by the \'nodelabels.placement\' option.")
+ .defaultValue(SPACING_LABEL_NODE_DEFAULT)
+ .lowerBound(SPACING_LABEL_NODE_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.labelPortHorizontal")
+ .group("spacing")
+ .name("Horizontal spacing between Label and Port")
+ .description("Horizontal spacing to be preserved between labels and the ports they are associated with. Note that the placement of a label is influenced by the \'portlabels.placement\' option.")
+ .defaultValue(SPACING_LABEL_PORT_HORIZONTAL_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.labelPortVertical")
+ .group("spacing")
+ .name("Vertical spacing between Label and Port")
+ .description("Vertical spacing to be preserved between labels and the ports they are associated with. Note that the placement of a label is influenced by the \'portlabels.placement\' option.")
+ .defaultValue(SPACING_LABEL_PORT_VERTICAL_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.nodeNode")
+ .group("spacing")
+ .name("Node Spacing")
+ .description("The minimal distance to be preserved between each two nodes.")
+ .defaultValue(SPACING_NODE_NODE_DEFAULT)
+ .lowerBound(SPACING_NODE_NODE_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.nodeSelfLoop")
+ .group("spacing")
+ .name("Node Self Loop Spacing")
+ .description("Spacing to be preserved between a node and its self loops.")
+ .defaultValue(SPACING_NODE_SELF_LOOP_DEFAULT)
+ .lowerBound(SPACING_NODE_SELF_LOOP_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.portPort")
+ .group("spacing")
+ .name("Port Spacing")
+ .description("Spacing between pairs of ports of the same node.")
+ .defaultValue(SPACING_PORT_PORT_DEFAULT)
+ .lowerBound(SPACING_PORT_PORT_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.individual")
+ .group("spacing")
+ .name("Individual Spacing")
+ .description("Allows to specify individual spacing values for graph elements that shall be different from the value specified for the element\'s parent.")
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(IndividualSpacings.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES, LayoutOptionData.Target.EDGES, LayoutOptionData.Target.PORTS, LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.spacing.portsSurrounding")
+ .group("spacing")
+ .name("Additional Port Space")
+ .description("Additional space around the sets of ports on each node side. For each side of a node, this option can reserve additional space before and after the ports on each side. For example, a top spacing of 20 makes sure that the first port on the western and eastern side is 20 units away from the northern border.")
+ .defaultValue(SPACING_PORTS_SURROUNDING_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(ElkMargin.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.partitioning.partition")
+ .group("partitioning")
+ .name("Layout Partition")
+ .description("Partition to which the node belongs. This requires Layout Partitioning to be active. Nodes with lower partition IDs will appear to the left of nodes with higher partition IDs (assuming a left-to-right layout direction).")
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.partitioning.partition",
+ "org.eclipse.elk.partitioning.activate",
+ PARTITIONING_PARTITION_DEP_PARTITIONING_ACTIVATE_0
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.partitioning.activate")
+ .group("partitioning")
+ .name("Layout Partitioning")
+ .description("Whether to activate partitioned layout. This will allow to group nodes through the Layout Partition option. a pair of nodes with different partition indices is then placed such that the node with lower index is placed to the left of the other node (with left-to-right layout direction). Depending on the layout algorithm, this may only be guaranteed to work if all nodes have a layout partition configured, or at least if edges that cross partitions are not part of a partition-crossing cycle.")
+ .defaultValue(PARTITIONING_ACTIVATE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.nodeLabels.padding")
+ .group("nodeLabels")
+ .name("Node Label Padding")
+ .description("Define padding for node labels that are placed inside of a node.")
+ .defaultValue(NODE_LABELS_PADDING_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(ElkPadding.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.nodeLabels.placement")
+ .group("nodeLabels")
+ .name("Node Label Placement")
+ .description("Hints for where node labels are to be placed; if empty, the node label\'s position is not modified.")
+ .defaultValue(NODE_LABELS_PLACEMENT_DEFAULT)
+ .type(LayoutOptionData.Type.ENUMSET)
+ .optionClass(NodeLabelPlacement.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES, LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portAlignment.default")
+ .group("portAlignment")
+ .name("Port Alignment")
+ .description("Defines the default port distribution for a node. May be overridden for each side individually.")
+ .defaultValue(PORT_ALIGNMENT_DEFAULT_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortAlignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portAlignment.north")
+ .group("portAlignment")
+ .name("Port Alignment (North)")
+ .description("Defines how ports on the northern side are placed, overriding the node\'s general port alignment.")
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortAlignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portAlignment.south")
+ .group("portAlignment")
+ .name("Port Alignment (South)")
+ .description("Defines how ports on the southern side are placed, overriding the node\'s general port alignment.")
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortAlignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portAlignment.west")
+ .group("portAlignment")
+ .name("Port Alignment (West)")
+ .description("Defines how ports on the western side are placed, overriding the node\'s general port alignment.")
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortAlignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portAlignment.east")
+ .group("portAlignment")
+ .name("Port Alignment (East)")
+ .description("Defines how ports on the eastern side are placed, overriding the node\'s general port alignment.")
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortAlignment.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.nodeSize.constraints")
+ .group("nodeSize")
+ .name("Node Size Constraints")
+ .description("What should be taken into account when calculating a node\'s size. Empty size constraints specify that a node\'s size is already fixed and should not be changed.")
+ .defaultValue(NODE_SIZE_CONSTRAINTS_DEFAULT)
+ .type(LayoutOptionData.Type.ENUMSET)
+ .optionClass(SizeConstraint.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.nodeSize.options")
+ .group("nodeSize")
+ .name("Node Size Options")
+ .description("Options modifying the behavior of the size constraints set on a node. Each member of the set specifies something that should be taken into account when calculating node sizes. The empty set corresponds to no further modifications.")
+ .defaultValue(NODE_SIZE_OPTIONS_DEFAULT)
+ .type(LayoutOptionData.Type.ENUMSET)
+ .optionClass(SizeOptions.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.nodeSize.minimum")
+ .group("nodeSize")
+ .name("Node Size Minimum")
+ .description("The minimal size to which a node can be reduced.")
+ .defaultValue(NODE_SIZE_MINIMUM_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(KVector.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.nodeSize.fixedGraphSize")
+ .group("nodeSize")
+ .name("Fixed Graph Size")
+ .description("By default, the fixed layout provider will enlarge a graph until it is large enough to contain its children. If this option is set, it won\'t do so.")
+ .defaultValue(NODE_SIZE_FIXED_GRAPH_SIZE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.edgeLabels.placement")
+ .group("edgeLabels")
+ .name("Edge Label Placement")
+ .description("Gives a hint on where to put edge labels.")
+ .defaultValue(EDGE_LABELS_PLACEMENT_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(EdgeLabelPlacement.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.edgeLabels.inline")
+ .group("edgeLabels")
+ .name("Inline Edge Labels")
+ .description("If true, an edge label is placed directly on its edge. May only apply to center edge labels. This kind of label placement is only advisable if the label\'s rendering is such that it is not crossed by its edge and thus stays legible.")
+ .defaultValue(EDGE_LABELS_INLINE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.font.name")
+ .group("font")
+ .name("Font Name")
+ .description("Font name used for a label.")
+ .type(LayoutOptionData.Type.STRING)
+ .optionClass(String.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.font.size")
+ .group("font")
+ .name("Font Size")
+ .description("Font size used for a label.")
+ .lowerBound(FONT_SIZE_LOWER_BOUND)
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.LABELS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.port.anchor")
+ .group("port")
+ .name("Port Anchor Offset")
+ .description("The offset to the port position where connections shall be attached.")
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(KVector.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PORTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.port.index")
+ .group("port")
+ .name("Port Index")
+ .description("The index of a port in the fixed order around a node. The order is assumed as clockwise, starting with the leftmost port on the top side. This option must be set if \'Port Constraints\' is set to FIXED_ORDER and no specific positions are given for the ports. Additionally, the option \'Port Side\' must be defined in this case.")
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PORTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.port.side")
+ .group("port")
+ .name("Port Side")
+ .description("The side of a node on which a port is situated. This option must be set if \'Port Constraints\' is set to FIXED_SIDE or FIXED_ORDER and no specific positions are given for the ports.")
+ .defaultValue(PORT_SIDE_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(PortSide.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PORTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.port.borderOffset")
+ .group("port")
+ .name("Port Border Offset")
+ .description("The offset of ports on the node border. With a positive offset the port is moved outside of the node, while with a negative offset the port is moved towards the inside. An offset of 0 means that the port is placed directly on the node border, i.e. if the port side is north, the port\'s south border touches the nodes\'s north border; if the port side is east, the port\'s west border touches the nodes\'s east border; if the port side is south, the port\'s north border touches the node\'s south border; if the port side is west, the port\'s east border touches the node\'s west border.")
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PORTS))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portLabels.placement")
+ .group("portLabels")
+ .name("Port Label Placement")
+ .description("Decides on a placement method for port labels; if empty, the node label\'s position is not modified.")
+ .defaultValue(PORT_LABELS_PLACEMENT_DEFAULT)
+ .type(LayoutOptionData.Type.ENUMSET)
+ .optionClass(PortLabelPlacement.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portLabels.nextToPortIfPossible")
+ .group("portLabels")
+ .name("Port Labels Next to Port")
+ .description("Use \'portLabels.placement\': NEXT_TO_PORT_OF_POSSIBLE.")
+ .defaultValue(PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.portLabels.treatAsGroup")
+ .group("portLabels")
+ .name("Treat Port Labels as Group")
+ .description("If this option is true (default), the labels of a port will be treated as a group when it comes to centering them next to their port. If this option is false, only the first label will be centered next to the port, with the others being placed below. This only applies to labels of eastern and western ports and will have no effect if labels are not placed next to their port.")
+ .defaultValue(PORT_LABELS_TREAT_AS_GROUP_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.VISIBLE)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.sizeCategories")
+ .group("topdown")
+ .name("Number of size categories")
+ .description("Defines the number of categories to use for the FIXED_INTEGER_RATIO_BOXES size approximator.")
+ .defaultValue(TOPDOWN_SIZE_CATEGORIES_DEFAULT)
+ .lowerBound(TOPDOWN_SIZE_CATEGORIES_LOWER_BOUND)
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.sizeCategories",
+ "org.eclipse.elk.topdown.sizeApproximator",
+ TOPDOWN_SIZE_CATEGORIES_DEP_TOPDOWN_SIZE_APPROXIMATOR_0
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.sizeCategoriesHierarchicalNodeWeight")
+ .group("topdown")
+ .name("Weight of a node containing children for determining the graph size")
+ .description("When determining the graph size for the size categorisation, this value determines how many times a node containing children is weighted more than a simple node. For example setting this value to four would result in a graph containing a simple node and a hierarchical node to be counted as having a size of five.")
+ .defaultValue(TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT_DEFAULT)
+ .lowerBound(TOPDOWN_SIZE_CATEGORIES_HIERARCHICAL_NODE_WEIGHT_LOWER_BOUND)
+ .type(LayoutOptionData.Type.INT)
+ .optionClass(Integer.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.sizeCategoriesHierarchicalNodeWeight",
+ "org.eclipse.elk.topdown.sizeCategories",
+ null
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.scaleFactor")
+ .group("topdown")
+ .name("Topdown Scale Factor")
+ .description("The scaling factor to be applied to the nodes laid out within the node in recursive topdown layout. The difference to \'Scale Factor\' is that the node itself is not scaled. This value has to be set on hierarchical nodes.")
+ .defaultValue(TOPDOWN_SCALE_FACTOR_DEFAULT)
+ .lowerBound(TOPDOWN_SCALE_FACTOR_LOWER_BOUND)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.scaleFactor",
+ "org.eclipse.elk.topdown.nodeType",
+ TOPDOWN_SCALE_FACTOR_DEP_TOPDOWN_NODE_TYPE_0
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.sizeApproximator")
+ .group("topdown")
+ .name("Topdown Size Approximator")
+ .description("The size approximator to be used to set sizes of hierarchical nodes during topdown layout. The default value is null, which results in nodes keeping whatever size is defined for them e.g. through parent parallel node or by manually setting the size.")
+ .defaultValue(TOPDOWN_SIZE_APPROXIMATOR_DEFAULT)
+ .type(LayoutOptionData.Type.OBJECT)
+ .optionClass(ITopdownSizeApproximator.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.sizeApproximator",
+ "org.eclipse.elk.topdown.nodeType",
+ TOPDOWN_SIZE_APPROXIMATOR_DEP_TOPDOWN_NODE_TYPE_0
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.hierarchicalNodeWidth")
+ .group("topdown")
+ .name("Topdown Hierarchical Node Width")
+ .description("The fixed size of a hierarchical node when using topdown layout. If this value is set on a parallel node it applies to its children, when set on a hierarchical node it applies to the node itself.")
+ .defaultValue(TOPDOWN_HIERARCHICAL_NODE_WIDTH_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.hierarchicalNodeWidth",
+ "org.eclipse.elk.topdown.nodeType",
+ null
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.hierarchicalNodeAspectRatio")
+ .group("topdown")
+ .name("Topdown Hierarchical Node Aspect Ratio")
+ .description("The fixed aspect ratio of a hierarchical node when using topdown layout. Default is 1/sqrt(2). If this value is set on a parallel node it applies to its children, when set on a hierarchical node it applies to the node itself.")
+ .defaultValue(TOPDOWN_HIERARCHICAL_NODE_ASPECT_RATIO_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS, LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.hierarchicalNodeAspectRatio",
+ "org.eclipse.elk.topdown.nodeType",
+ null
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.nodeType")
+ .group("topdown")
+ .name("Topdown Node Type")
+ .description("The different node types used for topdown layout. If the node type is set to {@link TopdownNodeTypes.PARALLEL_NODE} the algorithm must be set to a {@link TopdownLayoutProvider} such as {@link TopdownPacking}. The {@link nodeSize.fixedGraphSize} option is technically only required for hierarchical nodes.")
+ .defaultValue(TOPDOWN_NODE_TYPE_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(TopdownNodeTypes.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.nodeType",
+ "org.eclipse.elk.nodeSize.fixedGraphSize",
+ null
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.topdown.scaleCap")
+ .group("topdown")
+ .name("Topdown Scale Cap")
+ .description("Determines the upper limit for the topdown scale factor. The default value is 1.0 which ensures that nested children never end up appearing larger than their parents in terms of unit sizes such as the font size. If the limit is larger, nodes will fully utilize the available space, but it is counteriniuitive for inner nodes to have a larger scale than outer nodes.")
+ .defaultValue(TOPDOWN_SCALE_CAP_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.PARENTS))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.addDependency(
+ "org.eclipse.elk.topdown.scaleCap",
+ "org.eclipse.elk.topdown.nodeType",
+ TOPDOWN_SCALE_CAP_DEP_TOPDOWN_NODE_TYPE_0
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.insideSelfLoops.activate")
+ .group("insideSelfLoops")
+ .name("Activate Inside Self Loops")
+ .description("Whether this node allows to route self loops inside of it instead of around it. If set to true, this will make the node a compound node if it isn\'t already, and will require the layout algorithm to support compound nodes with hierarchical ports.")
+ .defaultValue(INSIDE_SELF_LOOPS_ACTIVATE_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.NODES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.insideSelfLoops.yo")
+ .group("insideSelfLoops")
+ .name("Inside Self Loop")
+ .description("Whether a self loop should be routed inside a node instead of around that node.")
+ .defaultValue(INSIDE_SELF_LOOPS_YO_DEFAULT)
+ .type(LayoutOptionData.Type.BOOLEAN)
+ .optionClass(Boolean.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.EDGES))
+ .visibility(LayoutOptionData.Visibility.ADVANCED)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.edge.thickness")
+ .group("edge")
+ .name("Edge Thickness")
+ .description("The thickness of an edge. This is a hint on the line width used to draw an edge, possibly requiring more space to be reserved for it.")
+ .defaultValue(EDGE_THICKNESS_DEFAULT)
+ .type(LayoutOptionData.Type.DOUBLE)
+ .optionClass(Double.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.EDGES))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutOptionData.Builder()
+ .id("org.eclipse.elk.edge.type")
+ .group("edge")
+ .name("Edge Type")
+ .description("The type of an edge. This is usually used for UML class diagrams, where associations must be handled differently from generalizations.")
+ .defaultValue(EDGE_TYPE_DEFAULT)
+ .type(LayoutOptionData.Type.ENUM)
+ .optionClass(EdgeType.class)
+ .targets(EnumSet.of(LayoutOptionData.Target.EDGES))
+ .visibility(LayoutOptionData.Visibility.HIDDEN)
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.layered")
+ .name("Layered")
+ .description("The layer-based method was introduced by Sugiyama, Tagawa and Toda in 1981. It emphasizes the direction of edges by pointing as many edges as possible into the same direction. The nodes are arranged in layers, which are sometimes called \"hierarchies\", and then reordered such that the number of edge crossings is minimized. Afterwards, concrete coordinates are computed for the nodes and edge bend points.")
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.orthogonal")
+ .name("Orthogonal")
+ .description("Orthogonal methods that follow the \"topology-shape-metrics\" approach by Batini, Nardelli and Tamassia \'86. The first phase determines the topology of the drawing by applying a planarization technique, which results in a planar representation of the graph. The orthogonal shape is computed in the second phase, which aims at minimizing the number of edge bends, and is called orthogonalization. The third phase leads to concrete coordinates for nodes and edge bend points by applying a compaction method, thus defining the metrics.")
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.force")
+ .name("Force")
+ .description("Layout algorithms that follow physical analogies by simulating a system of attractive and repulsive forces. The first successful method of this kind was proposed by Eades in 1984.")
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.circle")
+ .name("Circle")
+ .description("Circular layout algorithms emphasize cycles or biconnected components of a graph by arranging them in circles. This is useful if a drawing is desired where such components are clearly grouped, or where cycles are shown as prominent OPTIONS of the graph.")
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.tree")
+ .name("Tree")
+ .description("Specialized layout methods for trees, i.e. acyclic graphs. The regular structure of graphs that have no undirected cycles can be emphasized using an algorithm of this type.")
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.planar")
+ .name("Planar")
+ .description("Algorithms that require a planar or upward planar graph. Most of these algorithms are theoretically interesting, but not practically usable.")
+ .create()
+ );
+ registry.register(new LayoutCategoryData.Builder()
+ .id("org.eclipse.elk.radial")
+ .name("Radial")
+ .description("Radial layout algorithms usually position the nodes of the graph on concentric circles.")
+ .create()
+ );
+ new org.eclipse.elk.core.options.FixedLayouterOptions().apply(registry);
+ new org.eclipse.elk.core.options.BoxLayouterOptions().apply(registry);
+ new org.eclipse.elk.core.options.RandomLayouterOptions().apply(registry);
+ }
+}
diff --git a/screenshots/01-dashboard.png b/screenshots/01-dashboard.png
new file mode 100644
index 00000000..cbd03294
Binary files /dev/null and b/screenshots/01-dashboard.png differ
diff --git a/screenshots/02-dashboard-detail-panel.png b/screenshots/02-dashboard-detail-panel.png
new file mode 100644
index 00000000..7f2fb923
Binary files /dev/null and b/screenshots/02-dashboard-detail-panel.png differ
diff --git a/screenshots/03-exchange-detail.png b/screenshots/03-exchange-detail.png
new file mode 100644
index 00000000..04b9a378
Binary files /dev/null and b/screenshots/03-exchange-detail.png differ
diff --git a/screenshots/04-routes-metrics.png b/screenshots/04-routes-metrics.png
new file mode 100644
index 00000000..24a8b801
Binary files /dev/null and b/screenshots/04-routes-metrics.png differ
diff --git a/screenshots/05-agents.png b/screenshots/05-agents.png
new file mode 100644
index 00000000..afd3c08f
Binary files /dev/null and b/screenshots/05-agents.png differ
diff --git a/screenshots/06-agent-instance.png b/screenshots/06-agent-instance.png
new file mode 100644
index 00000000..9980c682
Binary files /dev/null and b/screenshots/06-agent-instance.png differ
diff --git a/screenshots/07-admin-rbac.png b/screenshots/07-admin-rbac.png
new file mode 100644
index 00000000..d55b639d
Binary files /dev/null and b/screenshots/07-admin-rbac.png differ
diff --git a/screenshots/08-admin-audit.png b/screenshots/08-admin-audit.png
new file mode 100644
index 00000000..2e4b95b5
Binary files /dev/null and b/screenshots/08-admin-audit.png differ
diff --git a/screenshots/09-admin-oidc.png b/screenshots/09-admin-oidc.png
new file mode 100644
index 00000000..d6ef175c
Binary files /dev/null and b/screenshots/09-admin-oidc.png differ
diff --git a/screenshots/10-admin-database.png b/screenshots/10-admin-database.png
new file mode 100644
index 00000000..c760bb20
Binary files /dev/null and b/screenshots/10-admin-database.png differ
diff --git a/screenshots/11-admin-opensearch.png b/screenshots/11-admin-opensearch.png
new file mode 100644
index 00000000..83aa203c
Binary files /dev/null and b/screenshots/11-admin-opensearch.png differ
diff --git a/screenshots/12-command-palette.png b/screenshots/12-command-palette.png
new file mode 100644
index 00000000..91f6e838
Binary files /dev/null and b/screenshots/12-command-palette.png differ
diff --git a/screenshots/13-dashboard-dark-mode.png b/screenshots/13-dashboard-dark-mode.png
new file mode 100644
index 00000000..f3668356
Binary files /dev/null and b/screenshots/13-dashboard-dark-mode.png differ
diff --git a/screenshots/14-login-page.png b/screenshots/14-login-page.png
new file mode 100644
index 00000000..e4fef351
Binary files /dev/null and b/screenshots/14-login-page.png differ
diff --git a/screenshots/appconfig-overview.png b/screenshots/appconfig-overview.png
new file mode 100644
index 00000000..91213e3f
Binary files /dev/null and b/screenshots/appconfig-overview.png differ
diff --git a/screenshots/appconfig-slidein-v2.png b/screenshots/appconfig-slidein-v2.png
new file mode 100644
index 00000000..561396b4
Binary files /dev/null and b/screenshots/appconfig-slidein-v2.png differ
diff --git a/screenshots/appconfig-slidein-v3.png b/screenshots/appconfig-slidein-v3.png
new file mode 100644
index 00000000..e41b4d1d
Binary files /dev/null and b/screenshots/appconfig-slidein-v3.png differ
diff --git a/screenshots/appconfig-slidein-v4.png b/screenshots/appconfig-slidein-v4.png
new file mode 100644
index 00000000..824b4837
Binary files /dev/null and b/screenshots/appconfig-slidein-v4.png differ
diff --git a/screenshots/appconfig-slidein.png b/screenshots/appconfig-slidein.png
new file mode 100644
index 00000000..691e2cd0
Binary files /dev/null and b/screenshots/appconfig-slidein.png differ
diff --git a/screenshots/complex-fulfillment-broken.png b/screenshots/complex-fulfillment-broken.png
new file mode 100644
index 00000000..af950b4b
Binary files /dev/null and b/screenshots/complex-fulfillment-broken.png differ
diff --git a/screenshots/error-handling-test.png b/screenshots/error-handling-test.png
new file mode 100644
index 00000000..4754900d
Binary files /dev/null and b/screenshots/error-handling-test.png differ
diff --git a/screenshots/exchanges-800h.png b/screenshots/exchanges-800h.png
new file mode 100644
index 00000000..2d70b483
Binary files /dev/null and b/screenshots/exchanges-800h.png differ
diff --git a/screenshots/exchanges-scroll-check.png b/screenshots/exchanges-scroll-check.png
new file mode 100644
index 00000000..e08f63c5
Binary files /dev/null and b/screenshots/exchanges-scroll-check.png differ
diff --git a/screenshots/process-diagram-after-fix.png b/screenshots/process-diagram-after-fix.png
new file mode 100644
index 00000000..25af53f4
Binary files /dev/null and b/screenshots/process-diagram-after-fix.png differ
diff --git a/screenshots/process-diagram-dev.png b/screenshots/process-diagram-dev.png
new file mode 100644
index 00000000..54feeada
Binary files /dev/null and b/screenshots/process-diagram-dev.png differ
diff --git a/screenshots/trace-indicator-check.png b/screenshots/trace-indicator-check.png
new file mode 100644
index 00000000..e488cb7d
Binary files /dev/null and b/screenshots/trace-indicator-check.png differ
diff --git a/ui/org/eclipse/elk/core/options/PortAlignment.java b/ui/org/eclipse/elk/core/options/PortAlignment.java
new file mode 100644
index 00000000..8dbb9b09
--- /dev/null
+++ b/ui/org/eclipse/elk/core/options/PortAlignment.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.core.options;
+
+/**
+ * Defines the distribution of ports.
+ *
+ * @author csp
+ */
+public enum PortAlignment {
+
+ /** Ports are evenly distributed, with the same amount of space between and around them. */
+ DISTRIBUTED,
+
+ /** Ports are justified and use up all the space except for the surrounding ports spacing. */
+ JUSTIFIED,
+
+ /** Ports are placed at the most top respectively left position with minimal spacing. */
+ BEGIN,
+
+ /** Ports are centered with minimal spacing. */
+ CENTER,
+
+ /** Ports are placed at the most top respectively left position with minimal spacing. */
+ END;
+
+}
diff --git a/ui/src/components/ProcessDiagram/CompoundNode.tsx b/ui/src/components/ProcessDiagram/CompoundNode.tsx
index 00850621..aa7e0a30 100644
--- a/ui/src/components/ProcessDiagram/CompoundNode.tsx
+++ b/ui/src/components/ProcessDiagram/CompoundNode.tsx
@@ -65,8 +65,8 @@ export function CompoundNode({
onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave,
};
- // _TRY_BODY / _CB_MAIN: transparent wrapper — no header, no border, just layout
- if (node.type === '_TRY_BODY' || node.type === '_CB_MAIN') {
+ // _TRY_BODY: transparent wrapper — no header, no border, just layout
+ if (node.type === '_TRY_BODY') {
return (
{renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
@@ -75,24 +75,6 @@ export function CompoundNode({
);
}
- // _CB_FALLBACK: section styling with EIP purple
- if (node.type === '_CB_FALLBACK') {
- const fallbackColor = '#7C3AED'; // EIP purple
- return (
-
-
-
-
- fallback
-
- {renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
- {renderChildren(node, absX, absY, childProps)}
-
- );
- }
-
// DO_CATCH / DO_FINALLY: section-like styling (tinted bg, thin border, label)
if (node.type === 'DO_CATCH' || node.type === 'DO_FINALLY') {
const sectionLabel = node.type === 'DO_CATCH'
diff --git a/ui/src/components/ProcessDiagram/node-colors.ts b/ui/src/components/ProcessDiagram/node-colors.ts
index 78488fc1..f14c1db6 100644
--- a/ui/src/components/ProcessDiagram/node-colors.ts
+++ b/ui/src/components/ProcessDiagram/node-colors.ts
@@ -64,7 +64,6 @@ const COMPOUND_TYPES = new Set([
'EIP_LOOP', 'EIP_MULTICAST', 'EIP_AGGREGATE',
'ON_EXCEPTION', 'ERROR_HANDLER',
'ON_COMPLETION',
- 'EIP_CIRCUIT_BREAKER', '_CB_MAIN', '_CB_FALLBACK',
]);
const ERROR_COMPOUND_TYPES = new Set([