Fix review findings: importer DoS, sync data loss, views/profile rendering, docs
Source importers: lstat before open so FIFOs no longer hang the directory walk; replace the quadratic Rust macro regex with a linear scan. Three-way sync: report page add/add and delete-vs-modify as conflicts instead of silently overwriting or resurrecting; canonical comparison so key order is not a change; the sync action withholds output and fails on conflicts unless --force. Story publishing escapes <, >, & and U+2028/9 inside the embedded JSON. Views: drop parentId of unselected containers, re-layout instead of manual geometry, validate before serialising; flatten() merges identical nodes repeated across pages so C4 output works with every analysis action. Dark theme edge labels get a background; tube-map corridors sit above the stations; C4 containers keep the swimlane style and orphan relationships land on the matching page; router keeps container header bands as obstacles; sequence self-messages loop on one side. Docs: SKILL.md lists all 23 CLI actions and how each capability family is invoked, task wrappers for query/test/what-if/doctor, capability tables and --page scope corrected, maintenance snippet uses the real synchronous action signature, duplicated rule bullets moved to the rule references, coverage matrix wording made verifiable. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { DiagramEdge, DiagramGeometry, DiagramPage, DiagramPoint } from "../../model/diagram-ir.js";
|
||||
import type { DiagramEdge, DiagramGeometry, DiagramNode, DiagramPage, DiagramPoint } from "../../model/diagram-ir.js";
|
||||
|
||||
interface Obstacle extends DiagramGeometry {
|
||||
id: string;
|
||||
@@ -102,6 +102,15 @@ function absoluteGeometryById(page: DiagramPage): Map<string, DiagramGeometry> {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Height of a container's label band. Mirrors ir-to-drawio's default container style (swimlane with
|
||||
// startSize=30) and draw.io's own swimlane default (40) when a custom style omits startSize.
|
||||
function headerHeight(node: DiagramNode): number {
|
||||
if (node.style === undefined) return node.kind === "container" ? 30 : 0;
|
||||
const explicit = /(?:^|;)startSize=(\d+(?:\.\d+)?)/.exec(node.style);
|
||||
if (explicit) return Number(explicit[1]);
|
||||
return /(?:^|;)swimlane(?:;|$)/.test(node.style) ? 40 : 0;
|
||||
}
|
||||
|
||||
function ancestorIds(page: DiagramPage, id: string): Set<string> {
|
||||
const nodes = new Map(page.nodes.map((node) => [node.id, node]));
|
||||
const ancestors = new Set<string>();
|
||||
@@ -134,9 +143,16 @@ function routeEdge(edge: DiagramEdge, page: DiagramPage, geometries: Map<string,
|
||||
}
|
||||
const endpoints = endpointPair(source, target);
|
||||
const endpointAncestors = new Set([...ancestorIds(page, edge.source), ...ancestorIds(page, edge.target)]);
|
||||
// Containers enclosing an endpoint may be crossed, except for their header band: a route through the
|
||||
// label bar (or along its bottom line) merges visually with the container border.
|
||||
const obstacles: Obstacle[] = page.nodes
|
||||
.filter((node) => node.id !== edge.source && node.id !== edge.target && !endpointAncestors.has(node.id) && geometries.has(node.id))
|
||||
.map((node) => ({ id: node.id, ...geometries.get(node.id)! }));
|
||||
.filter((node) => node.id !== edge.source && node.id !== edge.target && geometries.has(node.id))
|
||||
.flatMap((node) => {
|
||||
const geometry = geometries.get(node.id)!;
|
||||
if (!endpointAncestors.has(node.id)) return [{ id: node.id, ...geometry }];
|
||||
const header = headerHeight(node);
|
||||
return header > 0 ? [{ id: node.id, ...geometry, height: header }] : [];
|
||||
});
|
||||
const sourceStub = outwardStub(endpoints.source, source, padding);
|
||||
const targetStub = outwardStub(endpoints.target, target, padding);
|
||||
const middleX = snap((sourceStub.x + targetStub.x) / 2);
|
||||
|
||||
Reference in New Issue
Block a user