Commit graph

546 commits

Author SHA1 Message Date
Gergő Törcsvári
ac64799187
sync: bump pcbjam-shared pointer — COMMUNITY_SCOPE (team-libs 0001)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bHL5vjZW4P5aNK9gKPTGf
2026-07-15 10:05:25 +02:00
Gergő Törcsvári
2ca3ede7f8
sync: bump pcbjam-shared pointer after rebase onto team-scoped main
collabDocGoodKey now takes scopeId (collabDocKey moved to
teams/<scopeId>/... keys on main).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:15:49 +02:00
Gergő Törcsvári
cb27744d76
tasks-runner 0001 R2: kicad_tools joins the CI build set + lint gates
docker/build.sh "all" now includes kicad_tools (finalizes in-container, so
it never contends with the editor's wasm-opt critical path); .ci-cache-epoch
bumped — the cached FINAL output set changes. wasm-build.yml gains the
corpus-lint + CLI-contract gate step (run_tests leg): with the artifact now
built in CI, the skip-when-unbuilt scripts bite. Release runs
(upload_output) start shipping kicad_tools in the wasm-output artifact —
the closed repo's runner-image workflow consumes it from there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:02:13 +02:00
Gergő Törcsvári
95e0edf6a6
tasks-runner 0001 T4+T6: binding revert-marker tests + CLI contract script
T4: kicad-binding.test.ts — revertNonce → DOC_REVERTED_EVENT dispatch via a
window stub (once per nonce; stale marker on open silent; observer gone
after destroy). T6: tests/tools/cli-contract.ts (npm run tools:contract,
skip-when-unbuilt): resave version bump + relint clean for board/schematic/
hierarchy/footprint (the CTL_FOR_LIBRARY version-header regression), exit
codes 2/4/1 — the exact contract run-tools-job.ts keys off. 15 checks
green. Also bumps pcbjam-shared (T3 unit tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:02:13 +02:00
Gergő Törcsvári
fb2aa2759d
kicad-validity B3+C+S: revert toast, corpus lint, .kicad_mod tier
B3 (editor): kicad-binding observes kdoc_meta.revertNonce (deduped, like
seedNonce) → DOC_REVERTED_EVENT → WasmTool doc-reverted-toast (15 s,
data-testid for the deferred e2e check).
C: tests/tools/corpus-lint.ts (npm run corpus:lint) — kicad_tools --lint
over the fixture corpora raw + shared-codec round-trips
(docToFile(fileToDoc(x))); the E3/wrapInBoardEnvelope-class gate. Skips
when output/kicad_tools.js isn't built; CI step lands when kicad_tools
joins the CI app set. 7 fixtures / 7 round-trips green.
S: --lint + --resave gain the .kicad_mod tier (pcbToolsLintFootprint /
pcbToolsResaveFootprint; writer needs CTL_FOR_LIBRARY or the board flags
strip the (version) header). Verified: qa footprint round-trip lints
clean, resave stamps 20260206, garbage exits 1. Also bumps pcbjam-shared
(mergeYUpdates for the bisect pass).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:02:13 +02:00
Gergő Törcsvári
b18b0a6a08
kicad-validity B2: bump pcbjam-shared — revert primitives + .good key
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:02:13 +02:00
Gergő Törcsvári
b3fd24c993
kicad-validity A2: bump pcbjam-shared — projectFile invalidReason
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:02:12 +02:00
Gergő Törcsvári
c51ccf82ba
kicad-validity A1: kicad_tools --resave — full-parse rewrite in current format
New --resave <file> <outdir> subcommand (root docs/features/kicad-validity/
0001): .kicad_sch one file per sheet mirroring the hierarchy layout,
.kicad_pcb via pcbToolsResaveBoard callback (mirror of the lint callback),
.kicad_sym/.lib via ConvertLibrary. Exit 0 ok / 2 usage / 4 input invalid /
5 write failed — only 4 marks the input invalid for the upload gate.
The s-expr schematic save stub is now #ifndef KICAD_TOOLS_COMBINED: the
merged image links the real writer (+65 KB), standalone sym_convert keeps
its diet. Board save needed no un-stubbing (pcb_io/ survives the CMake
prune).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
2026-07-14 19:01:34 +02:00
Istvan Matejcsok
985408ce9e fix(editor): File→Quit returns to the management app it was entered from
The quit hook only history.back()ed on a SAME-ORIGIN referrer, so the
primary closed-deploy entry — app.pcbjam.com deep-linking into
editor.pcbjam.com (cross-origin) — fell through to the editor's own
project view instead of returning to the app. Quit is Back-button
semantics (3a2255c): any non-empty referrer with in-tab history now
goes back; deep links / fresh tabs (no referrer) keep the project-page
fallback. Verified live against the closed stack: :3047 → editor →
quit lands back on :3047; a direct editor URL still falls back to the
editor's project overview.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:28:49 +02:00
Istvan Matejcsok
bea14c772f feat(storage): thread the owning team's scopeId into collab rooms
The closed backend's storage rework scopes collab rooms/keys by team:
room ids are now <scopeId>:<projectId>:<docPath> (@pcbjam/shared bump).
The editor threads a scopeId prop parallel to projectId — ToolPage takes
it from the project DTO (falling back to "local" for scope-less
backends; Home/LibTool pages pass "local" explicitly) — through WasmTool
into the doc session, sheet-manager warm rooms, and cross-app presence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:13:45 +02:00
Gergő Törcsvári
f9091a28c5
site(blog): devblog w28 — collab week (presence videos, y-sexpr v2, platform)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dz6jMCAoU9ip7mEshjZci7
2026-07-13 17:57:57 +02:00
Istvan Matejcsok
93af288ffd sync: bump submodule pointers after rebase 2026-07-13 12:52:03 +02:00
Istvan Matejcsok
bb01f5d9e6 feat(read-only-viewer): editor viewer mode + kicadSetReadOnly binding
Anonymous / non-member sessions open PUBLIC projects as read-only viewers.

- kicadSetReadOnly(bool) embind (merged kicad_editor + pcbnew/eeschema/
  pl_editor TUs): sets the PCBJAM_READ_ONLY flag + Prj().SetReadOnly (greys
  the setup dialogs). Polls until the frame exists.
- read-only-mode.ts: resolveReadOnly(access, win) — server `access:"read"` or
  ?readonly=1 (narrow-only; no ?readonly=0). ToolPage threads it in, omits
  saveBytes (MEMFS-only saves).
- WasmTool: chrome force-hidden with a "View only" pill (toggle + Cmd+\
  disabled), presence/cross-app/comments/drift skipped, save-driven room
  writers unregistered, wasm frame locked via kicadSetReadOnly failing CLOSED
  (stale bundle → boot error, never a writable frame).
- collab: bindKicadCollab {readOnly} — inert DOWN hook, never seeds a room;
  UP observer + adopt stay live so peer edits render. index.ts / sheet-manager
  thread readOnly + drop initial awareness (invisible observer).
- Reference backend emits access:"write".

Bumps kicad + web/pcbjam-shared to the read-only-viewer commits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DN9py5GuPdExaaFzE4k27
2026-07-13 12:48:35 +02:00
Gergő Törcsvári
f07b9970d5
fix: wrapInBoardEnvelope layer order
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
2026-07-13 09:13:27 +02:00
Gergő Törcsvári
f590cbd49c
feat: kicad_tools --drill + --plot-board
--drill <pcb> [outdir]: excellon drill files (kicad-cli JOB defaults, no
map files). --plot-board [--pdf] [--layers a,b,...] <pcb> [out]: single
SVG/PDF document via PCB_PLOTTER; layers default to the board's enabled
set, --layers takes canonical or user names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
2026-07-10 19:09:02 +02:00
Gergő Törcsvári
19014ae113
feat: kicad_tools --gerbers
--gerbers <pcb> [outdir]: one .gbr per enabled layer (stackup plot order,
kicad-cli defaults via default JOB_EXPORT_PCB_GERBERS + PlotJobToPlotOpts)
plus the .gbrjob file — mirrors JobExportGerbers minus the zone re-check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
2026-07-10 19:09:02 +02:00
Gergő Törcsvári
ad9c4675f9
feat: kicad_tools headless CLI
One merged node-WASM CLI (wasm/tools, 26.5MB) supersedes sym_convert +
pcb_convert: --convert-lib (absolutizes paths — fixes the legacy plugin's
silent empty output on relative paths), --lint (now full-parses .kicad_pcb
via the linked pcbnew parser), --erc, --netlist, --bom, --plot, --drc.
Registered as the only headless CLI app; ASYNCIFY=0 CLIs skip the
nanosleep→Asyncify yield shim and exit via _exit (dieted static dtors trap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
2026-07-10 19:09:01 +02:00
Gergő Törcsvári
396d4c1d45
fix(ci): route occ-export-models.spec to chromium-ci (BIG_MODULE_SPECS)
720cff5 added occ-export-models.spec.ts (boots the ~190MB merged pcbnew
module, prefetches 17 model bodies, runs a 13MB OCC STEP export in the
occ_service worker — twice, serial) but never listed it in BIG_MODULE_SPECS,
so on CI it ran on the firefox project: headed under Xvfb with the
baseline-only WASM JIT and llvmpipe. That CPU storm ran concurrently with
the chromium-ci pool and starved the SwiftShader 3D-viewer opens past their
180s cap — 3d-viewer.spec:299 (edge-resize) timed out at openThreeDViewer in
runs 29076702564 (main) and 29090433643 (PR), and 29076702564 also dropped
occ-export.spec:105's menu click. In both red runs the last console line
before the 180s silence is the misrouted spec's export.

Add it to BIG_MODULE_SPECS like every other pcbnew.html-booting spec: it now
runs on chromium-ci (V8, headless SwiftShader) and firefox ignores it
(verified with CI=1 playwright --list).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VYxyBRJnKKExFcA5CRPXjM
2026-07-10 18:25:37 +02:00
Istvan Matejcsok
1d62dc89a9 feat(site): pricing copy refinements (commenter role, pro seats, collab bullet)
Commenter role in the viewers/commenters FAQ (reader = view, commenter =
+add comments, editor = +edit); real-time collab listed on Free only, Pro/
Enterprise inherit via 'Everything from ...'; Team states seats are Pro
seats scoped to that team's projects only, plus a matching FAQ entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RQcUBzy8mpYXkup9eNY1z
2026-07-10 16:33:03 +02:00
Istvan Matejcsok
89fcdf24c4 feat(site): pricing page (plan cards, billing toggle, coming-soon dialog)
Free/Pro live, Team/Enterprise as coming-soon cards; monthly/yearly price
toggle; CTAs open a coming-soon dialog pointing at the waitlist; Pricing
link in header nav + footer; header chip nowrap + earlier hamburger
breakpoint; CheckItem gains an optional dimmed note suffix.

Copy source of truth: docs/features/pricing/copy.md (root repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RQcUBzy8mpYXkup9eNY1z
2026-07-10 16:33:03 +02:00
Gergő Törcsvári
4ea400a70d
ysync 0009: bump pcbjam-shared (collabDocArchiveKey)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142yAmMGZ3Ejrq3wwVr2Yq2
2026-07-10 13:49:25 +02:00
Gergő Törcsvári
28388990ee
ysync 0009 phase 4: sym_convert --lint mode (kicad_lint folded into the converter)
sym_convert --lint [--strict] <file...>: full parse for .kicad_sch (restored
LoadSchematicFile + minimal headless PGM/SETTINGS_MANAGER runtime,
aSetActive=false) and .kicad_sym/.lib; structure-only for other s-expr files;
dup-uuid identity lints (walker) on every s-expr input; errors as
file:line:offset, exit 0/1/2. Convert mode byte-identical (qa gate).

Fixes en route: 64KB stack local overflowed the default wasm stack (mimalloc
corruption); pre-js now inherits process.env into getenv (KICAD_CONFIG_HOME /
SYM_CONVERT_TRACE); font abort stub retired (SCH_SCREEN::Append bbox resolves
draw fonts) -> 18.0MB; six GetMsgPanelInfo typeinfo stubs; BUILD_3D_VIEWER
defaults OFF for headless CLIs (gl1 shim needs glm).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142yAmMGZ3Ejrq3wwVr2Yq2
2026-07-10 13:13:51 +02:00
Gergő Törcsvári
33e59fde00
ysync 0009 phases 1-3: bump pcbjam-shared (y-sexpr v2 codec); SexprVersionError skew guard in kicad-binding
Refuse to bind docs written by an unsupported s-expr encoding (update
required); surfaced via WasmTool's fatal status path. Guard unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142yAmMGZ3Ejrq3wwVr2Yq2
2026-07-10 10:29:56 +02:00
Gergő Törcsvári
720cff54ba
feat(occ): ship board 3D model bodies with STEP/GLB exports (3d-models 0007)
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).

- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
  the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
  back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
  (best-effort — prefetch failure still exports, misses reported by the
  exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
  under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
  staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
  provider + captures report/productCount; new occ-export-models.spec.ts
  guards the delivery (green companion pins preconditions; guard asserts 0
  missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
  87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.

Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
2026-07-10 09:26:07 +02:00
Gergő Törcsvári
9315f56760
libs: peer lib edits reach the running editor + "placed symbol updated" toast
synced-source subscribes to its SyncStack: remote changes (self-save echoes
consumed via a selfPushed flag) debounce per kind into kicadLibsReload — a
new embind export (pcbjam_libs_reload.h, all three TUs) that drops the lib's
plugin cache (LIBRARY_MANAGER::ReloadLibraryEntry), reloads it, and mails
MAIL_RELOAD_LIB with the nickname so the symbol tree force-refreshes (the
plugin's modify hash is a pinned constant, so a plain sync would skip it).

After the reload, kicadLibsSymbolUsage (new eeschema embind: placed
SCH_SYMBOL count across unique screens) gates LIB_ITEM_UPDATED_EVENT, and
WasmTool shows an amber toast when a PLACED symbol changed — placed copies
keep the previous version until updated from the library.

syncedScopeLibsSource gives PROJECT sessions the synced source under
VITE_LIBS_SOURCE=synced (remote contract for lib listing/createLib, lazy
per-lib SyncStacks for item ops/presync) so realtime reaches open
schematics; previously project sessions silently fell back to the per-item
remote source. Unit tests cover reload debounce, self-echo skip, per-kind
routing, usage-gated event, and the no-Module no-op.

Bumps kicad (MAIL_RELOAD_LIB force-refresh payload).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QRWoXiM9uuo1enXGhAYku
2026-07-10 09:26:07 +02:00
Gergő Törcsvári
c229c75ca6
fix(collab): re-push viewport on canvas resize — comment pins sat offset until pan/zoom
emitViewportIfChanged deduped on scale+center only, so a canvas SIZE change
(boot layout settling after the bind-time seed, window resize) never re-pushed
{w,h} to JS. CommentLayer's worldToScreen maps through h/2, so every DOM pin
hit target (and its hover ring) sat vertically offset from its GAL dot by
exactly delta-h/2 css-px until the next pan/zoom finally passed the dedupe.

Size now participates in the dedupe and wxEVT_SIZE re-pushes post-layout
(CallAfter, after the GAL's own onSize). New regression e2e
comments-viewport-resize.spec.ts asserts the DOM pin re-aligns with GAL truth
(fresh kicadCollabGetViewport) across a window resize with no pan/zoom.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011x3h5AzkWDbHmCkeVYAuzU
2026-07-10 09:25:14 +02:00
Gergő Törcsvári
f8da88472c
sym_convert wasm diet: 31.3->11.05 MB via kiface prune + link-order shims (ysync 0009)
New wasm/cli/sym_convert_stubs.cpp: first-definition-wins overrides
(--allow-multiple-definition) severing schematic load/save, the KIFONT
factory (drops newstroke + freetype/harfbuzz), and the four UI virtuals
whose bodies reference pruned typeinfo/data. New
wasm/bindings/sym_convert_embind.cpp: no-op kicadCollabOnSave so the
converter stops linking eeschema's embind object (and --bind) that rooted
the editor surface from .init_array. build-kicad-target.sh: per-app
EMBIND_LINK_FLAG + sym_convert now uses its own embind TU. Bumps kicad
(kiface prune, gated). Output byte-identical on the qa corpus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H58sC87w12FotXEmxrJrQV
2026-07-10 09:25:14 +02:00
Gergő Törcsvári
1f85e13acf
feat(collab): follow-user (collab-presence 0008) + chip depth-layer fix
Follow-user: click a peer's roster avatar to mirror their viewport until
local input breaks it.
- collab_presence_core.h: CORE::fitViewport(cx, cy, halfW, halfH) — fit the
  leader's world rect with CONTAIN semantics (zoom derived from the
  follower's own canvas via the ToScreen ratio; GetScale is the zoom, not
  px/IU). Exported as kicadCollabFitViewport from both editor TUs + the
  merged dispatcher.
- presence-kicad.ts: publish the visible world rect (viewportRect) into
  awareness, 100 ms trailing throttle; guarded for pre-0008 handles.
- follow-user.ts: createFollow — follows an awareness CLIENT (a tab, not a
  user); applies leader rect changes via FitViewport, dedupes unchanged
  republishes; break-on-interact compares local onViewport echoes against
  the last applied rect (2% rel tolerance, echo-grace before the first fit
  lands); unfollows on leader-left; pauses on eeschema sheet mismatch.
- PresenceRoster: avatars are follow toggles (ring on the followed peer);
  WasmTool renders the "Following <name> — move to stop" banner.
- tests: 7 controller units (85/85 collab), fitViewport round-trip e2e in
  both kicad presence specs (20/20), two-tab tests/web/follow.spec.ts
  (converge → track → wheel-zoom breaks → subsequent moves ignored).

Chip depth-layer fix (user-reported): name chips washed out inside
low-alpha selection fills — chip rects shared the shapes overlay's single
depth, and same-depth fragments drawn LATER lose the depth test, so an
earlier-painted fill rejected the chip's pixels. Now three layers via the
fork's VIEW_OVERLAY::SetDepthOffset: text (0) < chips + pin dots (1) <
selection shapes (2). drawLabel/drawCursor/drawSelectionBox take the chip
overlay explicitly; comment-pin dots move to the chip layer too (the 0005
"drawn last so pins sit above" comment had the rule backwards). Verified
with a chip-inside-30%-fill pixel repro + the full presence suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u9h8fkQktH7KRECaHJmUG
2026-07-10 09:25:13 +02:00
Gergő Törcsvári
9697de7b26
deploy: pass --models-tag in release.yml demo + editor builds
The tag-triggered release pipeline built the demo (and backed editor)
without --models-tag, so modelsSourceConfig() returned null and every
kind=model3d ensure logged "(unserved)" — 3D component models never
loaded on demo.pcbjam.com. The manual deploy-demo.yml already passed
it; mirror its MODELS_TAG env (10.0.3) in release.yml for both builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQRBzfidkoTU5yc7HnF9x7
2026-07-10 09:22:52 +02:00
Gergő Törcsvári
855137c650
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h)
The eeschema/pcbnew binding TUs had ~1000 lines of copy-pasted collab code.
Factored into two header-only shared files (zero build-script changes — the
collab_presence_style.h precedent):

- collab_common.h (pcbjam_collab): toUtf8, runOnFiber (the CallAfter+COROUTINE
  fiber idiom — was ~25 inline copies), the window.kicadCollab wire emitters
  (onDelta/onItems/onCursor/onSelection/onViewport), frame-generic undo test
  hooks.
- collab_presence_core.h (pcbjam_presence::CORE): PEER/PIN + all presence
  state and machinery (start/canvas binds/lock query, setRemote/setPins/
  setStyle, selection check + dedupe, overlay redraw loop, viewport push/pull,
  releaseSelection, locks probe), written against the EDA_DRAW_FRAME +
  SELECTION_TOOL base classes. Per-editor hooks: frame, selectionTool,
  selectionEmitPayload, resolveItem, drawPeerShapes. One CORE instance per TU
  (anonymous-namespace presenceCore()) so the merged image keeps per-editor
  state separation.
- NEW per-editor resolveXsel(frame, peer): ONE cross-app resolver shared by
  the ghost render AND kicadCollabTestGetCrossMapped — the mapping loop was
  duplicated within each TU, letting the test probe drift from the pixels.

Deliberately NOT factored: the Yjs differ/apply halves (itemToJson/makeItem/
flushDiff/doApply*) — structurally parallel but the bodies encode per-editor
sync semantics and editor-specific asyncify devirtualization workarounds that
must stay visible. kicadOpenFile/kicadCollabOnSave keep the existing
KICAD_MERGED_EMBIND mechanism. TestClearSelection stays editor-typed
(ClearSelection is not on the SELECTION_TOOL base).

eeschema_embind 2203→1721 lines, pcbnew_embind 2518→1993. JS-facing names,
signatures and the kicad_editor_embind.cpp dispatcher are unchanged.

Verified: kicad_editor image builds clean; tests/kicad presence+locks 18/18
(incl. ghost-render pixel compares), collab+ysync-repros 31 passed/2 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u9h8fkQktH7KRECaHJmUG
2026-07-10 09:17:32 +02:00
Istvan Matejcsok
d44ba9bdfb feat(mobile): Figma-like hide-UI toggle — Cmd/Ctrl+\ hotkey + floating button, snapshot-exact chrome restore
Chrome visibility is now a runtime toggle on any device instead of being
device-wired: mobile defaults to canvas-only, desktop to full UI, and the
floating top-right button (or Figma's Cmd/Ctrl+\ chord — free in KiCad,
only bare \ is bound) flips between them live.

- chrome-visibility.ts: module-global store (default isMobileMode(),
  session-only) + pure hotkey matcher (rejects AltGr backslash + repeats)
- WasmTool: capture-phase hotkey (stopped before the wx layer), floating
  toggle pill (matches the comment FAB design), useLayoutEffect apply with
  sync first call + retry; overlays follow the toggle, capability-gated on
  the kicad_editor bundle's kicadSetChrome export
- boot.ts mobile opt now installs touch gestures only
- kicadSetChrome: frame-keyed hide-time snapshot so restore re-shows ONLY
  what hide took away (blanket Show(true) surfaced KiCad's default-hidden
  Search/Properties/Net-Inspector panes); toolbars-only fallback otherwise
- tests: chrome-toggle.spec.ts (desktop hide/restore + geometric
  restore-exactness ±3px), mobile toggle round-trip, 12 new unit tests,
  test:web:mobile npm script

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 16:15:37 +02:00
Istvan Matejcsok
dc7c60f723 feat(mobile): canvas-only mobile mode — pinch-zoom/pan/tap gestures + chrome-less editors
On a mobile device (or ?mobile=1) the editors run canvas-only with touch
gestures driving the view:

- touch-gestures.ts: pure recognizer (unit-tested) + DOM shim installed in
  boot preRun — one-finger drag → synthetic middle-drag (pan), pinch →
  synthetic wheel at the centroid (zoom-to-cursor), tap → left click.
  preRun registration order is what lets stopImmediatePropagation suppress
  the wx layer's single-finger→LEFT-drag touch mapping.
- kicadSetChrome(bool) embind: hides all AUI panes except DrawFrame + the
  menubar/status bar via generic wx APIs (kicad fork untouched); boot polls
  it after runtime init. Pairs with the wxwidgets IsShown layout fix.
- mobile-mode.ts: ?mobile=1/0 override or UA-CH/coarse-pointer autodetect;
  shell hides its overlays and the inherent-to-mobile preflight warnings.
- e2e: mobile-chromium project (Pixel 7) + 4 specs (chrome-less, tap,
  pinch, pan) with screenshot-invertibility assertions; also fixes
  tool-switch.spec's stale pre-scope-refactor URLs (was broken on main).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 16:15:37 +02:00
Istvan Matejcsok
3a2255c817 feat(editor): File→Quit navigates back to the project page
Every KiCad editor's File→Quit used to destroy the wx top frame and
notify nobody, leaving the SPA stranded on the editor URL with a dead
canvas. It now behaves like the browser Back button: return to wherever
the user entered the editor from (project page, or a previous editor).

- WasmTool installs window.wxAppTopWindowClosed (the wx wasm port fires it
  from the main frame's destructor — see the wxwidgets pointer bump). On a
  real quit it history.back()s when there is same-origin in-app history,
  else location.assign()s the project page (or "/" for lib editors).
- The navigation is deferred one task (setTimeout 0): it fires inside the
  C++ destructor via EM_ASM under Asyncify, and a cross-document
  location.assign() started there is aborted by the continuing teardown;
  only same-document history.back() survives synchronously.
- Latches off on pagehide (the port also closes the frame on page unload
  via UnloadCallback) and reloads on a bfcache pageshow so Forward never
  restores a dead frame.
- e2e: quit-to-project.spec.ts covers both the history-back and the
  deep-link fallback paths.

No kicad changes — the whole hook lives in the wxWidgets wasm port and the
web app, keeping the kicad fork clean for upstream syncs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:19:24 +02:00
Istvan Matejcsok
d43f8ca719 deploy: Plausible analytics on editor.pcbjam.com (own site)
build-editor.mjs already threads --plausible → VITE_PLAUSIBLE_DOMAIN; the
deploy-editor job now passes editor.pcbjam.com so the backed editor tracks
as its own Plausible site (separate from the demo's vars.PLAUSIBLE_DOMAIN).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dasZuqo6FStgT3rkC85im
2026-07-08 15:34:53 +02:00
Gergő Törcsvári
fdac450a05
test(presence): rate-based cursor-throttle bound — deflake slow CI runners
The cursor-emit tests asserted a fixed <25 emits for 30 awaited mouse
moves, assuming the sweep outpaces the 50 ms throttle. On a loaded
Ubicloud runner each Playwright->Firefox->WASM round-trip can straddle
a throttle window, so 28/30 moves emitted and the test failed (twice on
main, once from a docs-only commit).

Bound by measured sweep duration instead: one emit per 50 ms window,
+1 leading edge, +2 slack. Fast machines get a tighter bound (~9-13);
slow runners scale with actual elapsed time. A broken throttle still
fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-08 12:19:25 +02:00
Istvan Matejcsok
a0df92bfd1 deploy: backed editor at editor.pcbjam.com (remote mode, same CDN WASM)
New release.yml job `deploy-editor` (parallel to deploy-demo, same per-tag
WASM manifest): builds the standalone in REMOTE mode via the new
scripts/deploy/build-editor.mjs — projects/libs/auth from the closed API
(VITE_API_BASE_URL=https://api.pcbjam.com, VITE_LIBS_SOURCE=synced), Yjs
board rooms through the API host's path route (VITE_YJS_ENDPOINT=api origin,
doc source ydoc) — and ships it to the pcbjam-editor Pages project. The
demo deploy is unchanged. The closed stack itself (api./app.pcbjam.com)
deploys from pcbjam-private; when both sides change, that repo tags first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dasZuqo6FStgT3rkC85im
2026-07-08 11:42:38 +02:00
Gergő Törcsvári
574284c486
feat(collab): local-ops-only undo — remote applies skip the undo stack (ysync miss 09)
Ctrl+Z after a peer's edit no longer reverts (and re-broadcasts) the
peer's work, and the adopt undo-bomb is gone:

- doApply/doApplyItems (both editors) Push with SKIP_UNDO; the emit path
  is unaffected (suppression keys off s_applyingRemote, not undo).
- With SKIP_UNDO no picker owns removed items — the bindings free them
  after Push (explicit removals + upsert's remove-before-re-add; fields
  excluded: CHT_REMOVE hides them, parent keeps ownership). Freeing stays
  out of the fork commit classes so DRC's SKIP_UNDO callers can't
  double-free.
- Test hooks kicadCollabTestUndo/UndoDepth, registered per-editor AND in
  the kicad_editor dispatcher (merged image compiles out per-app
  registrations).
- kicad pointer: eeschema UUID undo guard + SKIP_UNDO connectivity split
  + quiet stale-entry drop (ca8877324c).
- tests/kicad/collab-undo.spec.ts: 5 scenarios (no undo entry from remote
  applies; selective undo; stranded replaced/deleted entries) — 5/5, plus
  collab/ysync regression 30 pass.
- docs: ysync-review 20 fix record; 09 marked FIXED; overview indexed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ejJEvS7ogef2o9gVTXjmp
2026-07-08 11:09:26 +02:00
Gergő Törcsvári
cd68114cdc
docs(ysync-review): 19 — miss-09 option 1 (local-ops-only undo) feasibility research
Verdict: ~3-5 days. pcbnew undo already UUID-guards stale pointers; core
work = porting that guard to eeschema, splitting connectivity out of the
SCH SKIP_UNDO gate, plugging the SKIP_UNDO removed-item leak, silencing
the incomplete-undo modal. Cross-linked from 09 and the overview index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ejJEvS7ogef2o9gVTXjmp
2026-07-07 21:09:24 +02:00
Gergő Törcsvári
c30bcd4531
feat(collab): selection soft-locks — remote-selected items can't be dragged locally (collab-presence 0007)
While a peer has an item selected, local users can still select it for
inspection but move/drag/rotate/delete skip it with an infobar naming the
holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK
query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping
holds (both grabbed inside the awareness propagation window) tie-break
deterministically: lowest (user.id, clientID) keeps the item, every losing
client auto-releases it.

- lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other
  clients' selections incl. own user's other tabs, minus own-held-and-winning
  uuids so the winner isn't blocked mid-release), contestedReleases()
- presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for
  a pre-existing flaky stack overflow — resolveCollision re-entered itself
  synchronously via its own patch's awareness 'change' and could ping-pong on
  stale same-user states (~1-in-3 unit runs); re-entrancy guard defers
  re-resolution to the next genuine delivery
- presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot
  (`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection
- wasm bindings (both TUs + merged dispatch): g_locks map + fork query
  install; kicadCollabReleaseSelection (cancelInteractive only when a tool
  stack is live — bare ESC would clear the whole selection — then selective
  RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked
- tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real
  move veto — pcbnew click+M hotkey since its default left-drag is
  rubber-band select, eeschema real drag — each with an unlocked control);
  two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak
  release + unlock on clear, passing vs real partykit)

Spec: docs/features/collab-presence/0007 (closed repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 21:09:24 +02:00
Gergő Törcsvári
3186986a0a
feat(collab): cross-app selection — eeschema symbol ⇄ pcbnew footprint ghost highlight (collab-presence 0006)
Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in
every pcbnew tab of the project, and vice versa — across users AND one
user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame
per page); this rides the presence layer instead.

- cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes
  full PresenceState at selection rate (cursor always null); peers() = other-
  TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle
- presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}),
  xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim),
  cross peers appended to the kicadCollabSetRemote snapshot as
  {id "<user>#x<client>", name "<user> · sch|pcb", xsel}
- C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath)
  and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via
  ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike
  room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner-
  patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped /
  TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) +
  merged-image dispatch
- tests: presence suites extended (payload shape, ghost render pixel tests,
  13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real
  partykit); eeschema pixel compares now target the #glcanvas-* GAL panel
  (the whole-window #canvas compare flaked on the auto-dismissing version
  infobar — also fixes the long-known presence-eeschema restore flake);
  cross-app + presence-kicad vitest suites

Spec: docs/features/collab-presence/0006 (closed repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
157d60f6b8
feat(standalone): 8-way parallel lib presync + stable load counters
Library warm-up and the per-item fallback fat-load now run through a
shared asyncMap helper (counting semaphore, 8 in flight): the CDN
presync's hand-rolled 6-worker pool is replaced and the previously
fully-serial listItems+getItemBody fallback is parallelized (order
preserved).

The load-screen sync line stops flashing: progress reports on lib
COMPLETION only (no "current lib" — with 8 in flight there isn't one),
the label is a fixed prefix ("Syncing symbol libraries — ") and the
counter is space-padded to the total's width in font-mono +
whitespace-pre, so 2→3 digit growth doesn't shift the text. Same
padding for the fat-load overlay's "x / y libraries" line.

Verified in demo mode against the live R2 CDN: 7-8 overlapping
manifest/bundle requests, 222 cold symbol libs in a few seconds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
93e73c590e
footer build SHA auto-resolve + devblog w27
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dz6jMCAoU9ip7mEshjZci7
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
ef15c0d863
feat(collab): ship the tuned presence style as per-editor defaults
Defaults picked with the PresenceTuner (2026-07-07):
- pcbnew: exact-outline selection, 6px stroke @0.7, 46% fill, bottom-end
  chips @7.5px, cross cursor 8/3px @1.0 with chip label, 9px pins w/ 3px ring.
- eeschema (eeschemaDefaultStyle): same look softened for the schematic
  canvas — hairline 1px outline, 14% fill, 0.5 cursor alpha.
- New chipBgAlpha knob (default 0.7 = the border alpha) for label AND cursor
  badges, + tuner slider.
- Tuner: per-tool defaults + per-tool localStorage keys; Reset returns each
  editor to its own shipped look; tool prop threaded from WasmTool.
Verified: pure defaults (no style push) render the picked look in both
editors; presence suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
c8c2c5f583
fix(collab): name badges render for real — text overlay above a depth-offset shapes overlay
The full root-cause chain of the empty/mangled badges:
- GAL text justify is painter-residue → PRESENCE_TEXT_OVERLAY pins TOP-LEFT.
- VIEW_OVERLAY::ViewDraw hard-sets EVERY overlay to GetMinDepth(), so the
  shapes and text overlays always collided at one depth, where later-drawn
  fragments lose (and bitmap glyphs are textured quads whose transparent
  cells also write depth — punch-through produced cell-shaped holes instead).
  Fix rides the new fork VIEW_OVERLAY::SetDepthOffset: shapes at min+1,
  labels at min — 'rect first, text on top' now holds regardless of paint
  order. Verified: chips contain crisp names (dark-on-light, white-on-dark)
  for selection tags and cursor labels; presence suites 10/10.
- kicad pointer bump (fork 24c5854d5b).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
4c287c2a27
fix(collab): deterministic nameplates — pin GAL text justify + punch chip text through the depth test
Two root causes of the wandering/invisible badge text:
- A plain VIEW_OVERLAY draws with whatever text justify the LAST painter left
  in the GAL (CENTER is only the reset default) — anchoring was
  nondeterministic. PRESENCE_OVERLAY (VIEW_OVERLAY subclass, replaces
  MakeOverlay) pins TOP-LEFT justify before executing its commands; the label
  math is written against that.
- The whole overlay draws at ONE depth and same-depth fragments drawn later
  LOSE the depth test — a chip rect over its text erased the text. Draw the
  text FIRST, the chip rect AFTER: the rect is rejected exactly on the glyph
  pixels, punching the text through.
Verified live: chips contain their names on light (dark text) and dark
(white text) user colors, for both selection tags and cursor labels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
21e3df8fd0
feat(collab): nth-in-room colors, comment toolbar + drag + global hide, luminance chip text
- presence.ts: colors claimed by ARRIVAL ORDER — each client takes the lowest
  free palette slot (no birthday-problem hash collisions; the best palette
  colors go first; N ≤ palette-size editors always distinct). Coordination-
  free: claims ride awareness; simultaneous-join collisions converge (lower
  clientID keeps, the other re-claims), same-user tabs adopt one color,
  claims are sticky across eeschema sheet rebinds (skeletons reuse them).
  colorOf(userId) resolves live colors; comment pins/popovers follow it
  (offline authors fall back to the hash). +2 unit tests (verified live:
  alice=slot0, bob=slot1, roster/pins consistent).
- CommentLayer: single comment icon expanding into a horizontal bar — new
  comment · list · show/hide all (eye empties the GAL pin set + DOM targets;
  re-shown on new-comment/panel-jump). Pins are DRAGGABLE: live LWW anchor
  writes while dragging (peers + the GAL dot follow), nearest-item re-snap on
  drop, click-vs-drag by 4px threshold (verified live: drag synced to the
  peer tab exactly). Shared setThreadAnchor + controller moveThread/
  setPinsVisible/colorFor.
- collab_presence_style.h: chip text color by background luminance (dark on
  light chips, white on dark — BitmapText draws with the stroke color).
- comments/presence-roster e2e updated for the bar + passing; collab units
  56/56; shared 122/122; shared pointer bump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
0f19811602
feat(collab): tuner round 2 — center-anchored labels, exact outlines, varied demo set, clearer color modes
- collab_presence_style.h: GAL BitmapText CENTERS on its position (confirmed
  in GAL::ResetTextAttributes — the mispositioned nameplates); labels/chips
  now hand GAL the block center. New selection shape 5 'exact outline':
  pcbnew hugs real geometry (footprint bounding hull, TransformShapeToPolygon
  for the rest, padding inflates the polygon); eeschema falls back to rect.
- kicadCollabTestDemoSet (both TUs + merged): labeled demo groups — smallest
  + largest footprint and the two busiest nets' segments (symbols + wire
  bundles on sch) — so the style preview covers the real range of shapes.
- PresenceTuner: Colors section rebuilt as explicit modes (per-user / fixed /
  palette) with preset palettes (default, pastel, vivid, okabe-ito), buffered
  hex editing + Apply (the old always-filtering textarea ate keystrokes), an
  'overlay only' hint; demo injection consumes the varied demo set; 'exact
  outline (pcb)' in the shape list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:22 +02:00
Gergő Törcsvári
ce99773af4
feat(collab): dev-time presence style tuner (VITE_PRESENCE_TUNER=1)
Parametrizes every visual knob of the presence overlay so we can pick the
shipped look live, then wire the winners into the defaults:
- collab_presence_style.h: shared STYLE struct + drawing (now used by BOTH
  editor TUs — no more duplicated overlay code): selection shape (rect /
  corner brackets / underline / rounded rect / filled-only), border width +
  alpha, infill alpha, padding, corner radius; name tag show/size/chip-
  background/inside-outside/top-bottom/start-end-center/offset; cursor shape
  (cross / pointer / circle+dot), size/width/alpha + label knobs; fixed-color
  and palette-by-name-hash overrides (try palettes without changing what
  senders publish); pin radius/ring/alphas. Defaults == shipped look.
- kicadCollabSetStyle(json) live-patch export + kicadCollabTestListItems(n)
  (real KIIDs for synthetic previews); merged dispatch; pins now carry the
  author name so palette overrides recolor them consistently.
- PresenceTuner.tsx: floating dev panel (env-gated, tree-shaken otherwise) —
  grouped sliders/selects, demo peers+pins injection for SOLO tuning,
  localStorage persistence across reloads, Copy JSON export, reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:22 +02:00
Gergő Törcsvári
635aa2926a
feat(comments): figma-like comment pins + threads (collab-presence 0005)
Hybrid pins: the wasm draws the dot (kicadCollabSetPins rides the presence
VIEW_OVERLAY, author color + white ring, drawn above selections; zero
kicad-fork changes), the DOM owns interaction —
- comments.ts: controller gluing the MIT kdoc_comments helpers to the editor:
  anchor resolution per tool IU (pins track item moves via kdoc_items
  observation), throttled pin snapshots, anchorAt nearest-item snap, jumpTo
  via new kicadCollabSetViewport; rebinds per sheet like presence.
- CommentLayer.tsx: comment mode (click catcher + composer), pin hit targets
  over the GAL dots, thread popover (reply/edit/delete own, resolve/reopen,
  delete thread), panel with resolved filter + jump-to (popover centers when
  the pin is off-screen). Resolved pins drop figma-style.
- WasmTool: controller lifecycle beside presence; live viewport feed;
  window.__pcbjamComments test handle (threads persist in the room ydoc).
- e2e tests/web/comments.spec.ts: two-tab create → reply → resolve → panel
  filter → delete, passing vs real partykit; presence suites + collab units
  stay green; shared pointer bump (0004 model).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:22 +02:00
Gergő Törcsvári
6bc54de92b
feat(collab): presence P3 — eeschema port (collab-presence 0003)
- eeschema_embind.cpp: presence section (0002 pattern, zero fork changes) —
  wx canvas triggers + SCHEMATIC_LISTENER piggyback → post-settle
  SCH_SELECTION_TOOL emit; throttled cursor; remote VIEW_OVERLAY render
  (SCHEMATIC::ResolveItem, name tags, screen-constant via GAL matrix);
  schCollab{PresenceStart,SetRemote,GetViewport,GetSelection,TestSelectFirst,
  TestClearSelection}; kicad_editor_embind dispatches by active frame.
- sheet-manager: parked rooms carry SKELETON awareness states
  ({user,tool,sheetPath=bound sheet}) via publishSkeletons on switch + late
  warm-up — the bound room's awareness then holds every project peer, so no
  multi-room aggregation; presence.ts publishSkeleton helper.
- PresenceRoster: sheet-aware — peers on another sheet render dimmed with
  'on <sheet>' tooltip; WasmTool un-gates the kicad presence bridge for
  eeschema and threads activeSheetPath.
- tests: presence-eeschema.spec.ts (5 e2e, mirrors pcbnew incl. the px/IU
  band at eeschema's 1e4/mm IU); presence.test.ts +2 (skeleton visibility,
  no ghost cursor after rebind). eeschema-collab/subschema stay green.

Verified live: two tabs on demo.kicad_sch — peer cursor cross + label,
selection box + name tag, roster avatar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:22 +02:00