Conversation
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
There was a problem hiding this comment.
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_sharedfields to backendProject, 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
readOnlymode 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. |
| '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'] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| if project.user: | ||
| owner_display_name = project.user.display_name or project.user.email |
There was a problem hiding this comment.
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.
| 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 |
| null=True, | ||
| blank=True, | ||
| unique=True, | ||
| db_index=True, |
There was a problem hiding this comment.
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.
| db_index=True, |
| createdAt: number | ||
| updatedAt: number | ||
| share_token?: string | null | ||
| is_shared?: boolean | ||
| } |
There was a problem hiding this comment.
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.
| <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} |
There was a problem hiding this comment.
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).
| useEffect(() => { | ||
| if (!shareToken) return | ||
|
|
||
| const load = async () => { | ||
| setIsLoading(true) |
There was a problem hiding this comment.
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.
| handleExport() | ||
| } | ||
| setPendingAction(null) | ||
| }, [user]) |
There was a problem hiding this comment.
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.
| }, [user]) | |
| }, [user, pendingAction, handleMakeCopy, handleExport]) |
| }: ShareDialogProps) { | ||
| const [isShared, setIsShared] = useState(initialIsShared) | ||
| const [shareToken, setShareToken] = useState<string | null>(initialShareToken) | ||
| const [isLoading, setIsLoading] = useState(false) |
There was a problem hiding this comment.
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.
…ent API responses Co-authored-by: RETR0-OS <74290459+RETR0-OS@users.noreply.github.com>
Fix inconsistent sharing fields between ProjectSerializer and ProjectDetailSerializer
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
share_token(UUID) andis_shared(Boolean) fields to theProjectmodel and database schema to support public sharing and link generation. [1] [2]ProjectSerializerto includeshare_tokenandis_sharedfields, and made them read-only.sharing_views.pyand registered them inurls.py. [1] [2] [3]Frontend: Read-only Shared Project Canvas
SharedProjectCanvascomponent and route to render shared projects in read-only mode. [1] [2]CanvasandFlowCanvascomponents to support areadOnlyprop, disabling editing features, keyboard shortcuts, and context menus when viewing shared projects. [1] [2] [3] [4] [5] [6] [7]ConfigPanelto 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]