Skip to content

feat(#66): "Connect to…" cross-scope connection gesture#73

Merged
CuriouslyCory merged 2 commits into
mainfrom
feat/66-connect-to-cross-scope
Jun 2, 2026
Merged

feat(#66): "Connect to…" cross-scope connection gesture#73
CuriouslyCory merged 2 commits into
mainfrom
feat/66-connect-to-cross-scope

Conversation

@CuriouslyCory

@CuriouslyCory CuriouslyCory commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Closes #66. The search-first gesture that resolves the original need — wiring a Component to one in another subtree without ever having both endpoints on screen — plus a Connections section in the Component-detail panel. Builds on #65's cross-scope rendering.

What changed

Two slug-readable reads (ADR-0032), deliberately distinct from scope-keyed getCanvas.

  • listProjectComponents — every live Component flat ({id, title, kind, parentId}); the project-wide search source. The flat parentId lets the client rebuild ancestor paths for disambiguation with no server walk.
  • listNodeConnections — a Component's complete incident Connections across scopes, far end resolved to its display fields. Node-keyed, so it includes lineal Connections to a Component's own descendants that collapse off its home Canvas and never appear in getCanvas. Both single round trip; neither folded into getCanvas's CTE.

ConnectToPalette — a cmdk surface modeled on the kind palette: search every Component at any depth by title/kind, muted ancestor-path label per row for disambiguation, excludes self + already-connected targets. Project-wide read is lazy (fires only when the popover opens).

Connections section in the Component-detail panel — list for everyone (viewer read-only), owner adds via the search. The "+ Add connection" affordance is omitted, not disabled for viewers (ADR-0002).

Optimistic cross-scope connect (commitConnect) — inserts the far-end boundary proxy this frame and reconciles temp→real ids on success. Where the edge lands on the current scope follows the same rep(N,S) partition getCanvas derives (ADR-0031), computed client-side from the already-loaded listProjectComponents map — correct for the same-Canvas, altitude, off-scope-proxy, and lineal-collapse cases live. (The RF store is seeded once and not re-seeded by a refetch, so the temp→real reconcile is manual, exactly like the same-Canvas drag path.) New Connections default to ASSOCIATION; the interaction is set afterward via the #65 picker.

Scope boundary

  • connectNodes stays the sole Edge writer (ADR-0028) — the gesture is a thin caller, no new write path or de-dupe logic.
  • Same-flow directional connect (drawing a typed Connection up front) is deferred; it would grow the directional arm in ~/lib/connection-rules.ts.

Docs (travel with the slice)

CONTEXT.md tense flips (Connection + Component-detail panel) and a new Connections section term; ADR-0032 records the node/project-keyed reads, the complete-across-scopes list, and the client-derived optimistic insert (refining the plan's invalidate-to-reconcile sketch once the RF-store-not-reseeded constraint surfaced).

Verification

  • pnpm check green.
  • 12 new service tests + full node/edge suites (140 passing) against real Postgres: same-Canvas / cross-scope / lineal incident edges; soft-deleted edge and far endpoint omitted; not-found slug.
  • dev-browser end-to-end: deep A → deep B across subtrees shows the far-end proxy + list row on A's canvas, the reciprocal proxy on B's canvas, the altitude edge on the common ancestor, persists across reload, and a capability viewer sees a read-only Connections list with no add affordance.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Connections section in the component detail panel showing all incident connections across project scopes
    • "Connect to…" gesture for owners to add connections via project-wide component search with optimistic insertion and rollback
  • Documentation

    • Updated glossary and ADR documenting the Connections UI and connect flow
  • Tests

    • Added coverage for project-wide search and connection-listing behaviors

Closes #66. The search-first gesture that resolves the original need — wiring a
Component to one in another subtree without both endpoints on screen.

- Two slug-readable reads, deliberately distinct from scope-keyed getCanvas
  (ADR-0032): listProjectComponents (project-wide flat search source) and
  listNodeConnections (a Component's COMPLETE incident Connections across scopes,
  including lineal ones that collapse off its home Canvas).
- ConnectToPalette: a cmdk surface modeled on the kind palette, with client-built
  ancestor-path labels for disambiguation and self/already-connected exclusion.
- Connections section in the Component-detail panel: list for everyone (viewer
  read-only), owner adds via the project-wide search (affordance omitted, not
  disabled, for viewers).
- Optimistic cross-scope connect (commitConnect): inserts the far-end boundary
  proxy this frame and reconciles temp→real ids on success. The on-scope render
  follows the SAME rep(N,S) partition getCanvas derives (ADR-0031), computed
  client-side from the already-loaded listProjectComponents map — correct for the
  same-Canvas, altitude, off-scope-proxy, and lineal-collapse cases live (the RF
  store is seeded once and not re-seeded by a refetch, so reconcile is manual).
- New Connections default to ASSOCIATION; interaction is set afterward via the
  #65 picker.

Docs travel with the slice: CONTEXT.md (Connection + Component-detail panel tense
flips, new Connections section term) and ADR-0032.

Verified: pnpm check green; 12 new service tests (+ full node/edge suites, 140
passing) against real Postgres; dev-browser end-to-end — deep A→deep B across
subtrees renders far-end proxy + list row on A's canvas, reciprocal proxy on B's,
altitude edge on the common ancestor, persists across reload, and the viewer sees
a read-only Connections list with no add affordance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
infinite-docs Ready Ready Preview, Comment Jun 2, 2026 10:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd61e22f-5252-4fbd-a927-41d9a6c4efa8

📥 Commits

Reviewing files that changed from the base of the PR and between 528052e and 26612bf.

📒 Files selected for processing (4)
  • docs/adr/0032-connect-to-gesture-node-keyed-reads-and-client-derived-optimism.md
  • src/app/p/[slug]/_canvas/canvas.tsx
  • src/app/p/[slug]/_canvas/component-detail-panel.tsx
  • src/app/p/[slug]/_canvas/connect-to-palette.tsx

📝 Walkthrough

Walkthrough

PR implements the cross-scope "Connect to…" gesture: a project-wide component search lets users wire a component to any other at any scope with optimistic boundary-proxy insertion and reconciliation. The component detail panel gains a Connections section listing incident edges (viewer read-only, owner editable via search). Canvas orchestration handles multi-store optimistic updates, temp-id reconciliation, and conflict rollback.

Changes

Cross-Scope Connection Gesture

Layer / File(s) Summary
Input schemas and derived types
src/lib/schemas.ts, src/lib/types.ts
listProjectComponentsInput and listNodeConnectionsInput Zod validators; ProjectComponent and NodeConnection types inferred from router outputs.
Service-layer data reads
src/server/architecture/node.service.ts, src/server/architecture/edge.service.ts
listProjectComponents returns all live nodes with parentId for ancestor reconstruction; listNodeConnections returns incident edges with far-endpoint details and soft-delete filtering.
tRPC router exposure
src/server/api/routers/architecture.ts
Public procedures listProjectComponents and listNodeConnections with actor-optional access and schema validation.
Project-wide component search UI
src/app/p/[slug]/_canvas/connect-to-palette.tsx
ConnectToPalette cmdk component with ancestor breadcrumb labels; ConnectToPopover wrapper with lazy TRPC querying, keyboard handling, and Escape/outside-click dismissal.
Component detail panel Connections section
src/app/p/[slug]/_canvas/component-detail-panel.tsx
Panel now requires slug and accepts owner-only onConnect callback; ConnectionsSection queries incident connections and renders search popover (owner) or read-only list (viewer); ConnectionRow renders endpoint glyph/title and interaction label.
Canvas orchestration and optimistic commitment
src/app/p/[slug]/_canvas/canvas.tsx
Scope-aware repOnScope resolver; commitConnect callback validates, applies multi-store optimistic updates (React Flow edges, boundary proxies, panel cache), reconciles temp IDs on success, and rolls back with conflict toast on failure; wiring updated to pass slug and onConnect into the panel.
Service-layer test validation
src/server/architecture/__tests__/edge.service.test.ts, src/server/architecture/__tests__/node.service.test.ts
Full test suites for listProjectComponents (flat listing, soft-delete, slug-only access, error cases) and listNodeConnections (same-canvas, cross-scope, lineal collapse, endpoint filtering).
Design documentation
docs/adr/0032-connect-to-gesture-node-keyed-reads-and-client-derived-optimism.md, CONTEXT.md
ADR-0032 specifies two separate reads and client-side rep(N, S) computation; glossary entries document Connection, Component-detail panel, and Connections section behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (Browser)
  participant Panel as ComponentDetailPanel
  participant Search as ConnectToPalette/Popover
  participant Canvas as Canvas.commitConnect
  participant Server as Server (commitConnect)
  participant Stores as React Flow + Panel Cache

  User->>Panel: Click "Connect to…"
  Panel->>Search: Open popover (lazy listProjectComponents)
  Search->>Server: listProjectComponents(slug)
  Server-->>Search: Component list (byId,parentId)
  User->>Search: Select target
  Search-->>Panel: ConnectTarget{id,title,kind}
  Panel->>Canvas: onConnect(sourceNodeId, target)
  Canvas->>Canvas: repOnScope(targetId, scopeId)
  Canvas->>Stores: Optimistic insert (panel list, edge, proxy)
  Canvas->>Server: commitConnect {source, targetRep,...}
  alt Success
    Server-->>Canvas: real ids (edgeId, proxyId)
    Canvas->>Stores: Reconcile temp -> real
  else Conflict
    Server-->>Canvas: CONFLICT
    Canvas->>Stores: Rollback optimistic inserts
    Canvas->>Panel: Show conflict toast
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Across the scopes a little rabbit hops,

It scours the trees and finds the missing stops,
Optimistic proxies pop into view,
Reconciled later when the server says true,
Hooray — two nodes are friends anew!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing a 'Connect to…' cross-scope connection gesture feature referenced in issue #66.
Linked Issues check ✅ Passed The PR implements all coding requirements from #66: project-wide component search, Connections section in detail panel with owner/viewer differentiation, optimistic cross-scope updates with boundary proxies, and supporting service functions (listProjectComponents, listNodeConnections).
Out of Scope Changes check ✅ Passed All changes are directly aligned with #66 objectives: new UI components (ConnectToPalette, ConnectionsSection), service functions, schemas, tests, and documentation (ADR-0032, CONTEXT.md) supporting the connect-to gesture.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/66-connect-to-cross-scope

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/server/architecture/edge.service.ts (1)

205-245: ⚡ Quick win

Consider filtering soft-deleted endpoints at the query level for better performance.

The current implementation loads soft-deleted endpoint nodes from the database and then filters them in application code (line 235). This approach is correct but less efficient than filtering at the query level, and it's inconsistent with the pattern used in getCanvas (lines 373-374 filter at the SQL level with AND sn."deletedAt" IS NULL).

♻️ More efficient query-level filtering

You can use Prisma relation filters within the OR clause to exclude edges with soft-deleted endpoints before they're loaded:

  const edges = await db.edge.findMany({
    where: {
      projectId: project.id,
      deletedAt: null,
-     OR: [{ sourceId: nodeId }, { targetId: nodeId }],
+     OR: [
+       { sourceId: nodeId, target: { deletedAt: null } },
+       { targetId: nodeId, source: { deletedAt: null } },
+     ],
    },
    select: {
      id: true,
      sourceId: true,
      interaction: true,
      label: true,
      source: {
-       select: { id: true, title: true, kind: true, deletedAt: true },
+       select: { id: true, title: true, kind: true },
      },
      target: {
-       select: { id: true, title: true, kind: true, deletedAt: true },
+       select: { id: true, title: true, kind: true },
      },
    },
    orderBy: { createdAt: "asc" },
  });

  const connections: NodeConnection[] = [];
  for (const edge of edges) {
    const sourceIsSelf = edge.sourceId === nodeId;
    const far = sourceIsSelf ? edge.target : edge.source;
-   // A soft-deleted far endpoint hides the Connection — the same posture
-   // `getCanvas` takes when an endpoint is dead.
-   if (far.deletedAt !== null) continue;
    connections.push({
      id: edge.id,
      interaction: edge.interaction,
      label: edge.label,
      sourceIsSelf,
      other: { id: far.id, title: far.title, kind: far.kind },
    });
  }
  return connections;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/architecture/edge.service.ts` around lines 205 - 245, The edges
query loads soft-deleted endpoint nodes and then filters them in code (checking
far.deletedAt), which is less efficient; update the db.edge.findMany where
clause to filter out edges whose endpoint rows are soft-deleted by adding
relation filters to each OR branch (e.g. replace OR: [{ sourceId: nodeId }, {
targetId: nodeId }] with OR: [{ sourceId: nodeId, source: { deletedAt: null } },
{ targetId: nodeId, target: { deletedAt: null } }]) and remove the runtime
far.deletedAt check so only non-deleted endpoints are returned by
db.edge.findMany.
src/app/p/[slug]/_canvas/canvas.tsx (1)

40-40: ⚡ Quick win

Use the ~/* alias for the new ConnectTarget import.

This new relative import extends the local src/* path style the repo is trying to avoid. As per coding guidelines "Use the path alias ~/* to reference src/* throughout the codebase".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/p/`[slug]/_canvas/canvas.tsx at line 40, The import for ConnectTarget
in canvas.tsx uses a relative path; update the import to use the project path
alias by replacing the relative "./connect-to-palette" import with the aliased
path "~/<path-to-connect-to-palette>" so that the ConnectTarget type is imported
via the ~/ alias (ensure the symbol ConnectTarget remains unchanged in the
import statement).
src/app/p/[slug]/_canvas/connect-to-palette.tsx (1)

56-78: Whole-project list is rendered up front into the DOM.

ConnectToPalette mounts a CommandItem for every project Component (cmdk renders all rows and hides non-matches via filtering). Unlike the bounded kind palette this models on, this list is project-wide and can grow without limit, so very large projects could create thousands of DOM nodes on popover open. Consider virtualizing the list (e.g. cmdk + a windowing layer) if projects are expected to scale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/p/`[slug]/_canvas/connect-to-palette.tsx around lines 56 - 78, The
Command list currently renders every project Component up front (see
ConnectToPalette mapping candidates -> ConnectItem inside
CommandList/CommandGroup, using ancestorPath and onSelect), which will create
thousands of DOM nodes for large projects; change this to a windowed/virtualized
list so only visible items are mounted — for example integrate a virtualization
library (react-window, react-virtual, or similar) to drive the
CommandList/CommandGroup rendering and render only the slice of candidates
visible in the viewport, mapping those items to ConnectItem (preserving
key=component.id and passing ancestorPath/component/onSelect) and keep
CommandEmpty and headings outside/above the virtualized viewport as appropriate.
Ensure keyboard search/filtering still works by virtualizing the filtered
candidates array rather than the entire project list.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/p/`[slug]/_canvas/canvas.tsx:
- Around line 853-856: The code currently computes targetRep from the cached
listProjectComponents (components, byId, repOnScope) after connectNodes
succeeds, which can keep an optimistic/incorrect on-scope representative if the
canvas changed; update the success/reconciliation path to re-read authoritative
canvas data instead of relying on listProjectComponents cache: call the
getCanvas query (or have the connectNodes mutation return the resolved
representative/proxy data) and derive targetRep from that fresh getCanvas result
before applying final reconcilation logic (replace usages of
utils.architecture.listProjectComponents.getData, components, byId and
repOnScope in this success path with data from getCanvas or the
mutation-returned resolved representative).

In `@src/app/p/`[slug]/_canvas/component-detail-panel.tsx:
- Around line 326-336: The UI currently treats a failed listNodeConnections
query the same as an empty result because it only checks isLoading and
connections; update the render logic in component-detail-panel to explicitly
handle the query error by checking isError (from the listNodeConnections
hook/query) before the empty-state branch and render an error message (include
the error.message if available) so failures are distinguishable from "No
connections yet."; locate the block using isLoading, connections, and
ConnectionRow and insert the isError branch there.

In `@src/app/p/`[slug]/_canvas/connect-to-palette.tsx:
- Around line 200-213: The UI currently treats missing components the same as
loading so a failed listProjectComponents query (isError=true) will still show
"Loading components…" forever; update the render to check isError and surface an
error state (or pass an empty array) instead of only checking isLoading ||
!components. Specifically, change the conditional around isLoading/components
to: if (isLoading) show the loading box; else if (isError) show an error box
that displays the query error and a retry action that calls the query's refetch;
otherwise render <ConnectToPalette components={components ?? []}
excludeIds={excludeIds} onSelect={...}> so ConnectToPalette receives a safe
array when data is missing. Use the existing query symbols
(listProjectComponents, isLoading, isError, error, refetch, components) and the
ConnectToPalette props to implement this.

---

Nitpick comments:
In `@src/app/p/`[slug]/_canvas/canvas.tsx:
- Line 40: The import for ConnectTarget in canvas.tsx uses a relative path;
update the import to use the project path alias by replacing the relative
"./connect-to-palette" import with the aliased path
"~/<path-to-connect-to-palette>" so that the ConnectTarget type is imported via
the ~/ alias (ensure the symbol ConnectTarget remains unchanged in the import
statement).

In `@src/app/p/`[slug]/_canvas/connect-to-palette.tsx:
- Around line 56-78: The Command list currently renders every project Component
up front (see ConnectToPalette mapping candidates -> ConnectItem inside
CommandList/CommandGroup, using ancestorPath and onSelect), which will create
thousands of DOM nodes for large projects; change this to a windowed/virtualized
list so only visible items are mounted — for example integrate a virtualization
library (react-window, react-virtual, or similar) to drive the
CommandList/CommandGroup rendering and render only the slice of candidates
visible in the viewport, mapping those items to ConnectItem (preserving
key=component.id and passing ancestorPath/component/onSelect) and keep
CommandEmpty and headings outside/above the virtualized viewport as appropriate.
Ensure keyboard search/filtering still works by virtualizing the filtered
candidates array rather than the entire project list.

In `@src/server/architecture/edge.service.ts`:
- Around line 205-245: The edges query loads soft-deleted endpoint nodes and
then filters them in code (checking far.deletedAt), which is less efficient;
update the db.edge.findMany where clause to filter out edges whose endpoint rows
are soft-deleted by adding relation filters to each OR branch (e.g. replace OR:
[{ sourceId: nodeId }, { targetId: nodeId }] with OR: [{ sourceId: nodeId,
source: { deletedAt: null } }, { targetId: nodeId, target: { deletedAt: null }
}]) and remove the runtime far.deletedAt check so only non-deleted endpoints are
returned by db.edge.findMany.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf8e48cf-52b1-4b49-8ec5-7ef0dee62af0

📥 Commits

Reviewing files that changed from the base of the PR and between b0a01bd and 528052e.

📒 Files selected for processing (12)
  • CONTEXT.md
  • docs/adr/0032-connect-to-gesture-node-keyed-reads-and-client-derived-optimism.md
  • src/app/p/[slug]/_canvas/canvas.tsx
  • src/app/p/[slug]/_canvas/component-detail-panel.tsx
  • src/app/p/[slug]/_canvas/connect-to-palette.tsx
  • src/lib/schemas.ts
  • src/lib/types.ts
  • src/server/api/routers/architecture.ts
  • src/server/architecture/__tests__/edge.service.test.ts
  • src/server/architecture/__tests__/node.service.test.ts
  • src/server/architecture/edge.service.ts
  • src/server/architecture/node.service.ts

Comment thread src/app/p/[slug]/_canvas/canvas.tsx
Comment thread src/app/p/[slug]/_canvas/component-detail-panel.tsx
Comment thread src/app/p/[slug]/_canvas/connect-to-palette.tsx Outdated
Three findings from the autofix run on PR #73:

- canvas.tsx commitConnect — fire a background `getCanvas.invalidate(canvasInput)`
  and `listProjectComponents.invalidate({slug})` after the temp→real reconcile, so
  a target reparented in-flight (e.g. by a concurrent MCP `move_component`) self-
  heals on the next remount. The live `rep(N,S)` derivation stays for the common
  case; the invalidate is the 1% edge-case safety net. No flicker — the RF store
  keeps its already-correct reconciled state. (ADR-0032 amended to record this.)
- component-detail-panel.tsx ConnectionsSection — destructure `isError` and add a
  branch so a failed `listNodeConnections` query renders "Couldn’t load
  connections." instead of falling through to "No connections yet." (TanStack v5
  leaves `data` undefined on first-fetch error).
- connect-to-palette.tsx ConnectToPopover — destructure `isError` and add a
  branch so a failed `listProjectComponents` query renders "Couldn’t load
  components." instead of spinning "Loading components…" forever after retries
  exhaust.

pnpm check green. No service-layer change; existing tests cover the queries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CuriouslyCory CuriouslyCory merged commit dd75be2 into main Jun 2, 2026
1 of 3 checks passed
@CuriouslyCory

Copy link
Copy Markdown
Owner Author

Fixes Applied Successfully

Addressed 3 CodeRabbit feedback items across 4 files (including ADR-0032).

Files modified:

  • src/app/p/[slug]/_canvas/canvas.tsx — background invalidate of getCanvas + listProjectComponents after successful commitConnect, so a target reparented in-flight self-heals on the next remount (the live rep(N,S) derivation stays for the common case; no flicker).
  • src/app/p/[slug]/_canvas/component-detail-panel.tsx — explicit isError branch in the Connections section so a failed listNodeConnections query is distinguishable from a genuinely empty list.
  • src/app/p/[slug]/_canvas/connect-to-palette.tsx — explicit isError branch in ConnectToPopover so a failed listProjectComponents query no longer spins "Loading…" forever after retries exhaust.
  • docs/adr/0032-…md — amended with the safety-net rationale.

Commit: 26612bf7629341a625c6c339fdbcb2add3b7aee6

The latest autofix changes are on the feat/66-connect-to-cross-scope branch. pnpm check green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Canvas: "Connect to…" cross-scope connection gesture

1 participant