* Docs: one copy of each row and status item, and a check that keeps it so
Merge resolutions that kept both sides had left progress-doc rows (the .fig row four times, co-editing, live cursors, Windows, M0.9) and status-line items (302 in all) twice or more. The rows are merged keeping every distinct fact, the newest wording winning (#1649's connector routing included); repeated status items are removed, the first kept. cargo xtask docs, run by ci, fails on a repeated table row or status item.
* Docs: remove the status items the merge of main brought back
Merging main into #1658 rebuilt the status lines from main's copies, which still listed items twice: ROADMAP 2, docs/roadmap.md 5, docs/gaps.md 43 and docs/target-app-parity.md 190, whose unclosed parenthesis also came back and hid them from cargo xtask docs. They are removed again, the first copy kept, and the parenthesis closed; every distinct item is still present against main.
Builds on #1618. Variables::get and Variables::collection take an exact id before a name; stored mode ids go through Collection::mode_by_id (expressions, the prototype's mode switch); font family variables and the colour picker, prototype editor and panels read stored ids exactly. Ported from fix/stored-ids-exact.
The board connector router moves, unchanged, from the engine into figcraft-doc (route_points, RouteEnd) so the .fig importer can use it: an elbowed or curved connector saved without its path runs in right angles or one curve from the side of each layer it joins (the file's magnet, else the edge its point is on, else the side facing the other end). Straight connectors are unchanged. Gap 1.
* M6: figcraft-cli connect publish publishes code connect mappings to a design file
figcraft-cli connect publish <file.figcraft> [--mappings figcraft.connect.json] [--dry-run] links each entry's component or set by name through codeConnect.import, warns about and skips links that don't fit, saves the file in place through the engine's file.save, and exits 1 when nothing links. Gap 16.
* M6: the generated man pages include connect and connect publish
The engine notes when a drag has only moved the selected layers by one offset (Interaction::moved_by); the canvas keeps the outlines, box and slots from the drag's start document and shifts them, the selection spacing and the Design panel's values (but the position fields) come from that document, and the Export section's values are kept. Dragging 648 layers: UI pass 2.10 to 1.44 ms a frame.
* M12: create the next board shape already connected (#1597)
A selected sticky or shape shows a + beside each side; hovering one previews where the next goes, clicking makes one of the same kind, size and colour there, joined by a connector, and starts typing in it, in one undo step. board.addConnected {id?, side?, edit?} does it from commands.
* M12: the connected-shape handle and command in every full catalog (#1597)
* M12: Shift+Cmd/Ctrl+Enter on a board shape adds the next one already connected (#1597)
Fonts name a family by its typographic name and by a legacy name per weight; fonts without typographic names are installed under the legacy ones only. FontDb::resolve finds either from the other, and matches names across case, spaces, hyphens and underscores; every lookup (import, paste, REST import, rendering, missing fonts, the font report) uses it. A weight-number style goes by that weight, and .fig and REST imports wait for the installed fonts to be indexed.
* M8: opening a file contacts no team library server it names until you check it (#1097)
* M8: translate the team library check strings (#1097)
* M8: the Assets panel library test checks what allows a library, without its own fake link (Sonar duplication and coverage)
* M8: cap the people a client keeps track of and the deleted ids a session remembers (#1121)
A hostile host or server could grow a client's presence, seen-peers and who-is-here lists without bound, and a session's deleted-id set only grew. Clients keep at most MAX_PEERS (4,096) others, leaving newcomers out with a warning; max_people is at most that; a session remembers at most 262,144 deleted ids, the lowest forgotten first.
* M8: a max_people above what clients track is lowered with a warning, not refused (#1121)
Refusing it would stop a server whose existing config says more from starting after an upgrade. Config::people() lowers max_people to MAX_PEERS for the core's limits, and the startup warnings name the configured and the used value.
* M8: the live welcome's handling in its own function (Sonar S3776)
* M8: a large selection's overlay and Design panel values are kept for frames where nothing changed (#1571)
The selection's outlines, box, size and slots on the canvas, and the Design panel's instance checks and position values, are kept with the document snapshot (held weakly), the selection and, for the overlay, the view's mapping to the screen and the overlay green, and made again when any changes. Dragging 648 selected layers: overlays 1.99 to 0.86 ms a frame, Design panel 2.54 to 1.40 ms.
* M8: split the position fields into one function per row (Sonar S3776)
The per-address download bucket never held more than its 16 MiB burst, so a larger library or plugin was refused for good. Bucket::take_spread serves one from a full bucket and leaves the debt to refill at the rate before the next download; the rate over time still holds.
The REST import's layer walk (also the .fig import's second stage) took about 4 KB of stack a level, a megabyte at the 256-level cap, and overflowed a 1 MB thread. It now runs on a work list in the same order, fitting groups once their children are in. The .fig mapping stays recursive at about 1.8 KB a level (455 KB at the cap). Both 256-deep imports now run on a 1 MB stack; the cap stays 256 everywhere.