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-06 17:50:11 +02:00
|
|
|
import { test, expect, type Page } from '@playwright/test';
|
2026-07-16 14:56:31 +02:00
|
|
|
import { openOverlayMenu } from './overlay-menu';
|
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-06 17:50:11 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Figma-like comments e2e (collab-presence 0005): two tabs of the real React
|
|
|
|
|
* app on the SAME schematic share the comments map in the per-file Y.Doc.
|
|
|
|
|
* Flow: alice pins a comment via comment mode → bob sees the pin, opens the
|
|
|
|
|
* thread, replies → alice sees the reply live → resolve → delete clears both.
|
|
|
|
|
*
|
|
|
|
|
* Uses eeschema (demo.kicad_sch — the comment bridge ships in the merged
|
|
|
|
|
* kicad_editor bundle, so pcbnew/eeschema only; pl_editor has no pins).
|
|
|
|
|
* The GAL dot is drawn by the wasm; the assertions here drive the DOM halves
|
|
|
|
|
* (hit targets, composer, popover) which is what this layer owns.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const SCOPE = 'default';
|
|
|
|
|
const ROUTE = 'demo.kicad_sch';
|
|
|
|
|
const TITLE = /demo — Schematic Editor/i;
|
|
|
|
|
|
|
|
|
|
async function bootAs(page: Page, user: string): Promise<void> {
|
|
|
|
|
await page.goto(`/${SCOPE}/projects/demo/${ROUTE}?user=${user}`);
|
|
|
|
|
await expect(page.locator('#canvas')).toBeVisible({ timeout: 120000 });
|
|
|
|
|
await expect
|
|
|
|
|
.poll(() => page.title(), {
|
|
|
|
|
message: `${user}: editor never reached the expected title`,
|
|
|
|
|
timeout: 120000,
|
|
|
|
|
intervals: [1000],
|
|
|
|
|
})
|
|
|
|
|
.toMatch(TITLE);
|
2026-07-20 20:59:24 +02:00
|
|
|
// The comment controls mount once the collab session + bridge are up. They
|
|
|
|
|
// live in the overlay menu's "Comments" section and are visible as soon as it
|
|
|
|
|
// opens — the old nested expand toggle (comment-bar-toggle) is gone, since the
|
|
|
|
|
// section itself is the group.
|
|
|
|
|
await openOverlayMenu(page); // the comment controls live in the overlay menu (0010)
|
|
|
|
|
await expect(page.getByTestId('comment-mode-toggle')).toBeVisible({ timeout: 30000 });
|
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-06 17:50:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Delete every leftover thread from previous runs — comments PERSIST in the
|
|
|
|
|
* room's ydoc (that's the feature), so the spec must start from a clean slate.
|
|
|
|
|
* Uses the controller's test handle; the UI delete path is covered below. */
|
|
|
|
|
async function deleteAllThreads(page: Page): Promise<void> {
|
|
|
|
|
await page.evaluate(() => {
|
|
|
|
|
const ctl = (window as unknown as {
|
|
|
|
|
__pcbjamComments?: {
|
|
|
|
|
threads(): Array<{ id: string }>;
|
|
|
|
|
deleteThread(id: string): void;
|
|
|
|
|
};
|
|
|
|
|
}).__pcbjamComments;
|
|
|
|
|
ctl?.threads().forEach((t) => ctl.deleteThread(t.id));
|
|
|
|
|
});
|
|
|
|
|
await expect(page.getByTestId('comment-pin')).toHaveCount(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('comment lifecycle across two tabs: create → reply → resolve → delete', async ({
|
|
|
|
|
page,
|
|
|
|
|
context,
|
|
|
|
|
}) => {
|
|
|
|
|
test.setTimeout(360000); // two full tool boots
|
|
|
|
|
|
|
|
|
|
await bootAs(page, 'alice');
|
|
|
|
|
await deleteAllThreads(page);
|
|
|
|
|
const pageB = await context.newPage();
|
|
|
|
|
await bootAs(pageB, 'bob');
|
|
|
|
|
|
|
|
|
|
// ── create (alice): comment mode → click the canvas → composer → submit ──
|
|
|
|
|
await page.getByTestId('comment-mode-toggle').click();
|
|
|
|
|
const catcher = page.getByTestId('comment-click-catcher');
|
|
|
|
|
await expect(catcher).toBeVisible();
|
|
|
|
|
await catcher.click({ position: { x: 400, y: 250 } });
|
|
|
|
|
|
|
|
|
|
const composer = page.getByTestId('comment-composer');
|
|
|
|
|
await expect(composer).toBeVisible();
|
|
|
|
|
await composer.locator('textarea').fill('check this net');
|
|
|
|
|
await page.getByTestId('comment-submit').click();
|
|
|
|
|
|
|
|
|
|
// The thread popover opens on the fresh pin; the pin target exists.
|
|
|
|
|
await expect(page.getByTestId('comment-popover')).toBeVisible();
|
|
|
|
|
await expect(page.getByTestId('comment-pin')).toHaveCount(1);
|
|
|
|
|
|
|
|
|
|
// ── bob sees the pin live, opens it, reads alice, replies ──
|
|
|
|
|
await expect(pageB.getByTestId('comment-pin')).toHaveCount(1, { timeout: 20000 });
|
|
|
|
|
await pageB.getByTestId('comment-pin').click();
|
|
|
|
|
await expect(pageB.getByTestId('comment-popover')).toBeVisible();
|
|
|
|
|
await expect(pageB.getByTestId('comment-message')).toContainText('check this net');
|
|
|
|
|
|
|
|
|
|
await pageB.getByTestId('comment-reply').fill('will fix');
|
|
|
|
|
await pageB.getByTestId('comment-reply').press('Enter');
|
|
|
|
|
|
|
|
|
|
// ── alice sees the reply live in her open popover ──
|
|
|
|
|
await expect(page.getByTestId('comment-message')).toHaveCount(2, { timeout: 20000 });
|
|
|
|
|
await expect(page.getByTestId('comment-message').nth(1)).toContainText('will fix');
|
|
|
|
|
|
|
|
|
|
// ── resolve (alice) → bob's popover flips to resolved ──
|
|
|
|
|
await page.getByTestId('comment-resolve').click();
|
|
|
|
|
await expect(pageB.getByTestId('comment-popover')).toContainText('resolved', {
|
|
|
|
|
timeout: 20000,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Resolved pins hide from the default (unresolved) pin set in both tabs…
|
|
|
|
|
await expect(page.getByTestId('comment-pin')).toHaveCount(0, { timeout: 20000 });
|
2026-07-16 14:56:31 +02:00
|
|
|
// …but stay reachable via the panel's "resolved" filter. (The popover
|
|
|
|
|
// click above closed the overlay menu — click-away — so re-open it.)
|
|
|
|
|
await openOverlayMenu(page);
|
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-06 17:50:11 +02:00
|
|
|
await page.getByTestId('comment-panel-toggle').click();
|
|
|
|
|
await page.getByTestId('comment-show-resolved').check();
|
|
|
|
|
await expect(page.getByTestId('comment-panel-item')).toHaveCount(1);
|
|
|
|
|
|
|
|
|
|
// ── delete (alice, thread author) clears the pin everywhere ──
|
|
|
|
|
await page.getByTestId('comment-panel-item').click();
|
|
|
|
|
await expect(page.getByTestId('comment-popover')).toBeVisible();
|
|
|
|
|
await page.getByTestId('comment-delete-thread').click();
|
|
|
|
|
|
2026-07-16 14:56:31 +02:00
|
|
|
await openOverlayMenu(page); // the delete click closed the menu (click-away)
|
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-06 17:50:11 +02:00
|
|
|
await expect(page.getByTestId('comment-panel-item')).toHaveCount(0, { timeout: 20000 });
|
|
|
|
|
await expect(pageB.getByTestId('comment-pin')).toHaveCount(0, { timeout: 20000 });
|
|
|
|
|
await pageB.close();
|
|
|
|
|
});
|