Skip to content

Make connection direction structural, remove EdgeDirection enum#31

Merged
CuriouslyCory merged 2 commits into
mainfrom
feature/feat/structural-ports
May 28, 2026
Merged

Make connection direction structural, remove EdgeDirection enum#31
CuriouslyCory merged 2 commits into
mainfrom
feature/feat/structural-ports

Conversation

@CuriouslyCory

@CuriouslyCory CuriouslyCory commented May 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Remove EdgeDirection enum (NONE/FORWARD/BIDIRECTIONAL) from schema — direction is no longer a stored field
  • Connection direction is now structural: derived from the sourceId→targetId ordering, never user-set
  • Arrow always points at the target (input Port), determined implicitly by the handle-pairing order
  • Bidirectional relationships become two separate connections (A→B and B→A), each independently labelable
  • Add ADR-0009 documenting the decision and rationale (amends ADR-0005)
  • Update CONTEXT.md glossary: introduce "Port" terminology (input/output); explain structural direction; retire "Edge direction" section
  • Remove direction field from Edge model, connectNodesInput / updateEdgeInput, service-layer parity guard, and client Zod schema
  • Introduce canConnect helper in ~/lib/connection-rules.ts for pure topology validation (no self-links, no duplicates)
  • Update canvas to use strict connectionMode (enforce source→target pairing) and call canConnect in React Flow's isValidConnection callback
  • Mark /generated/ as git-ignored (regenerated on install, not committed)
  • Update CLAUDE.md to reflect that generated Prisma client is no longer committed

Testing

  • pnpm check — eslint, type-check, and parity guards pass
  • pnpm dev — start app and verify:
    • Draw a connection from Component A's right handle (output Port) to Component B's left handle (input Port); arrow points at B
    • Try to draw A→A (self-link) — rejected by canConnect
    • Create A→B, then try to create A→B again — rejected (duplicate); reverse B→A is allowed (separate connection)
    • Create A→B and B→A in sequence; both render with correct arrow directions
    • Label both directions independently; labels persist
    • Delete one direction; the other remains
    • No regressions in component drag, rename, delete, or Canvas navigation

Summary by CodeRabbit

  • New Features

    • Stronger connection validation: rejects self-links and duplicate ordered connections; A→B and B→A treated as distinct.
  • Refactor

    • Connections render with a structural arrow (source→target); direction is no longer user-editable.
    • Connection editor simplified to label-only editing; port handles now include accessibility labels.
  • Documentation

    • Clarified port/connection terminology and that the client generation output is git-ignored and regenerated on install.

Review Change Stack

- Arrow is now derived from source→target ordering, never stored
- Two-way relationships are two separate Connections, not a BIDIRECTIONAL flag
- Removed EdgeDirection enum, direction field, and related UI controls
- Introduced "Port" terminology for input/output connection points
- Added ADR-0009 documenting the decision and its consequences
- Updated CONTEXT.md with structural direction rules and new Port term
- Generated Prisma files removed from git (regenerated on install)
@vercel

vercel Bot commented May 27, 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 May 28, 2026 12:46am

Request Review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c646888d-310e-44cc-a6dd-2a3308136858

📥 Commits

Reviewing files that changed from the base of the PR and between c6e3f40 and c3ced6e.

📒 Files selected for processing (3)
  • README.md
  • src/app/p/[slug]/_canvas/connection-edge.tsx
  • src/server/architecture/__tests__/edge.service.test.ts

📝 Walkthrough

Walkthrough

Removes stored EdgeDirection end-to-end: direction is now structural (sourceId → targetId). Docs, Prisma schema, Zod inputs, service, React Flow canvas, edge UI, validation helper, and tests updated to persist only label and enforce topology via canConnect.

Changes

Edge Direction Structural Refactor

Layer / File(s) Summary
Design, docs and schema contracts
CONTEXT.md, docs/adr/0009-connection-direction-is-structural.md, docs/adr/0005-edge-scope-and-service-enforced-invariants.md, prisma/schema.prisma, src/lib/schemas.ts, README.md, CLAUDE.md, .gitignore
Adds Port terminology and documents direction as structural; removes EdgeDirection and direction from schema and Zod inputs; updates README/CLAUDE and gitignore for generated Prisma client.
Connection validation helper and tests
src/lib/connection-rules.ts, src/lib/connection-rules.test.ts
New pure canConnect(proposed, existing) helper rejects self-links and duplicate ordered source→target connections; tests exercise observable outcomes.
Canvas configuration and rendering
src/app/p/[slug]/_canvas/canvas.tsx
React Flow switched to ConnectionMode.Strict; isValidConnection delegates to canConnect; edges rendered with single STRUCTURAL_MARKER_END; optimistic edges include markerEnd.
Connection edge component and label editing
src/app/p/[slug]/_canvas/connection-edge.tsx
ConnectionEdgeData reduced to { label, optimistic }; EditEdgeContext simplified to (id, label); direction glyph/edit removed; BaseEdge receives only markerEnd; UI supports double-click label edit or add-label button.
Service logic and tests
src/server/architecture/edge.service.ts, src/server/architecture/__tests__/edge.service.test.ts, src/server/api/routers/architecture.ts
connectNodes and updateEdge no longer accept or persist direction; conditional direction writes and enum parity guards removed; tests updated to focus on label behavior and duplicate rules.
Node handle accessibility and test setup
src/app/p/[slug]/_canvas/component-node.tsx, src/test/global-setup.ts
Input/output handles annotated with aria-label and title; global test setup runs prisma db push --accept-data-loss for non-interactive schema sync in disposable test environments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through code with gentle cheer,

Trimmed a direction that once lived here.
From source to target the arrow will gleam,
No stored moods — just a single stream.
Labels remain; the topology sings — hop, repeat!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% 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 accurately and concisely summarizes the main structural change: moving connection direction from a stored enum field to a derived property, enabling the removal of EdgeDirection entirely.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feature/feat/structural-ports

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 2

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

103-110: ⚡ Quick win

Add a regression test for the label: undefined no-op contract.

This doc now defines label: undefined as “leave unchanged”; please add an explicit test to lock that behavior.

🤖 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 103 - 110, Add a
regression test that verifies the Edge service method which edits a Connection's
label (the method that loads an Edge by id, resolves its Project and calls
access.assertCanWrite) treats label: undefined as a no-op and leaves the stored
label unchanged while label: null clears it; implement the test by (1) creating
an Edge/Connection with a known label, (2) invoking the service's update/edit
method with the same Edge id and payload { label: undefined } and asserting the
label remains the same, and (3) invoking it with { label: null } and asserting
the label is cleared, mock or stub access.assertCanWrite as needed and use the
same service method used in production to ensure the behavior is locked in.
🤖 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 `@README.md`:
- Around line 192-193: The README currently states that `generated/prisma` is
"committed to git" which conflicts with the PR's changes to `.gitignore` and
`CLAUDE.md`; update the README text mentioning `generated/prisma` (and any
related sentence on Line 81) to reflect the new policy (e.g., "regenerated and
git-ignored" or remove the claim that it's committed) so the docs match the
`.gitignore` and `CLAUDE.md` change.

In `@src/app/p/`[slug]/_canvas/connection-edge.tsx:
- Line 59: The label editor and updateEdge logic in connection-edge.tsx
currently rely only on optimistic state (const d: ConnectionEdgeData = data ?? {
label: null }) which allows read-only viewers to open edits; wrap the edit
affordances and mutation calls with the existing CanEditContext
(useContext(CanEditContext) or the project's CanEdit hook) so that label editor
UI (around the label open handlers at lines ~75-77) is only rendered/triggered
when canEdit is true, and also guard the updateEdge mutation and rollback/toast
paths (around lines ~134-158) by checking canEdit before dispatching mutations
or showing rollbacks. Ensure you reference and use the same CanEditContext
symbol used elsewhere in the app to prevent viewers from seeing or executing
edit actions.

---

Nitpick comments:
In `@src/server/architecture/edge.service.ts`:
- Around line 103-110: Add a regression test that verifies the Edge service
method which edits a Connection's label (the method that loads an Edge by id,
resolves its Project and calls access.assertCanWrite) treats label: undefined as
a no-op and leaves the stored label unchanged while label: null clears it;
implement the test by (1) creating an Edge/Connection with a known label, (2)
invoking the service's update/edit method with the same Edge id and payload {
label: undefined } and asserting the label remains the same, and (3) invoking it
with { label: null } and asserting the label is cleared, mock or stub
access.assertCanWrite as needed and use the same service method used in
production to ensure the behavior is locked in.
🪄 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: 745170d9-60c2-4d47-8a97-8c7d98d39f70

📥 Commits

Reviewing files that changed from the base of the PR and between b027f57 and c6e3f40.

⛔ Files ignored due to path filters (16)
  • generated/prisma/browser.ts is excluded by !**/generated/**
  • generated/prisma/client.ts is excluded by !**/generated/**
  • generated/prisma/commonInputTypes.ts is excluded by !**/generated/**
  • generated/prisma/enums.ts is excluded by !**/generated/**
  • generated/prisma/internal/class.ts is excluded by !**/generated/**
  • generated/prisma/internal/prismaNamespace.ts is excluded by !**/generated/**
  • generated/prisma/internal/prismaNamespaceBrowser.ts is excluded by !**/generated/**
  • generated/prisma/models.ts is excluded by !**/generated/**
  • generated/prisma/models/Account.ts is excluded by !**/generated/**
  • generated/prisma/models/Edge.ts is excluded by !**/generated/**
  • generated/prisma/models/Node.ts is excluded by !**/generated/**
  • generated/prisma/models/Post.ts is excluded by !**/generated/**
  • generated/prisma/models/Project.ts is excluded by !**/generated/**
  • generated/prisma/models/Session.ts is excluded by !**/generated/**
  • generated/prisma/models/User.ts is excluded by !**/generated/**
  • generated/prisma/models/VerificationToken.ts is excluded by !**/generated/**
📒 Files selected for processing (17)
  • .gitignore
  • CLAUDE.md
  • CONTEXT.md
  • README.md
  • docs/adr/0005-edge-scope-and-service-enforced-invariants.md
  • docs/adr/0009-connection-direction-is-structural.md
  • prisma/schema.prisma
  • src/app/p/[slug]/_canvas/canvas.tsx
  • src/app/p/[slug]/_canvas/component-node.tsx
  • src/app/p/[slug]/_canvas/connection-edge.tsx
  • src/lib/connection-rules.test.ts
  • src/lib/connection-rules.ts
  • src/lib/schemas.ts
  • src/server/api/routers/architecture.ts
  • src/server/architecture/__tests__/edge.service.test.ts
  • src/server/architecture/edge.service.ts
  • src/test/global-setup.ts

Comment thread README.md
Comment thread src/app/p/[slug]/_canvas/connection-edge.tsx
- Gate connection label editing to non-owners, mirroring component rename/delete permissions
- Clarify README: Prisma client is git-ignored and regenerated on install
- Add test locking in undefined (no-op) vs null (clear) contract for edge labels
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.

1 participant