Add formboard harness layout designer

Separate read-only canvas for physically laying out wire harnesses.
Syncs connectors from the main diagram (one-way), no changes flow back.

Backend:
- FormboardLayout, FormboardNode, FormboardSegment models
- /api/formboard/ router: get/create layout, sync connectors from diagram,
  full CRUD for nodes and segments

Frontend (formboard.js):
- Konva canvas: drag nodes, click-to-connect segments, zoom/pan
- Node types: Connector (blue rect, synced from diagram), Branch (green
  circle, manual), Splice/joint (orange diamond, manual)
- Segments scale thickness by wire count; fitting overlays for open,
  split loom, conduit, heat shrink, tape wrap
- Right-click context menu for delete on nodes/segments

App integration:
- "⊞ Formboard" tab in view-tab-bar (distinct color, independent of views)
- Sub-toolbar: Select / Branch / Splice / Connect / Sync / Fit / Delete
- Right panel swaps to formboard props when active:
  - Node: label, notes, linked device info
  - Segment: fitting type, fitting color, label, length, wire checklist
    (checkboxes for every wire in the main diagram)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 07:18:43 -04:00
parent 0bbcaa8ee5
commit 82d8570767
8 changed files with 985 additions and 1 deletions
+60
View File
@@ -105,8 +105,24 @@
<div id="view-tab-bar" style="display:none">
<button class="view-tab active" data-view-id="main">Main</button>
<button id="btn-add-view" title="Add view">+</button>
<div class="view-tab-sep"></div>
<button id="btn-formboard-tab" class="view-tab fb-tab" title="Formboard harness layout">⊞ Formboard</button>
</div>
<!-- Formboard sub-toolbar (visible only in formboard mode) -->
<div id="fb-toolbar" style="display:none">
<button id="fb-btn-select" class="fb-mode-btn active" title="Select / move">▸ Select</button>
<button id="fb-btn-branch" class="fb-mode-btn" title="Click canvas to add a branch point">⬤ Branch</button>
<button id="fb-btn-splice" class="fb-mode-btn" title="Click canvas to add a splice/joint">◆ Splice</button>
<button id="fb-btn-connect" class="fb-mode-btn" title="Click two nodes to draw a segment">⌒ Connect</button>
<div class="sep"></div>
<button id="fb-btn-sync" title="Import all connectors from main diagram">↻ Sync connectors</button>
<button id="fb-btn-fit" title="Fit view">⊞ Fit</button>
<button id="fb-btn-del" class="danger-btn" title="Delete selected (Del)">✕ Delete</button>
</div>
<div id="canvas-container"></div>
<div id="fb-container" style="display:none;flex:1;width:100%;position:relative">
<div id="fb-canvas" style="width:100%;height:100%"></div>
</div>
<div id="canvas-placeholder">
<div class="placeholder-content">
<h2>⚡ WireDraw</h2>
@@ -346,6 +362,49 @@
</div>
</div>
</div>
<!-- Formboard properties (shown when formboard is active) -->
<div id="props-formboard" style="display:none">
<div id="fb-props-empty" style="padding:20px 12px;color:#444466;font-size:12px;text-align:center">
Select a node or segment.
</div>
<!-- Node props -->
<div id="fb-node-props" style="display:none;padding:8px 10px;display:none">
<div class="cm-label" style="margin-top:4px">Node type</div>
<div id="fb-node-type-badge" style="font-size:11px;color:#8899bb;margin-bottom:8px"></div>
<div class="cm-label">Label</div>
<input id="fb-node-label" class="cm-input" type="text" placeholder="Label…">
<div class="cm-label" style="margin-top:6px">Notes</div>
<textarea id="fb-node-notes" class="cm-input" rows="2" style="resize:vertical" placeholder="Notes…"></textarea>
<div id="fb-connector-info" style="display:none;margin-top:8px;font-size:11px;color:#5566aa;padding:5px 7px;background:#0d0d1e;border-radius:4px;border:1px solid #1a1a33">
<div class="cm-label" style="margin-bottom:2px">Linked device</div>
<div id="fb-connector-dev-label"></div>
</div>
</div>
<!-- Segment props -->
<div id="fb-seg-props" style="display:none;padding:8px 10px">
<div class="cm-label" style="margin-top:4px">Fitting / covering</div>
<select id="fb-seg-fitting" class="cm-input">
<option value="open">Open (no covering)</option>
<option value="loom">Split loom</option>
<option value="conduit">Rigid conduit</option>
<option value="heat_shrink">Heat shrink</option>
<option value="tape">Tape wrap</option>
</select>
<div id="fb-seg-color-row" style="display:none;margin-top:4px">
<div class="cm-label">Fitting color</div>
<input id="fb-seg-fitting-color" type="color" value="#884444" style="width:100%;height:28px;padding:0;border:1px solid #2a2a44;border-radius:4px;background:none;cursor:pointer">
</div>
<div class="cm-label" style="margin-top:6px">Label / notes</div>
<input id="fb-seg-label" class="cm-input" type="text" placeholder="e.g. engine bay trunk">
<div class="cm-label" style="margin-top:6px">Length (mm)</div>
<input id="fb-seg-length" class="cm-input" type="number" min="0" placeholder="e.g. 350">
<div class="cm-label" style="margin-top:10px">Wires through this segment</div>
<div id="fb-wire-list" style="max-height:200px;overflow-y:auto;display:flex;flex-direction:column;gap:2px;font-size:11px"></div>
</div>
</div>
</aside>
</div><!-- #main -->
@@ -472,6 +531,7 @@
<script src="/js/deviceTypes.js"></script>
<script src="/js/connectorLibrary.js"></script>
<script src="/js/canvas.js"></script>
<script src="/js/formboard.js"></script>
<script src="/js/app.js"></script>
</body>
</html>