diff --git a/frontend/src/__tests__/pin-inspector-layout.test.ts b/frontend/src/__tests__/pin-inspector-layout.test.ts index 8e553c4b..e0a93663 100644 --- a/frontend/src/__tests__/pin-inspector-layout.test.ts +++ b/frontend/src/__tests__/pin-inspector-layout.test.ts @@ -127,6 +127,27 @@ describe('layoutInspectorPins — small parts', () => { }); }); +describe('layoutInspectorPins — a crowded header fits its edge', () => { + it('packs 8 top pins inside the art width (they are drawn as vertical text)', () => { + // The SSD1306's 8-pin header measured 240px wide in the dialog. With + // horizontal labels this demanded ~288px and the labels escaped the + // dialog; vertical labels use the side spacing instead. + const pins: InspectorPinInput[] = Array.from({ length: 8 }, (_, i) => ({ + name: ['DATA', 'CLK', 'DC', 'RST', 'CS', '3V3', 'VIN', 'GND'][i], + x: 14 + i * 13, + y: 2, + })); + const r = layoutInspectorPins(pins, { width: 128, height: 119 }); + for (const p of r.pins) expect(p.edge).toBe('top'); + const xs = r.pins.map((p) => p.labelX).sort((a, b) => a - b); + expect(xs[0]).toBeGreaterThanOrEqual(0); + expect(xs[xs.length - 1]).toBeLessThanOrEqual(r.artWidth); + for (let i = 1; i < xs.length; i++) { + expect(xs[i] - xs[i - 1]).toBeGreaterThanOrEqual(16 - 1e-6); + } + }); +}); + describe('layoutInspectorPins — interior pins', () => { it('detects a pin far from every edge and anchors its label on the dot', () => { const r = layoutInspectorPins( diff --git a/frontend/src/components/simulator/PartInspectorDialog.css b/frontend/src/components/simulator/PartInspectorDialog.css index 2c88deb5..5192767c 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.css +++ b/frontend/src/components/simulator/PartInspectorDialog.css @@ -147,6 +147,9 @@ display: flex; align-items: center; justify-content: center; + /* Labels are laid out inside the reserved gutters; clip anything that still + overshoots rather than letting it escape over the header or the canvas. */ + overflow: hidden; } .pid-preview-art { @@ -248,8 +251,16 @@ transform: translate(-100%, -50%); } +/* Top/bottom labels are VERTICAL, like a printed pinout diagram: a header row + packs pins every ~12px, where horizontal text would need ~36px and spill + out of the dialog (an 8-pin SSD1306 header is 240px wide). */ +.pid-pin-label--top, +.pid-pin-label--bottom { + writing-mode: vertical-rl; +} + .pid-pin-label--top { - transform: translate(-50%, -100%); + transform: translate(-50%, -100%) rotate(180deg); } .pid-pin-label--bottom { diff --git a/frontend/src/pro b/frontend/src/pro new file mode 120000 index 00000000..84e7b926 --- /dev/null +++ b/frontend/src/pro @@ -0,0 +1 @@ +/home/dave/velxio-prod/pro/frontend/src/pro \ No newline at end of file diff --git a/frontend/src/utils/pinInspectorLayout.ts b/frontend/src/utils/pinInspectorLayout.ts index 27d10a64..af8877f6 100644 --- a/frontend/src/utils/pinInspectorLayout.ts +++ b/frontend/src/utils/pinInspectorLayout.ts @@ -69,10 +69,14 @@ export interface PinLayoutOptions { /** Horizontal gutter for left/right label columns (room for "GND2" etc.). */ const SIDE_GUTTER = 72; -/** Vertical gutter for top/bottom labels (one text line + leader). */ -const CAP_GUTTER = 28; -/** Top/bottom labels are horizontal text, so they need more room than 16px. */ -const CAP_MIN_SPACING = 36; +/** + * Vertical gutter for top/bottom labels. Those are drawn as VERTICAL text — + * the way printed pinout diagrams do it — because horizontal ones do not fit: + * an SSD1306 has 8 pins along a 240px edge, and at the ~36px a horizontal + * "DATA" needs they demand 288px and spill out of the dialog. Vertical text + * needs only the same spacing as a side column, and the gutter grows instead. + */ +const CAP_GUTTER = 52; /** Small parts (a resistor is 107x11) are upscaled, but only this far. */ const MAX_UPSCALE = 2; @@ -197,7 +201,7 @@ export function layoutInspectorPins( const group = byEdge(edge); const { out, nudged } = stackLabels( group.map(([c]) => c.x), - Math.max(minSpacing, CAP_MIN_SPACING), + minSpacing, W, ); group.forEach(([c, i], k) => {