feat(doc): F4.4a devices and cables in the document, undo per person; NewDeviceRun

A new device is what Java's palette makes, measured with the new oracle
NewDeviceRun (every kind and router size saved as FILIUS saves). Devices
move to whole pixels and go with their cables; a cable takes the first free
port, a home router's WAN port unless its LAN port is asked for, a wireless
host only joins a switch, two devices join once, each refusal named with
Java's message. Each person's undo takes back their own changes only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 09:18:14 +00:00
co-authored by Claude Opus 5.5
parent 63273916b5
commit 27ab7a1f49
9 changed files with 595 additions and 9 deletions
+18 -8
View File
@@ -122,7 +122,8 @@ function filesMap(files: VirtualFileSystem): Y.Map<Y.Map<unknown>> {
return map;
}
function nodeMap(node: TopologyNode, order: number, project: ImportedProject): Y.Map<unknown> {
/** A device's entry in the document, with what the project holds for it. */
export function nodeEntry(node: TopologyNode, order: number, project: ImportedProject): Y.Map<unknown> {
const map = new Y.Map<unknown>();
map.set('order', order);
map.set('kind', node.kind);
@@ -200,7 +201,7 @@ export function writeProject(doc: Y.Doc, project: ImportedProject, origin: unkno
meta.set('schema', SCHEMA_VERSION);
meta.set('versionLine', project.versionLine);
const nodes = doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
project.graph.nodes.forEach((node, index) => nodes.set(node.id, nodeMap(node, index, project)));
project.graph.nodes.forEach((node, index) => nodes.set(node.id, nodeEntry(node, index, project)));
const links = doc.getMap<Y.Map<unknown>>(ROOTS.links);
project.graph.links.forEach((link, index) => links.set(link.id, linkMap(link, index)));
const docs = doc.getMap<Y.Map<unknown>>(ROOTS.docs);
@@ -240,11 +241,10 @@ function fileSystemOf(files: Y.Map<Y.Map<unknown>>): VirtualFileSystem {
return fileSystem;
}
/** The project the document holds; its warnings are the reader's and stay with the import. */
export function readProject(doc: Y.Doc): ImportedProject {
const meta = doc.getMap(ROOTS.meta);
const project: ImportedProject = {
versionLine: (meta.get('versionLine') as string | null | undefined) ?? null,
/** A project with nothing in it. */
export function emptyProject(): ImportedProject {
return {
versionLine: null,
graph: { nodes: [], links: [] },
deviceConfigurations: new Map(),
interfaceConfigurations: new Map(),
@@ -269,10 +269,20 @@ export function readProject(doc: Y.Doc): ImportedProject {
echoServerConfigurations: new Map(),
simpleClientConfigurations: new Map(),
documentationItems: [],
archiveEntries: new Map(doc.getMap<Uint8Array>(ROOTS.archive).entries()),
archiveEntries: new Map(),
ownApplicationsByNodeId: new Map(),
warnings: [],
};
}
/** The project the document holds; its warnings are the reader's and stay with the import. */
export function readProject(doc: Y.Doc): ImportedProject {
const meta = doc.getMap(ROOTS.meta);
const project: ImportedProject = {
...emptyProject(),
versionLine: (meta.get('versionLine') as string | null | undefined) ?? null,
archiveEntries: new Map(doc.getMap<Uint8Array>(ROOTS.archive).entries()),
};
for (const [id, map] of inOrder(doc.getMap<Y.Map<unknown>>(ROOTS.nodes))) {
const ports = (map.get('ports') as Y.Array<Y.Map<unknown>>).toArray();
project.graph.nodes.push({