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
…ent API responses Co-authored-by: RETR0-OS <74290459+RETR0-OS@users.noreply.github.com>
Fix inconsistent sharing fields between ProjectSerializer and ProjectDetailSerializer
Claude/graph sharing feature x9qdt
There was a problem hiding this comment.
Pull request overview
This PR introduces a comprehensive project sharing feature enabling users to publicly share their neural network architectures via unique, secure links. The implementation includes backend support for managing sharing state and frontend read-only views that preserve full interactivity (pan, zoom, inspect) while preventing modifications.
Changes:
- Added database fields (
share_token,is_shared) to the Project model with proper migration and unique indexing - Implemented backend endpoints for enabling/disabling sharing (owner-only) and fetching shared project data (public, no auth)
- Created a dedicated
SharedProjectCanvascomponent mirroring the main canvas with read-only mode, login-gated copy/export, and graceful error handling - Extended
Canvas,ConfigPanel, andInternalNodeConfigPanelwithreadOnlyprop to disable all editing interactions while preserving selection and viewing - Added
ShareDialogcomponent in the main Header for authenticated users to toggle sharing and copy shareable links
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
project/block_manager/models.py |
Added share_token (UUID, unique, indexed) and is_shared (Boolean) fields to Project model |
project/block_manager/migrations/0005_project_sharing.py |
Database migration adding sharing fields with proper constraints |
project/block_manager/serializers.py |
Exposed share_token and is_shared as read-only fields in Project and ProjectDetail serializers |
project/block_manager/views/sharing_views.py |
Implemented 4 endpoints: public shared project/architecture fetch, owner-only enable/disable sharing |
project/block_manager/urls.py |
Added URL routes for sharing endpoints with UUID parameters |
project/frontend/src/lib/types.ts |
Extended Project interface with optional share_token and is_shared fields |
project/frontend/src/lib/projectApi.ts |
Added API client functions for fetching shared data and managing sharing state |
project/frontend/src/components/SharedProjectCanvas.tsx |
New component rendering read-only shared project view with login-gated copy/export actions |
project/frontend/src/components/ShareDialog.tsx |
New dialog component for toggling sharing and copying shareable URLs |
project/frontend/src/components/Canvas.tsx |
Added readOnly prop, custom node change handler filtering mutations, disabled editing interactions |
project/frontend/src/components/ConfigPanel.tsx |
Added readOnly prop, conditionally rendering controls and passing to InternalNodeConfigPanel |
project/frontend/src/components/InternalNodeConfigPanel.tsx |
Added readOnly prop, disabled all form inputs and mutation buttons when active |
project/frontend/src/components/Header.tsx |
Added Share button (authenticated users only) and integrated ShareDialog |
project/frontend/src/App.tsx |
Added /shared/:shareToken route rendering SharedProjectCanvas component |
| @api_view(['GET']) | ||
| def get_shared_project(request, share_token): | ||
| """ | ||
| Public endpoint — returns project metadata for a shared project. | ||
| No authentication required. | ||
| Returns 404 if the token doesn't exist or sharing is disabled. | ||
| """ | ||
| try: | ||
| project = Project.objects.get(share_token=share_token, is_shared=True) | ||
| except Project.DoesNotExist: | ||
| return Response( | ||
| {'error': 'Shared project not found or link is no longer active'}, | ||
| status=status.HTTP_404_NOT_FOUND | ||
| ) | ||
|
|
||
| owner_display_name = None | ||
| if project.user: | ||
| owner_display_name = project.user.display_name or "Anonymous" | ||
|
|
||
| return Response({ | ||
| 'name': project.name, | ||
| 'description': project.description, | ||
| 'framework': project.framework, | ||
| 'owner_display_name': owner_display_name, | ||
| 'share_token': str(project.share_token), | ||
| }) |
There was a problem hiding this comment.
The shared project endpoints should be exempt from Firebase authentication in the middleware. Currently, the middleware will attempt to verify tokens for these public endpoints. Add /api/shared/ to the exempt_paths list in FirebaseAuthenticationMiddleware.process_request() to avoid unnecessary token verification overhead and potential edge-case failures for unauthenticated users accessing shared projects.
| handleExport() | ||
| } | ||
| setPendingAction(null) | ||
| }, [user]) |
There was a problem hiding this comment.
The useEffect at line 119 has incomplete dependencies. It calls handleMakeCopy() and handleExport() which depend on projectMeta, nodes, edges, and groupDefinitions, but these are not in the dependency array. This can cause stale closures where the effect uses outdated values when the user logs in after the data has changed. Add projectMeta, nodes, edges, groupDefinitions, navigate, handleMakeCopy, and handleExport to the dependency array, or use refs for the data values, or restructure to avoid closures by inlining the logic.
| }, [user]) | |
| }, [ | |
| user, | |
| pendingAction, | |
| projectMeta, | |
| nodes, | |
| edges, | |
| groupDefinitions, | |
| navigate, | |
| handleMakeCopy, | |
| handleExport, | |
| ]) |
| } catch { | ||
| setNotFound(true) |
There was a problem hiding this comment.
The error handling at line 103 is too generic. All errors are treated as "not found" which could be misleading. Network errors, server errors (500), or authentication failures would all show the same "Link not found" message. Consider catching errors more granularly and showing appropriate messages: network errors should suggest checking connectivity, 404s can show "link not found", and 500s should indicate a server issue. This improves user experience and aids debugging.
| } catch { | |
| setNotFound(true) | |
| } catch (error: unknown) { | |
| // Log the error for debugging and distinguish common failure modes. | |
| console.error('Failed to load shared project', error) | |
| const anyError = error as { status?: number; response?: { status?: number }; name?: string } | |
| const status = anyError?.status ?? anyError?.response?.status | |
| if (status === 404) { | |
| // Preserve existing "not found" behavior for real 404s. | |
| setNotFound(true) | |
| toast.error('Shared link not found.') | |
| } else if (status === 401 || status === 403) { | |
| toast.error('You are not authorized to view this shared project.') | |
| } else if (typeof status === 'number' && status >= 500 && status < 600) { | |
| toast.error('Server error while loading shared project. Please try again later.') | |
| } else if (anyError?.name === 'TypeError' && status == null) { | |
| // Fetch often throws TypeError on network failures with no status code. | |
| toast.error('Network error while loading shared project. Check your connection and try again.') | |
| } else { | |
| toast.error('Unexpected error while loading shared project.') | |
| } |
| try: | ||
| architecture = project.architecture | ||
| except Exception: | ||
| return Response({'nodes': [], 'edges': [], 'groupDefinitions': []}) |
There was a problem hiding this comment.
The exception handling at line 53-56 is too broad and silently swallows errors. This catches all exceptions (including programming errors, database failures, etc.) and returns an empty architecture. This could hide real issues in production. Instead, catch specific exceptions like Project.architecture.RelatedObjectDoesNotExist or AttributeError and log unexpected exceptions before returning a fallback response. This will help with debugging and monitoring.
| if project.user: | ||
| owner_display_name = project.user.display_name or "Anonymous" |
There was a problem hiding this comment.
Potential information disclosure in shared project endpoint. The owner_display_name field at line 27 falls back to "Anonymous" when display_name is not set, but this reveals that a user exists even if they haven't set a display name. Consider using a consistent anonymization approach or omitting the field entirely when display_name is None/empty to avoid revealing user existence patterns.
| if project.user: | |
| owner_display_name = project.user.display_name or "Anonymous" | |
| if project.user and project.user.display_name: | |
| owner_display_name = project.user.display_name |
|
@claude |
|
Claude finished @RETR0-OS's task in 5m 3s —— View job Reviewing and Implementing ChangesAnalysis of Copilot Review Comments:
Tasks:
All 4 relevant fixes have been implemented and pushed to the Changes made:
|
This pull request introduces a new project sharing feature, allowing users to publicly share projects via a unique link. The backend adds fields and endpoints to support sharing, while the frontend implements read-only views for shared projects and ensures the UI disables editing in these contexts.
Project Sharing Feature
share_token(UUID) andis_shared(Boolean) fields to theProjectmodel, including migration and serializer updates to expose these fields as read-only. [1] [2] [3] [4]Frontend Support for Shared Projects
/shared/:shareToken, rendering a read-only project canvas for shared projects. [1] [2]Canvasand related components to support areadOnlymode, disabling editing, keyboard shortcuts, and context menus when viewing a shared project. [1] [2] [3] [4] [5] [6] [7]ConfigPanelto respectreadOnlymode, disabling editing controls and modal dialogs for custom blocks in shared project views. [1] [2] [3] [4] [5] [6] [7]