feat: ysync 0008 Stage D — wire the v2 items collab into the app + verify

- WasmTool: hard cutover to startKicadCollab (gates on
  kicadCollabSnapshotItems; scalar reconciler no longer runs in the app)
- file-seed (ysync 0005 tie-in): empty rooms are seeded from the opened
  file via fileToDoc/docToY (meta+layout+items — file recoverable from the
  Y.Doc alone); populated rooms still adopt (seed-once unchanged)
- binding: gate UP applies until seed() — the provider's initial state
  sync otherwise streams the full doc into an editor that already holds
  the file (trapped eeschema's paste path in the real app)
- pcbnew blobForItem: footprints Format a uuid-corrected copy directly —
  SaveSelection's copy regenerated mandatory-field uuids (FOOTPRINT copy
  ctor assigns into fresh fields), breaking wire identity (rebuild)
- roundtrip.spec on the v2 items wire; new passing pcbnew
  footprint-containment round trip (the 0004 containment win); full
  fixture stays fixme on the tracked envelope-parse limit
- real-app two-tab emit verified for eeschema + pcbnew (see 0008 doc)

Full kicad suite: 45 passed, 3 skipped (known), 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergő Törcsvári 2026-06-11 15:25:30 +02:00
commit 07863da416
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
7 changed files with 273 additions and 46 deletions

View file

@ -26,6 +26,8 @@
#include <eda_text.h>
#include <pcb_edit_frame.h>
#include <kicad_clipboard.h>
#include <io/kicad/kicad_io_utils.h>
#include <richio.h>
#include <tools/pcb_selection.h>
#include <geometry/shape_poly_set.h>
#include <geometry/shape_line_chain.h>
@ -253,8 +255,52 @@ json itemToJson( BOARD_ITEM* aItem )
// Serialize one live board item to a clipboard blob (used only for `added` payloads — NOT the
// diff unit, so `changed`/`removed` stay light and the blob never drives change detection).
//
// Footprints DON'T go through SaveSelection: its "make safe to transfer" step copies the
// footprint, and FOOTPRINT's copy ctor ASSIGNS the mandatory fields into the new footprint's
// freshly-constructed ones (`*existingField = *field`; EDA_ITEM::operator= keeps the target's
// uuid) — so Reference/Value/Datasheet/Description would carry NEW uuids in every blob,
// breaking the wire's identity-by-uuid (every emit would read as field remove+add, and round
// trips lose the field uuids). Instead we make the same safety copy ourselves, RESTORE the
// mandatory-field uuids from the source, and Format it directly — the same Format machinery
// SaveSelection uses internally, so asyncify behavior is identical.
std::string blobForItem( BOARD* aBoard, BOARD_ITEM* aItem )
{
if( aItem->Type() == PCB_FOOTPRINT_T )
{
const FOOTPRINT* src = static_cast<const FOOTPRINT*>( aItem );
FOOTPRINT copy( *src );
for( PCB_FIELD* field : copy.GetFields() )
{
if( field->IsMandatory() )
{
if( const PCB_FIELD* srcField = src->GetField( field->GetId() ) )
const_cast<KIID&>( field->m_Uuid ) = srcField->m_Uuid;
}
}
// The rest of SaveSelection's footprint safety steps, minus the refPoint move
// (the wire carries absolute positions).
for( PAD* pad : copy.Pads() )
pad->SetNetCode( 0 );
copy.SetLocked( false );
CLIPBOARD_IO io;
STRING_FORMATTER fmt;
io.SetBoard( aBoard );
io.SetOutputFormatter( &fmt );
io.Format( &copy );
copy.SetParent( nullptr );
copy.SetParentGroup( nullptr );
std::string out = fmt.GetString();
KICAD_FORMAT::Prettify( out, KICAD_FORMAT::FORMAT_MODE::COMPACT_TEXT_PROPERTIES );
return out;
}
PCB_SELECTION sel;
sel.Add( aItem ); // pointer-only insert; no mutation of the live item