Skip to content

Claude/graph sharing feature x9qdt#59

Merged
devgunnu merged 6 commits intomainfrom
claude/graph-sharing-feature-x9qdt
Feb 23, 2026
Merged

Claude/graph sharing feature x9qdt#59
devgunnu merged 6 commits intomainfrom
claude/graph-sharing-feature-x9qdt

Conversation

@RETR0-OS
Copy link
Member

This pull request implements a new public sharing feature for projects, enabling users to share project links and view architectures in read-only mode. It introduces backend endpoints and database fields to support sharing, and updates the frontend to allow rendering shared projects in a read-only canvas. The changes are grouped below by backend and frontend themes.

Backend: Project Sharing Feature

  • Added share_token (UUID) and is_shared (Boolean) fields to the Project model and database schema to support public sharing and link generation. [1] [2]
  • Updated ProjectSerializer to include share_token and is_shared fields, and made them read-only.
  • Added sharing endpoints: fetch shared project metadata, fetch shared architecture, enable sharing, and disable sharing in sharing_views.py and registered them in urls.py. [1] [2] [3]

Frontend: Read-only Shared Project Canvas

  • Introduced a new SharedProjectCanvas component and route to render shared projects in read-only mode. [1] [2]
  • Updated Canvas and FlowCanvas components to support a readOnly prop, disabling editing features, keyboard shortcuts, and context menus when viewing shared projects. [1] [2] [3] [4] [5] [6] [7]
  • Modified ConfigPanel to support read-only mode, disabling editing controls and showing configuration values as text when viewing shared projects. [1] [2] [3] [4] [5] [6] [7] [8]

Allows project owners to generate a stable shareable URL for their
architecture graphs. Key behaviours:

- Owner flow: "Share" button in header opens a dialog with a toggle to
  enable/disable sharing. Enabling generates a UUID token once (reused
  on re-enable so the URL never changes). Disabling deactivates the
  link without clearing the token.

- Viewer (anonymous): /shared/<token> loads a fully interactive
  read-only ReactFlow canvas — pan, zoom, click nodes to inspect
  config — with no editing capabilities. Shows project name, framework
  badge, owner attribution, and a "Read-only" badge.

- Viewer gated actions: "Make a Copy" and "Export Code" are always
  visible but show a login modal for unauthenticated users. Once
  signed in, "Make a Copy" creates a new project in the viewer's
  workspace and redirects them to it. "Export Code" runs the same
  export flow as the header, generating the original framework code.

Security: read-only enforcement is prop-based (readOnly={true} passed
into Canvas from SharedProjectCanvas), not a flippable global store
flag. All backend write endpoints already enforce ownership via
firebase_uid, providing a second layer of defence.

Backend:
- Project model: share_token (UUID, unique, nullable) + is_shared bool
- Migration 0005_project_sharing
- 4 new view functions in sharing_views.py (2 public GET, 2 auth POST/DELETE)
- New URL routes: /shared/<token>/, /shared/<token>/architecture/,
  /projects/<id>/share/, /projects/<id>/unshare/

Frontend:
- projectApi.ts: SharedProjectResponse type + 4 API helpers
- types.ts: optional share_token/is_shared on Project
- Canvas.tsx: readOnly prop disables drag, connect, delete, undo/redo,
  context menu, HistoryToolbar
- ShareDialog.tsx: new component (toggle + copy-link)
- Header.tsx: Share button + ShareDialog rendered for authed owners
- SharedProjectCanvas.tsx: new standalone page component
- App.tsx: /shared/:shareToken route

https://claude.ai/code/session_01FbQd5qajyLMQPjBae4R25k
…-only view

- Canvas: pass filtered onNodesChange in read-only mode so ReactFlow still
  propagates selection changes (node.selected=true). Without this the group
  block expand button (gated by `selected`) never appeared, and ConfigPanel
  had no selected node to display.
- ConfigPanel: add readOnly prop — disables all inputs (text, number, boolean,
  select, file), hides Delete Block button, Repeat Block section, and quick
  shape presets.
- InternalNodeConfigPanel: add readOnly prop — disables inputs, hides Reset /
  Reset All buttons, updates footer copy.
- SharedProjectCanvas: import and mount <ConfigPanel readOnly> alongside the
  canvas; add selectedNodeId to destructured store so the panel appears only
  when a node is selected.

https://claude.ai/code/session_01FbQd5qajyLMQPjBae4R25k
Copilot AI review requested due to automatic review settings February 23, 2026 04:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a public project sharing workflow (backend share tokens + endpoints) and adds a frontend shared-project route that renders projects in a read-only canvas with optional copy/export for signed-in users.

Changes:

  • Added share_token/is_shared fields to backend Project, exposed via serializers, and introduced sharing endpoints (/shared/<token>/..., share/unshare).
  • Added frontend API methods and UI for enabling/disabling sharing and for rendering shared projects at /shared/:shareToken.
  • Introduced readOnly mode support across the canvas/config UI to limit editing in shared views.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
project/frontend/src/lib/types.ts Extends frontend Project type with sharing-related fields.
project/frontend/src/lib/projectApi.ts Adds API contracts and fetch helpers for shared-project metadata/architecture + share/unshare calls.
project/frontend/src/components/SharedProjectCanvas.tsx New shared-project page: loads shared project + renders a read-only canvas with copy/export flows.
project/frontend/src/components/ShareDialog.tsx New dialog for enabling/disabling public link and copying the URL.
project/frontend/src/components/InternalNodeConfigPanel.tsx Adds readOnly support to internal node config editing UI.
project/frontend/src/components/Header.tsx Adds “Share” button and mounts ShareDialog for the active project.
project/frontend/src/components/ConfigPanel.tsx Adds readOnly support and blocks destructive/editing actions in shared view.
project/frontend/src/components/Canvas.tsx Adds readOnly mode wiring to disable certain canvas interactions in shared view.
project/frontend/src/App.tsx Adds /shared/:shareToken route.
project/block_manager/views/sharing_views.py New DRF function views for public shared access + owner share/unshare actions.
project/block_manager/urls.py Registers new sharing endpoints.
project/block_manager/serializers.py Exposes share_token/is_shared in ProjectSerializer as read-only.
project/block_manager/models.py Adds share_token + is_shared fields to the Project model.
project/block_manager/migrations/0005_project_sharing.py Migration adding the new sharing fields.

Comment on lines 211 to +215
'id', 'name', 'description', 'framework',
'share_token', 'is_shared',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
read_only_fields = ['id', 'share_token', 'is_shared', 'created_at', 'updated_at']
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

ProjectSerializer now exposes share_token/is_shared, but ProjectDetailSerializer (used for retrieve) does not. This makes list/create vs. retrieve responses inconsistent and prevents the frontend from reliably showing sharing status. Consider adding share_token/is_shared to the detail serializer as well.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 26 to 27
if project.user:
owner_display_name = project.user.display_name or project.user.email
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

owner_display_name falls back to project.user.email when display_name is missing, which can publicly expose user emails via a share link. Consider returning only display_name (or null/"Anonymous") unless the user explicitly opts in to showing email.

Suggested change
if project.user:
owner_display_name = project.user.display_name or project.user.email
if project.user and getattr(project.user, 'display_name', None):
owner_display_name = project.user.display_name

Copilot uses AI. Check for mistakes.
null=True,
blank=True,
unique=True,
db_index=True,
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

share_token is marked unique=True, which already creates an index in most databases; db_index=True is likely redundant and may create unnecessary DB overhead. Consider removing db_index=True here (and in the migration) unless there’s a DB-specific reason to keep it.

Suggested change
db_index=True,

Copilot uses AI. Check for mistakes.
Comment on lines 77 to 81
createdAt: number
updatedAt: number
share_token?: string | null
is_shared?: boolean
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The Project interface uses camelCase elsewhere (createdAt/updatedAt) but these new fields are snake_case. Consider using shareToken/isShared on the frontend model and mapping backend share_token/is_shared in convertToFrontendProject to keep TS naming consistent.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 713 to +717
<ReactFlow
nodes={nodesWithHandlers}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeClick={isInteractive ? onNodeClick : undefined}
onEdgeClick={isInteractive ? onEdgeClick : undefined}
onPaneClick={isInteractive ? onPaneClick : undefined}
onPaneContextMenu={isInteractive ? onPaneContextMenu : undefined}
onNodeContextMenu={isInteractive ? onNodeContextMenu : undefined}
onReconnect={onReconnect}
edgesReconnectable={true}
onNodesChange={readOnly ? onNodesChangeReadOnly : onNodesChange}
onEdgesChange={readOnly ? undefined : onEdgesChange}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

readOnly mode currently disables drag/connect/context menus, but node-internal actions can still mutate the graph (e.g., replicate-as-custom adds nodes; group expand/collapse rewires nodes/edges). To make shared view truly read-only, ensure these node-level mutating actions are disabled when readOnly is true (e.g., don’t attach onReplicate/onViewCode handlers and pass a readOnly flag so nodes can hide/disable expand buttons).

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +91
useEffect(() => {
if (!shareToken) return

const load = async () => {
setIsLoading(true)
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

This effect uses setNodes/setEdges/loadGroupDefinitions/reset but the dependency array only includes shareToken. This will trip react-hooks exhaustive-deps and can capture stale store actions; include the referenced values (or wrap in useCallback/useRef) in the deps list.

Copilot uses AI. Check for mistakes.
handleExport()
}
setPendingAction(null)
}, [user])
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

This effect depends on pendingAction/handleMakeCopy/handleExport, but only re-runs on user changes. To avoid stale closures (and satisfy exhaustive-deps), include the other referenced values or refactor to a stable callback/ref pattern.

Suggested change
}, [user])
}, [user, pendingAction, handleMakeCopy, handleExport])

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +34
}: ShareDialogProps) {
const [isShared, setIsShared] = useState(initialIsShared)
const [shareToken, setShareToken] = useState<string | null>(initialShareToken)
const [isLoading, setIsLoading] = useState(false)
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

ShareDialog initializes local state from props, but it doesn’t re-sync if projectId/initialIsShared/initialShareToken change (e.g., switching projects while the dialog stays mounted). Add a useEffect to update state on prop changes, or key the dialog by projectId so it remounts per project.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI commented Feb 23, 2026

@RETR0-OS I've opened a new pull request, #60, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 3 commits February 23, 2026 05:07
…ent API responses

Co-authored-by: RETR0-OS <74290459+RETR0-OS@users.noreply.github.com>
Fix inconsistent sharing fields between ProjectSerializer and ProjectDetailSerializer
@devgunnu devgunnu merged commit 2492fe0 into main Feb 23, 2026
4 checks passed
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.

5 participants