generated from coulomb/repo-seed
feat: add zone layout selector
This commit is contained in:
@@ -219,6 +219,11 @@ Container state belongs in saved or copied graph view state, not in the Fabric
|
|||||||
payload. It is an operator workspace preference, similar to manual visibility
|
payload. It is an operator workspace preference, similar to manual visibility
|
||||||
overrides.
|
overrides.
|
||||||
|
|
||||||
|
Zone-local layout is also view state. The first selectable algorithms are a
|
||||||
|
compact grid and a circle layout. Switching between them should rearrange the
|
||||||
|
assigned nodes inside each stable container without moving the container itself
|
||||||
|
or changing the underlying Fabric relationships.
|
||||||
|
|
||||||
### Context Edges
|
### Context Edges
|
||||||
|
|
||||||
Display-only context edges are not zone connectivity. Repository `declares`
|
Display-only context edges are not zone connectivity. Repository `declares`
|
||||||
|
|||||||
@@ -210,9 +210,10 @@ zone surface position and size in graph coordinates. Global graph layout may
|
|||||||
place unzoned nodes and provide an initial center for new zones, but existing
|
place unzoned nodes and provide an initial center for new zones, but existing
|
||||||
zone containers should keep their operator-chosen positions when the layout
|
zone containers should keep their operator-chosen positions when the layout
|
||||||
algorithm changes. After the global layout pass, each zone may project its
|
algorithm changes. After the global layout pass, each zone may project its
|
||||||
assigned visible nodes into local coordinates inside its container. The first
|
assigned visible nodes into local coordinates inside its container. The current
|
||||||
local layout may be a deterministic compact layout; later engines can replace
|
local layout choices are compact grid and circle. The selected zone-local
|
||||||
that with per-zone Cytoscape or engine-owned algorithms.
|
layout algorithm belongs in the nested `zone.layout.algorithm` view state so it
|
||||||
|
can be restored by saved or copied views without changing the Fabric payload.
|
||||||
|
|
||||||
Zone collapse is a view-only operation. A collapsed zone should hide its visible
|
Zone collapse is a view-only operation. A collapsed zone should hide its visible
|
||||||
member nodes, replace them with a synthetic zone node, and draw synthetic
|
member nodes, replace them with a synthetic zone node, and draw synthetic
|
||||||
|
|||||||
@@ -455,6 +455,12 @@ def graph_explorer_page() -> str:
|
|||||||
<option value="accessZone">Access Zone</option>
|
<option value="accessZone">Access Zone</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="map-control-field"><span class="field-label">Zone Layout <button type="button" class="help-tip" aria-label="Zone layout help" data-help-title="Zone Layout" data-help="Zone layout changes how each zone arranges its assigned visible nodes inside the stable zone rectangle. It does not change the underlying Fabric graph.">?</button></span>
|
||||||
|
<select id="zone-layout-select" title="Choose zone-local layout algorithm">
|
||||||
|
<option value="compact-grid">Grid</option>
|
||||||
|
<option value="circle">Circle</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="map-control-field"><span class="field-label">Labels <button type="button" class="help-tip" aria-label="Labels help" data-help-title="Labels" data-help="Label density changes only text visibility. Auto hides low-priority labels when the map is dense; Key keeps repositories, services, deployments, servers, and issue markers visible.">?</button></span>
|
<label class="map-control-field"><span class="field-label">Labels <button type="button" class="help-tip" aria-label="Labels help" data-help-title="Labels" data-help="Label density changes only text visibility. Auto hides low-priority labels when the map is dense; Key keeps repositories, services, deployments, servers, and issue markers visible.">?</button></span>
|
||||||
<select id="label-select" title="Control node label density">
|
<select id="label-select" title="Control node label density">
|
||||||
<option value="auto">Auto</option>
|
<option value="auto">Auto</option>
|
||||||
@@ -570,6 +576,7 @@ def graph_explorer_page() -> str:
|
|||||||
const labelSelect = document.getElementById("label-select");
|
const labelSelect = document.getElementById("label-select");
|
||||||
const zoneBoundaryToggle = document.getElementById("zone-boundary-toggle");
|
const zoneBoundaryToggle = document.getElementById("zone-boundary-toggle");
|
||||||
const zoneGroupSelect = document.getElementById("zone-group-select");
|
const zoneGroupSelect = document.getElementById("zone-group-select");
|
||||||
|
const zoneLayoutSelect = document.getElementById("zone-layout-select");
|
||||||
const nodeTypeFilter = document.getElementById("node-type-filter");
|
const nodeTypeFilter = document.getElementById("node-type-filter");
|
||||||
const nodeTypeSummary = document.getElementById("node-type-summary");
|
const nodeTypeSummary = document.getElementById("node-type-summary");
|
||||||
const edgeTypeFilter = document.getElementById("edge-type-filter");
|
const edgeTypeFilter = document.getElementById("edge-type-filter");
|
||||||
@@ -610,6 +617,7 @@ def graph_explorer_page() -> str:
|
|||||||
let activeLabelMode = "auto";
|
let activeLabelMode = "auto";
|
||||||
let activeZoneGrouping = "deploymentEnvironment";
|
let activeZoneGrouping = "deploymentEnvironment";
|
||||||
let activeZoneDefinitionSet = "fabric-default";
|
let activeZoneDefinitionSet = "fabric-default";
|
||||||
|
let activeZoneLayoutAlgorithm = "compact-grid";
|
||||||
let profilePersistence = "none";
|
let profilePersistence = "none";
|
||||||
let profiles = [];
|
let profiles = [];
|
||||||
let currentProfileId = "";
|
let currentProfileId = "";
|
||||||
@@ -1062,6 +1070,11 @@ def graph_explorer_page() -> str:
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const zoneLayoutAlgorithms = new Set(["compact-grid", "circle"]);
|
||||||
|
|
||||||
|
const normalizeZoneLayoutAlgorithm = (value) =>
|
||||||
|
zoneLayoutAlgorithms.has(String(value || "")) ? String(value) : "compact-grid";
|
||||||
|
|
||||||
const zoneElementData = (element) => {
|
const zoneElementData = (element) => {
|
||||||
if (!element) return {};
|
if (!element) return {};
|
||||||
if (typeof element.data === "function") return element.data();
|
if (typeof element.data === "function") return element.data();
|
||||||
@@ -1495,7 +1508,7 @@ def graph_explorer_page() -> str:
|
|||||||
.slice()
|
.slice()
|
||||||
.sort((left, right) => zoneNodeSortKey(left).localeCompare(zoneNodeSortKey(right)));
|
.sort((left, right) => zoneNodeSortKey(left).localeCompare(zoneNodeSortKey(right)));
|
||||||
if (!nodes.length || !container) return;
|
if (!nodes.length || !container) return;
|
||||||
const algorithm = String(zone.layout?.algorithm || "compact-grid");
|
const algorithm = normalizeZoneLayoutAlgorithm(activeZoneLayoutAlgorithm || zone.layout?.algorithm);
|
||||||
if (algorithm === "circle") {
|
if (algorithm === "circle") {
|
||||||
layoutZoneNodesInCircle(nodes, container);
|
layoutZoneNodesInCircle(nodes, container);
|
||||||
return;
|
return;
|
||||||
@@ -2031,6 +2044,9 @@ def graph_explorer_page() -> str:
|
|||||||
const currentZoneViewState = () => {
|
const currentZoneViewState = () => {
|
||||||
const visible = zoneBoundaryToggle ? zoneBoundaryToggle.checked : true;
|
const visible = zoneBoundaryToggle ? zoneBoundaryToggle.checked : true;
|
||||||
const grouping = zoneGroupSelect ? zoneGroupSelect.value || "deploymentEnvironment" : "deploymentEnvironment";
|
const grouping = zoneGroupSelect ? zoneGroupSelect.value || "deploymentEnvironment" : "deploymentEnvironment";
|
||||||
|
const layoutAlgorithm = normalizeZoneLayoutAlgorithm(
|
||||||
|
zoneLayoutSelect ? zoneLayoutSelect.value : activeZoneLayoutAlgorithm
|
||||||
|
);
|
||||||
const definitionSet = zoneDefinitionSets[activeZoneDefinitionSet]
|
const definitionSet = zoneDefinitionSets[activeZoneDefinitionSet]
|
||||||
? activeZoneDefinitionSet
|
? activeZoneDefinitionSet
|
||||||
: "fabric-default";
|
: "fabric-default";
|
||||||
@@ -2038,6 +2054,9 @@ def graph_explorer_page() -> str:
|
|||||||
visible,
|
visible,
|
||||||
grouping,
|
grouping,
|
||||||
definitionSet,
|
definitionSet,
|
||||||
|
layout: {
|
||||||
|
algorithm: layoutAlgorithm,
|
||||||
|
},
|
||||||
presentation: {
|
presentation: {
|
||||||
boundaries: visible,
|
boundaries: visible,
|
||||||
labels: true,
|
labels: true,
|
||||||
@@ -2061,6 +2080,7 @@ def graph_explorer_page() -> str:
|
|||||||
zoneBoundaries: zone.visible,
|
zoneBoundaries: zone.visible,
|
||||||
zoneGrouping: zone.grouping,
|
zoneGrouping: zone.grouping,
|
||||||
zoneDefinitionSet: zone.definitionSet,
|
zoneDefinitionSet: zone.definitionSet,
|
||||||
|
zoneLayoutAlgorithm: zone.layout.algorithm,
|
||||||
rules: filterRules.map((rule) => ({...rule})),
|
rules: filterRules.map((rule) => ({...rule})),
|
||||||
manualOverrides: {...manualOverrides},
|
manualOverrides: {...manualOverrides},
|
||||||
};
|
};
|
||||||
@@ -2093,6 +2113,7 @@ def graph_explorer_page() -> str:
|
|||||||
if (params.has("zoneBoundaries")) state.zoneBoundaries = params.get("zoneBoundaries") !== "0";
|
if (params.has("zoneBoundaries")) state.zoneBoundaries = params.get("zoneBoundaries") !== "0";
|
||||||
if (params.has("zoneGrouping")) state.zoneGrouping = params.get("zoneGrouping") || "";
|
if (params.has("zoneGrouping")) state.zoneGrouping = params.get("zoneGrouping") || "";
|
||||||
if (params.has("zoneDefinitionSet")) state.zoneDefinitionSet = params.get("zoneDefinitionSet") || "";
|
if (params.has("zoneDefinitionSet")) state.zoneDefinitionSet = params.get("zoneDefinitionSet") || "";
|
||||||
|
if (params.has("zoneLayout")) state.zoneLayoutAlgorithm = params.get("zoneLayout") || "";
|
||||||
if (params.has("profile")) state.profile = params.get("profile") || "";
|
if (params.has("profile")) state.profile = params.get("profile") || "";
|
||||||
if (params.has("state")) {
|
if (params.has("state")) {
|
||||||
try {
|
try {
|
||||||
@@ -2118,6 +2139,7 @@ def graph_explorer_page() -> str:
|
|||||||
if (state.zoneBoundaries === false) params.set("zoneBoundaries", "0");
|
if (state.zoneBoundaries === false) params.set("zoneBoundaries", "0");
|
||||||
if (state.zoneGrouping && state.zoneGrouping !== "deploymentEnvironment") params.set("zoneGrouping", state.zoneGrouping);
|
if (state.zoneGrouping && state.zoneGrouping !== "deploymentEnvironment") params.set("zoneGrouping", state.zoneGrouping);
|
||||||
if (state.zoneDefinitionSet && state.zoneDefinitionSet !== "fabric-default") params.set("zoneDefinitionSet", state.zoneDefinitionSet);
|
if (state.zoneDefinitionSet && state.zoneDefinitionSet !== "fabric-default") params.set("zoneDefinitionSet", state.zoneDefinitionSet);
|
||||||
|
if (state.zoneLayoutAlgorithm && state.zoneLayoutAlgorithm !== "compact-grid") params.set("zoneLayout", state.zoneLayoutAlgorithm);
|
||||||
if (currentProfileId) params.set("profile", currentProfileId);
|
if (currentProfileId) params.set("profile", currentProfileId);
|
||||||
if (includeStateBlob || hasManualOverrides() || filterRules.length > 0) {
|
if (includeStateBlob || hasManualOverrides() || filterRules.length > 0) {
|
||||||
params.set("state", encodeStateBlob(state));
|
params.set("state", encodeStateBlob(state));
|
||||||
@@ -2142,6 +2164,7 @@ def graph_explorer_page() -> str:
|
|||||||
const containers = nested.containers && typeof nested.containers === "object"
|
const containers = nested.containers && typeof nested.containers === "object"
|
||||||
? nested.containers
|
? nested.containers
|
||||||
: state.zoneContainers;
|
: state.zoneContainers;
|
||||||
|
const layout = nested.layout && typeof nested.layout === "object" ? nested.layout : {};
|
||||||
const grouping = nested.grouping || state.zoneGrouping || "deploymentEnvironment";
|
const grouping = nested.grouping || state.zoneGrouping || "deploymentEnvironment";
|
||||||
const definitionSet = zoneDefinitionSets[nested.definitionSet || state.zoneDefinitionSet]
|
const definitionSet = zoneDefinitionSets[nested.definitionSet || state.zoneDefinitionSet]
|
||||||
? nested.definitionSet || state.zoneDefinitionSet
|
? nested.definitionSet || state.zoneDefinitionSet
|
||||||
@@ -2155,6 +2178,9 @@ def graph_explorer_page() -> str:
|
|||||||
visible,
|
visible,
|
||||||
grouping: zoneGroupSelect && optionExists(zoneGroupSelect, grouping) ? grouping : "deploymentEnvironment",
|
grouping: zoneGroupSelect && optionExists(zoneGroupSelect, grouping) ? grouping : "deploymentEnvironment",
|
||||||
definitionSet,
|
definitionSet,
|
||||||
|
layout: {
|
||||||
|
algorithm: normalizeZoneLayoutAlgorithm(layout.algorithm || state.zoneLayoutAlgorithm),
|
||||||
|
},
|
||||||
presentation: {
|
presentation: {
|
||||||
boundaries: "boundaries" in presentation ? presentation.boundaries !== false : visible,
|
boundaries: "boundaries" in presentation ? presentation.boundaries !== false : visible,
|
||||||
labels: "labels" in presentation ? presentation.labels !== false : true,
|
labels: "labels" in presentation ? presentation.labels !== false : true,
|
||||||
@@ -2173,11 +2199,13 @@ def graph_explorer_page() -> str:
|
|||||||
if ("edgeTypes" in state) setCheckedValues(edgeTypeFilter, (state.edgeTypes || []).filter((value) => allEdgeTypes.includes(value)));
|
if ("edgeTypes" in state) setCheckedValues(edgeTypeFilter, (state.edgeTypes || []).filter((value) => allEdgeTypes.includes(value)));
|
||||||
if ("review" in state) reviewFilter.value = optionExists(reviewFilter, state.review) ? state.review : "";
|
if ("review" in state) reviewFilter.value = optionExists(reviewFilter, state.review) ? state.review : "";
|
||||||
if ("unresolved" in state) unresolvedFilter.value = optionExists(unresolvedFilter, state.unresolved) ? state.unresolved : "";
|
if ("unresolved" in state) unresolvedFilter.value = optionExists(unresolvedFilter, state.unresolved) ? state.unresolved : "";
|
||||||
if ("zone" in state || "zoneBoundaries" in state || "zoneGrouping" in state || "zoneDefinitionSet" in state) {
|
if ("zone" in state || "zoneBoundaries" in state || "zoneGrouping" in state || "zoneDefinitionSet" in state || "zoneLayoutAlgorithm" in state) {
|
||||||
const zone = normalizeZoneViewState(state);
|
const zone = normalizeZoneViewState(state);
|
||||||
activeZoneDefinitionSet = zone.definitionSet;
|
activeZoneDefinitionSet = zone.definitionSet;
|
||||||
if (zoneBoundaryToggle) zoneBoundaryToggle.checked = zone.visible;
|
if (zoneBoundaryToggle) zoneBoundaryToggle.checked = zone.visible;
|
||||||
if (zoneGroupSelect) zoneGroupSelect.value = zone.grouping;
|
if (zoneGroupSelect) zoneGroupSelect.value = zone.grouping;
|
||||||
|
activeZoneLayoutAlgorithm = zone.layout.algorithm;
|
||||||
|
if (zoneLayoutSelect) zoneLayoutSelect.value = activeZoneLayoutAlgorithm;
|
||||||
zoneContainerState = zone.containers;
|
zoneContainerState = zone.containers;
|
||||||
}
|
}
|
||||||
if ("manualOverrides" in state && state.manualOverrides && typeof state.manualOverrides === "object") {
|
if ("manualOverrides" in state && state.manualOverrides && typeof state.manualOverrides === "object") {
|
||||||
@@ -2192,6 +2220,9 @@ def graph_explorer_page() -> str:
|
|||||||
activeMode = modeSelect.value || "full";
|
activeMode = modeSelect.value || "full";
|
||||||
activeLabelMode = labelSelect.value || "auto";
|
activeLabelMode = labelSelect.value || "auto";
|
||||||
activeZoneGrouping = zoneGroupSelect ? zoneGroupSelect.value || "deploymentEnvironment" : "deploymentEnvironment";
|
activeZoneGrouping = zoneGroupSelect ? zoneGroupSelect.value || "deploymentEnvironment" : "deploymentEnvironment";
|
||||||
|
activeZoneLayoutAlgorithm = normalizeZoneLayoutAlgorithm(
|
||||||
|
zoneLayoutSelect ? zoneLayoutSelect.value : activeZoneLayoutAlgorithm
|
||||||
|
);
|
||||||
selectedZoneId = "";
|
selectedZoneId = "";
|
||||||
collapsedZoneSnapshots = new Map();
|
collapsedZoneSnapshots = new Map();
|
||||||
focusSet = null;
|
focusSet = null;
|
||||||
@@ -2233,6 +2264,9 @@ def graph_explorer_page() -> str:
|
|||||||
if (zoneGroupSelect && (zoneGroupSelect.value || "deploymentEnvironment") !== "deploymentEnvironment") {
|
if (zoneGroupSelect && (zoneGroupSelect.value || "deploymentEnvironment") !== "deploymentEnvironment") {
|
||||||
parts.push("access zone boundaries");
|
parts.push("access zone boundaries");
|
||||||
}
|
}
|
||||||
|
if (zoneLayoutSelect && (zoneLayoutSelect.value || "compact-grid") !== "compact-grid") {
|
||||||
|
parts.push(`${zoneLayoutSelect.options[zoneLayoutSelect.selectedIndex]?.textContent || zoneLayoutSelect.value} zone layout`);
|
||||||
|
}
|
||||||
if (filterRules.length) parts.push(`${filterRules.length} rule${filterRules.length === 1 ? "" : "s"}`);
|
if (filterRules.length) parts.push(`${filterRules.length} rule${filterRules.length === 1 ? "" : "s"}`);
|
||||||
const overrideCount = Object.keys(manualOverrides).length;
|
const overrideCount = Object.keys(manualOverrides).length;
|
||||||
if (overrideCount) parts.push(`${overrideCount} override${overrideCount === 1 ? "" : "s"}`);
|
if (overrideCount) parts.push(`${overrideCount} override${overrideCount === 1 ? "" : "s"}`);
|
||||||
@@ -2968,6 +3002,18 @@ def graph_explorer_page() -> str:
|
|||||||
updateProfileSummary();
|
updateProfileSummary();
|
||||||
updateUrlState();
|
updateUrlState();
|
||||||
});
|
});
|
||||||
|
zoneLayoutSelect.addEventListener("input", () => {
|
||||||
|
activeZoneLayoutAlgorithm = normalizeZoneLayoutAlgorithm(zoneLayoutSelect.value);
|
||||||
|
currentProfileId = "";
|
||||||
|
profileSelect.value = "";
|
||||||
|
applyZoneContainerLayout();
|
||||||
|
fitVisibleGraph();
|
||||||
|
renderZoneOverlay();
|
||||||
|
if (!selected) renderMapOverview();
|
||||||
|
updateProfileControls();
|
||||||
|
updateProfileSummary();
|
||||||
|
updateUrlState();
|
||||||
|
});
|
||||||
zoneOverlay.addEventListener("click", (event) => {
|
zoneOverlay.addEventListener("click", (event) => {
|
||||||
if (suppressZoneClick) {
|
if (suppressZoneClick) {
|
||||||
suppressZoneClick = false;
|
suppressZoneClick = false;
|
||||||
@@ -3089,6 +3135,8 @@ def graph_explorer_page() -> str:
|
|||||||
zoneBoundaryToggle.checked = true;
|
zoneBoundaryToggle.checked = true;
|
||||||
zoneGroupSelect.value = "deploymentEnvironment";
|
zoneGroupSelect.value = "deploymentEnvironment";
|
||||||
activeZoneGrouping = "deploymentEnvironment";
|
activeZoneGrouping = "deploymentEnvironment";
|
||||||
|
zoneLayoutSelect.value = "compact-grid";
|
||||||
|
activeZoneLayoutAlgorithm = "compact-grid";
|
||||||
activeZoneDefinitionSet = "fabric-default";
|
activeZoneDefinitionSet = "fabric-default";
|
||||||
selectedZoneId = "";
|
selectedZoneId = "";
|
||||||
collapsedZoneSnapshots = new Map();
|
collapsedZoneSnapshots = new Map();
|
||||||
|
|||||||
@@ -402,6 +402,7 @@ def test_registry_serves_graph_explorer_exports(tmp_path: Path) -> None:
|
|||||||
assert 'id="zone-overlay"' in page
|
assert 'id="zone-overlay"' in page
|
||||||
assert 'id="zone-boundary-toggle"' in page
|
assert 'id="zone-boundary-toggle"' in page
|
||||||
assert 'id="zone-group-select"' in page
|
assert 'id="zone-group-select"' in page
|
||||||
|
assert 'id="zone-layout-select"' in page
|
||||||
assert 'id="node-type-filter"' in page
|
assert 'id="node-type-filter"' in page
|
||||||
assert 'id="edge-type-filter"' in page
|
assert 'id="edge-type-filter"' in page
|
||||||
assert 'id="rule-panel"' in page
|
assert 'id="rule-panel"' in page
|
||||||
@@ -429,6 +430,13 @@ def test_registry_serves_graph_explorer_exports(tmp_path: Path) -> None:
|
|||||||
assert "zoneDefinitionSet" in page
|
assert "zoneDefinitionSet" in page
|
||||||
assert "zoneDefinitionSets" in page
|
assert "zoneDefinitionSets" in page
|
||||||
assert "fabric-default" in page
|
assert "fabric-default" in page
|
||||||
|
assert "activeZoneLayoutAlgorithm" in page
|
||||||
|
assert "zoneLayoutAlgorithms" in page
|
||||||
|
assert "normalizeZoneLayoutAlgorithm" in page
|
||||||
|
assert "zoneLayoutAlgorithm" in page
|
||||||
|
assert "zoneLayout" in page
|
||||||
|
assert "compact-grid" in page
|
||||||
|
assert "circle" in page
|
||||||
assert "zoneContainerState" in page
|
assert "zoneContainerState" in page
|
||||||
assert "ensureZoneContainer" in page
|
assert "ensureZoneContainer" in page
|
||||||
assert "packZoneContainers" in page
|
assert "packZoneContainers" in page
|
||||||
|
|||||||
44
workplans/ADHOC-2026-05-25.md
Normal file
44
workplans/ADHOC-2026-05-25.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
id: ADHOC-2026-05-25
|
||||||
|
type: workplan
|
||||||
|
title: "Ad Hoc Fixes 2026-05-25"
|
||||||
|
domain: railiance
|
||||||
|
repo: railiance-fabric
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: railiance
|
||||||
|
created: "2026-05-25"
|
||||||
|
updated: "2026-05-25"
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADHOC-2026-05-25 - Ad Hoc Fixes
|
||||||
|
|
||||||
|
## Add Zone Layout Algorithm Control
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: ADHOC-2026-05-25-T01
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
The graph explorer now lays zone subgraphs out as a grid inside each zone
|
||||||
|
container. Add an operator-facing control that can switch the zone-local layout
|
||||||
|
algorithm while keeping stable zone containers intact.
|
||||||
|
|
||||||
|
Expected result: the map controls expose a zone layout selector, the selected
|
||||||
|
algorithm applies to each zone subgraph, and the setting persists in saved or
|
||||||
|
copied view state.
|
||||||
|
|
||||||
|
Result: Added a `Zone Layout` selector with Grid and Circle algorithms. The
|
||||||
|
selected algorithm is stored in nested zone view state, reflected by the
|
||||||
|
`zoneLayout` URL alias for non-default layout, and reapplies zone-local node
|
||||||
|
placement without moving stable zone containers.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- `python3 -m pytest tests/test_graph_explorer.py tests/test_zone_view.py -q`
|
||||||
|
passed.
|
||||||
|
- Generated graph explorer JavaScript passed `node --check`.
|
||||||
|
- `python3 -m railiance_fabric.cli validate .` passed.
|
||||||
|
- `python3 -m pytest -q` passed with 72 tests.
|
||||||
|
- Headless Edge screenshots confirmed Grid and Circle zone layouts render.
|
||||||
Reference in New Issue
Block a user