feat(editor): F4.5b the device sheet for routers and home routers

Measured first: SheetRun now types into FILIUS' router and home router
panels, records five forwarding tables (holeTabelle) and steps in the
panel's table. Java turns a card address that passes the pattern but not
IP.ipCheck (".0.0.1") into null and the router panel throws; an empty
gateway is marked red yet taken; a refused table cell is stored as null;
a home router's DHCP client leaves its DHCP server running. None of the
nulls is adopted. The core's routeTableRows matches all five tables.

- doc: card addresses (the first card mirrored in the device's settings),
  router gateway, RIP, manual routes (a new row once all four cells pass,
  a cell, removal); the sheet reads cards with what their cable joins and
  the whole table
- editor: Java's tabs (general, each card titled by its address, the
  forwarding table greyed out under RIP; general, LAN, WAN on a home
  router); the open card's cable drawn active in Java's green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 11:51:03 +00:00
co-authored by Claude Opus 5.5
parent 9b7ec2fc72
commit eff9433673
22 changed files with 2201 additions and 119 deletions
+13 -29
View File
@@ -1,16 +1,14 @@
// The device sheet (plan §7.1): the settings of the selected device, the
// fields of FILIUS' settings panel of its kind (JHostKonfiguration,
// JSwitchKonfiguration, JModemKonfiguration) in the panels' order. Java
// shows the panel when a device is chosen; so does the sheet, beside the
// surface or below it on a phone. Each change is one step of the person's
// undo. A router's and a home router's sheet name the device only, until
// their own panels follow (F4.5b).
// JSwitchKonfiguration, JModemKonfiguration; a router's and a home router's
// in RouterSheets.tsx) in the panels' order. Java shows the panel when a
// device is chosen; so does the sheet, beside the surface or below it on a
// phone. Each change is one step of the person's undo.
import { useEffect, useState } from 'react';
import type * as Y from 'yjs';
import {
deviceSheet,
renameDevice,
setDhcpClient,
setHostAddress,
setHostLabels,
@@ -30,6 +28,8 @@ import {
import { iconOf } from '../canvas/layout.js';
import { ICON_URLS } from '../canvas/icons.js';
import { useTranslate } from '../i18n/i18n.js';
import { NameField, type Change } from './NameField.js';
import { HomeRouterFields, RouterFields } from './RouterSheets.js';
import { SheetCheck, SheetField } from './SheetField.js';
import './sheet.css';
@@ -55,17 +55,11 @@ export interface DeviceSheetProps {
readonly onChanged: () => void;
readonly onMessage: (message: string) => void;
readonly onClose: () => void;
/** The cable to draw active (a router's card whose tab is open), or none. */
readonly onHighlight: (link: string | null) => void;
}
interface Change {
readonly doc: Y.Doc;
readonly id: string;
readonly origin: object;
/** Runs a change and ends the undo step. */
readonly run: <T>(change: () => T) => T;
}
export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onClose }: DeviceSheetProps) {
export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onClose, onHighlight }: DeviceSheetProps) {
const t = useTranslate();
const sheet = useDeviceSheet(doc, id);
if (sheet === undefined) return null;
@@ -97,6 +91,10 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
<SwitchFields sheet={sheet} change={change} />
) : sheet.kind === 'remoteLink' ? (
<ModemFields sheet={sheet} change={change} />
) : sheet.kind === 'router' ? (
<RouterFields sheet={sheet} change={change} onHighlight={onHighlight} />
) : sheet.kind === 'gateway' ? (
<HomeRouterFields sheet={sheet} change={change} onHighlight={onHighlight} />
) : (
<NameField name={sheet.name} change={change} />
)}
@@ -105,20 +103,6 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
);
}
function NameField({ name, change, shown, disabled = false }: { readonly name: string; readonly change: Change; readonly shown?: string; readonly disabled?: boolean }) {
const t = useTranslate();
return (
<SheetField
name="name"
label={t('sheet.name')}
value={shown ?? name}
rule="name"
disabled={disabled}
onCommit={(text) => change.run(() => (renameDevice(change.doc, change.id, text, change.origin), { value: text }))}
/>
);
}
function HostFields({ sheet, label, change, onMessage }: { readonly sheet: HostSheet; readonly label: string; readonly change: Change; readonly onMessage: (message: string) => void }) {
const t = useTranslate();
const address =