Collaboration
Collaboration
Complete guide to collaboration features in Cine Power Planner, enabling teams to work together on shared projects with real-time sync, comments, and conflict resolution.
Table of Contents
- Overview
- Requirements
- Getting Started with Collaboration
- Sharing Projects
- Permission Levels
- What Gets Shared
- Real-Time Collaboration
- Field-Level Locks
- Comments & Communication
- Conflict Resolution
- Project Locking
- Managing Collaborators
- Notifications
- Technical Services
- Best Practices
- Troubleshooting
- Related Documentation
Overview
Production teams can work together on shared gear lists in real time. Built on top of cloud synchronization, collaboration provides:
- Real-time sync: Changes appear instantly for all collaborators
- Access control: Granular permissions for different team members
- Communication: Built-in commenting and discussion threads
- Conflict prevention: Smart locking and merge tools
- Transparency: Activity tracking and presence indicators
Requirements
Functional Requirements
| ID | Requirement | Acceptance Criteria | Priority | Source |
|---|---|---|---|---|
| REQ-COL-001 | The system shall allow project owners to invite collaborators via email with role assignment (Owner, Editor, Viewer) | Send invite → email delivered; recipient sees pending invite; accept → added to project; decline → invite closed | Must | data/collab |
| REQ-COL-002 | The system shall enforce permission levels: Owners manage everything, Editors add/edit/delete items and comment, Viewers read-only | Viewer attempts edit or comment → blocked; Editor edits → succeeds; Owner changes roles → succeeds | Must | data/collab |
| REQ-COL-003 | The system shall show real-time presence indicators (avatars, online status, cursor positions) via Supabase Realtime channels | Open project on 2 devices → both show presence dots; close one → presence removed within 10s | Must | data/collab |
| REQ-COL-004 | The system shall provide automatic edit locking when a user is actively editing, preventing conflicting edits | User A edits item → User B sees lock indicator; User A saves → lock releases within 30s timeout | Must | acquire_project_lock |
| REQ-COL-005 | The system shall support flat, append-only comment threads with @mentions, per-project and per-item scope (no per-comment replies) | Post comment → visible to all collaborators; @mention → notification sent; further comments append chronologically | Must | data/collab |
| REQ-COL-006 | The system shall detect sync conflicts and present merge options: Keep Local, Keep Remote, Merge, Review (side-by-side diff) | Offline edit + remote edit → conflict dialog; each option resolves correctly; diff view shows both versions | Must | features/conflict-resolution |
| REQ-COL-007 | The system shall provide real-time project chat for quick coordination | Type message → appears for all members; messages ordered chronologically; persist across page reload | Should | data/collab |
| REQ-COL-008 | The system shall maintain a field-level edit history showing who changed what and when | Edit item → history entry created; view history → shows user, field, old/new values, timestamp | Should | data/collab |
| REQ-COL-009 | The system shall support the invitation lifecycle Pending → Accepted / Declined / Revoked, with owner-side revoke | Invite row is status check in ('pending','accepted','declined','revoked'); owner clicks "Revoke Invite" → recipient can no longer accept. Invitations do not expire and there is no resend | Must | accept_invite |
| REQ-COL-010 | The system shall support real-time cursor presence across the project workspace (all tabs), showing collaborator positions | Two users open same project → cursor positions visible; move cursor → other user sees movement; label shows user name | Should | useProjectCursors |
| REQ-COL-011 | The system shall support a per-project chat for team communication | Open project → chat panel available; send message → visible to collaborators; timestamps shown; offline → queued | Should | useProjectChat |
Deferred, not dropped. Two capabilities were once specified in this table and have never been built. REQ-COL-002 and REQ-COL-005 above now describe what ships; the unbuilt parts are recorded here so they stay product decisions rather than silent omissions.
- Viewer commenting (was in REQ-COL-002). A viewer resolves to
editMode === 'readOnlyRole', andaddCommentis absent fromREAD_SAFE_PROJECT_ACTIONS, so useGuardedProjectActions.js refuses the call — before the disabled comment box matters. Enabling it is not an entry in that allow-list, which is read-safe-only by contract: posting a comment writes to IndexedDB and the sync outbox. It needs a separate comment-permission axis.- Nested replies (was in REQ-COL-005). In comments.js,
buildCommenthas noparentIdandbuildThreadcreates one thread per target. Adding them is a comment-schema change:STORAGE_VERSIONbump, migration, the silent-loss touchpoint matrix, sync handling, EN+DE strings.Either one is a feature and needs the SDD pipeline before any code. Per-comment edit/delete and reactions were settled the same way in #588 — see CommentsTab.jsx.
Non-Functional Requirements
| ID | Requirement | Metric | Priority |
|---|---|---|---|
| NFR-COL-001 | Presence updates shall propagate to all collaborators within 2 seconds | Realtime latency | Should |
| NFR-COL-002 | Edit lock acquisition/release shall complete within 500ms | Lock latency | Must |
| NFR-COL-003 | Conflict resolution UI shall render diffs for projects with ≤500 items within 1 second | Diff render time | Should |
| NFR-COL-004 | Collaboration features shall degrade gracefully when Realtime channel disconnects | Graceful degradation | Must |
Data Requirements
- Invitations: Stored in Supabase, linked to project ID and invitee email
- Permissions: Stored per-project in the collaboration metadata table
- Presence: Ephemeral (Supabase Realtime channels) — not persisted
- Comments/chat: Stored in Supabase, synced to all collaborators
- Edit history: Stored per-project with field-level diffs
Constraints & Limits
- All collaborators must have cloud-synced accounts
- Invitations do not expire — a pending invite stays acceptable until the owner revokes it
- Edit lock timeout: 30 seconds (auto-release on save or inactivity)
- Collaboration is available on all tiers and is not limited by the number of collaborators. The free tier's limit is on the number of projects (2 total), and projects shared with you count toward that project limit — Pro lifts the project limit.
Offline Behavior
- Changes made offline are queued and synced on reconnect
- Offline edits may create conflicts requiring resolution on sync
- Presence indicators, chat, and push notifications require network connectivity
Dependencies
- Requires: Authentication (REQ-AUTH-001) — all collaborators need accounts
- Requires: Projects (REQ-PRJ-001) — collaboration operates on shared projects
- Requires: Sync Architecture — realtime channels for presence and sync
- Requires: Notifications — invite/comment notifications
- Required by: Conflict Resolution — triggered by collaboration sync conflicts
Who Can Collaborate?
Requirements:
- All collaborators must have accounts
- Cloud sync must be enabled
- Project owner must share the project
- Invitees must accept the invitation
Typical Users:
- Production managers sharing with crew
- Directors collaborating with DPs
- Rental coordinators sharing with clients
- Team members on multi-day shoots
Getting Started with Collaboration
Prerequisites
Before using collaboration features:
-
Create an Account (all collaborators)
- Sign up in Settings → Account
- Verify email
- Complete profile
-
Cloud sync
- There is nothing to switch on: sync runs automatically once you are signed in and online
- Confirm it is running under Settings → Security & Privacy → Sessions & Devices — the read-only Sync status row should read Synced
-
Create or Open a Project
- Have a project ready to share
- Or create a new project
Collaboration Workflow
Create Project → Share with Team → Collaborate → Sync Changes
Typical Flow:
- Owner creates project and adds initial gear
- Owner shares project with team members
- Team accepts invitations
- Everyone collaborates in real-time
- Changes sync automatically
Sharing Projects
Inviting Collaborators
Steps to Share:
-
Open Project:
- Navigate to the project workspace
- Ensure you're the project owner
-
Open the Collaborators section:
- It lives in the project workspace itself — there is no "Share" button in the toolbar and no Project → Share menu; this guide named both until 2026-09-07
- The invite form inside it is owner-only
-
Add a collaborator:
- Enter one email address. The form is a single email field plus a role picker — there is no contacts picker, no message field, and comma-separated lists are not split. Invite people one at a time.
- One invitation per email address per project (a unique index enforces it)
-
Set Permissions:
- Choose Editor or Viewer. Ownership cannot be granted this way —
project_invites.roleischeck (role in ('editor','viewer')), and the picker offers only those two. The guide offered "Owner" as a third choice until 2026-09-07.
- Choose Editor or Viewer. Ownership cannot be granted this way —
-
Send the invitation:
- The invite is queued and sent
- While you are offline it is held as a queued invite ("Will send when online") and goes out when the connection returns
- Pending invitations are listed in the same panel
Invitation States
Pending:
- Invitation sent
- Awaiting acceptance
- Can be cancelled by owner
Accepted:
- User joined project
- Can access based on permissions
- Appears in collaborators list
Declined:
- User declined invitation
- Can be re-invited
- No access granted
Revoked:
- The owner withdrew the invitation
- The recipient can no longer accept it
Invitations do not expire. This guide described an "Expired" state that times out "usually 7 days" until 2026-09-07.
project_inviteshas no expiry column, and its status ischeck (status in ('pending','accepted','declined','revoked'))— there is noexpiredvalue for a row to take. A pending invitation stays acceptable indefinitely, which matters: an invitation sent to someone who has since left is still live until you revoke it.
Managing Invitations
View invitations: open the project and scroll to the Collaborators section — pending invitations are listed there alongside the members, each showing "Invited as {role}".
Revoke an invitation:
- Find the pending invitation in Collaborators
- Click "Revoke Invite"
- Confirm — the recipient can no longer accept it
Remove a member: click "Remove Member" on their row and confirm.
There is no "Resend". This guide described one until 2026-09-07; no such control exists. Because invitations never expire, there is nothing to resend — a pending invite is still valid. If you revoked one by mistake, invite the same address again.
Permission Levels
Available Roles
Owner:
- Full control over project
- Can delete project
- Can manage all collaborators
- Can change permissions
- Cannot be removed by others
Editor:
- Can view project
- Can add/edit/delete items
- Can add comments
- Can see other collaborators
- Cannot delete project
- Cannot manage collaborators
Viewer:
- Can view project
- Cannot add comments — see the note below on how this is enforced
- Cannot edit items
- Cannot delete anything
- Cannot export the project (PDF / CSV / briefing)
- Read-only access
There is no Guest role, and no time-limited access. This guide listed one until 2026-09-07, with a "limited time period". The role column is constrained in the database to
check (role in ('editor','viewer'))and the invite form offers exactly those two, so an owner cannot grant temporary or expiring access to anyone. If you need someone out, revoke their invite or remove them — that is the only mechanism.
Permission Matrix
| Action | Owner | Editor | Viewer |
|---|---|---|---|
| View project | ✅ | ✅ | ✅ |
| Edit items | ✅ | ✅ | ❌ |
| Add items | ✅ | ✅ | ❌ |
| Delete items | ✅ | ✅ | ❌ |
| Add comments | ✅ | ✅ | ❌ |
| Edit/delete comments | ❌ | ❌ | ❌ |
| Manage collaborators | ✅ | ❌ | ❌ |
| Change permissions | ✅ | ❌ | ❌ |
| Delete project | ✅ | ❌ | ❌ |
| Export PDF / CSV | ✅ | ✅ | ❌ |
How read-only is enforced, and what a Viewer actually sees. The comment box is not visually disabled — this guide said it was until 2026-09-07. Instead every write action is refused at the point the workspace hands the project API down (
useGuardedProjectActions, a deliberate allow-list of three read-safe actions), and the refusal surfaces as a toast. So a Viewer can type a comment and press send; the comment simply never posts. Exports are the exception that is visibly disabled, with the reason shown: "Read-only access — exports require editor role".Note the toast currently reads "this project is open elsewhere. Choose 'Take over editing'", which is written for the other cause of read-only mode — the single-writer lock — and does not describe a Viewer's situation, since a Viewer cannot take over editing at all.
Changing Permissions
Update Collaborator Role:
- Open project
- Scroll to the Collaborators section
- Find collaborator
- Click current role
- Select new role
- Confirm change
Remove Collaborator:
- Find collaborator in list
- Click "Remove"
- Confirm removal
- User loses access immediately
What Gets Shared
Sharing a project shares two different kinds of data, and until v0.682.0 only the first of them actually travelled.
The project document. Location, Call Sheets, Schedule, Crew, Requirements and the
project's own settings live inside the project record and are reconciled field-group by
field-group by the merge engine (app/src/data/collab/fieldGroups/). These have always
reached collaborators.
Pre-production records. Cast, Script, Scenes, Shot List, Episodes, Floor Plans and
floor-plan markers are stored as their own rows, one per entity. Those rows were
partitioned per user and gated by auth.uid() = uid, so they never crossed between
collaborators — each participant accumulated a private copy of the same production.
Measured on a live three-member project on 2026-08-27: 28 cast rows across five user
ids, of which the owner held seven.
Since v0.682.0 the seven Pre-production families are shared:
| Family | Shared | Notes |
|---|---|---|
characters (Cast) | ✅ | One canonical row per cast member |
scripts | ✅ | |
scenes | ✅ | |
shots (Shot List) | ✅ | |
episodes | ✅ | |
floorPlans | ✅ | |
floorPlanNodes | ✅ | Markers; carry a denormalized project_id since storage v257 |
Nothing else is. Invoices, expenses, contacts, bank accounts and journal entries are
not shareable through this mechanism — the type list is an explicit allowlist held in
app/src/data/sync/sharedProjectScopedTypes.js and mirrored into four SQL predicates, and
sharedTypeAllowlistParity.test.js fails the build if the two ever disagree.
Reading and writing
- A member reads the owner's rows. There is one copy, not one per participant.
- A member with edit rights writes through
apply_shared_project_sync_batch, which resolves the project owner server-side and writes that single row. - A Viewer is refused server-side, not merely in the UI.
project_idis immutable for authorization: a write that re-points a row at another project is rejected (project_id_immutable).- The project owner's own cloud-sync consent is checked as well as the writer's; if the
owner has withdrawn it, collaborator writes into their partition are refused
(
owner_consent_missing).
Propagation
A write broadcasts a pointer — { type, entityId, projectId } — on the project's
existing collab channel. Receivers re-read through the RLS-governed REST path. No row
travels over the realtime channel, and no additional channel is opened. Because a
broadcast is best-effort, the same re-read also runs on project open and on window focus.
Losing access
Revocation purges the project's Pre-production rows from local state without enqueueing any deletes. The server copy is untouched — the user lost access, the data did not stop existing.
Existing fragmented data
Rows that were already scattered across participants before this shipped are not healed
by the read and write paths alone; they are consolidated into the owner's partition by a
one-off, separately-authorized server-side pass
(supabase/migrations/20260827000100_consolidate_fragmented_preproduction.sql), which
runs in dry-run mode by default and tombstones rather than deletes.
Real-Time Collaboration
Presence Indicators
Active Collaborators:
- Avatars shown in top bar
- Names on hover
- Online status indicator
Each client re-publishes its own presence entry about every 25 seconds
(PRESENCE_HEARTBEAT_MS in useProjectPresence.js), which keeps it inside the
60-second client-side freshness window (PRESENCE_TTL_MS). The window exists so
a crashed tab or a dropped socket stops showing as a ghost participant before
the server notices; the heartbeat is what stops it from also hiding people who
are genuinely still there. Until v0.703.58 nothing refreshed the entry, so a
collaborator who joined a project and stayed on one tab became invisible to
anyone arriving more than a minute later.
Current Activity:
- "Sarah is editing Camera section"
- "Mike is viewing"
- "2 people editing"
Cursor Tracking:
- See where others are working in real-time
- Cursor positions labeled with collaborator names
- Updates broadcast via Supabase Realtime channels
- Colored indicators distinguish between users
Cursors are bound to the interface, not to coordinates. On broadcast, the app walks up from the
element under the pointer to the nearest one carrying data-entity-id / data-cursor-anchor and
records an offset inside that element; the receiver finds the same element in its own DOM and
places the cursor there. A cursor over a shot row therefore lands on that shot row for everyone, no
matter how each person's window wrapped the layout or how far they have scrolled. Source:
cursorAnchor.js.
Where no anchor is found, the payload falls back to a fraction of the surface's scroll box — which is
only faithful while both peers measure the same content height, and that number more than doubles
during hydration. That fallback is why an unanchored surface shows cursors in visibly wrong places.
Every record-bearing surface in the Pre-production tab is anchored as of v0.764.0;
preproductionCursorAnchors.test.js
fails if one loses its anchor.
Project Locked Overlay:
When you open a project that is already open in another tab (or on another device), the app renders ProjectLockedOverlay before the workspace mounts. Source: ProjectLockedOverlay.jsx. It offers three choices:
- Force Open Here — take over editing in this tab immediately; the other tab loses the edit lock.
- View in Read-Only Mode — open the workspace with editing disabled everywhere (all tabs, including Camera Logs, gear list, and shot list). The choice applies to this project in this tab session only; opening a different locked project shows that project's own overlay.
- Back to Dashboard — leave the project untouched.
This is distinct from per-field locks (below) — the overlay is the coarse "one editing session per project" surface.
View-Only Banner:
While you are in read-only mode after choosing View in Read-Only Mode, an inline banner at the top of the workspace reminds you why editing is disabled and offers Take over editing, which claims the edit lock for this tab and restores full editing without reopening the project.
Read-only is enforced, not advisory (#517):
Passing a readOnly flag down to each tab only asks every control to render inert, and two of them
did not — the Budget panel (BudgetReportPanel was mounted without the flag: 55 of the tab's 58
inputs stayed live) and the gear-list category name. Underneath the per-control gating, the workspace
now hands its tabs a guarded project API
(useGuardedProjectActions.js):
while read-only, every mutating project action is refused, and the first refusal raises one toast
explaining the state and pointing at Take over editing. A control that misses the flag can
therefore no longer write, and an action added to the project API later is refused by default until
it is explicitly declared read-safe.
Still available while read-only, because none of them touch the project: switching tabs, search and filters, every export (PDF / CSV / ALE / JSON / Carnet ZIP), the project chat, and Take over editing itself.
Live Updates
Automatic Sync:
- Changes appear within seconds
- No manual refresh needed
- Optimistic UI updates
Update Types:
- Item added/removed
- Item edited
- Category reordered
- Comments added
- Metadata changed
Offline Handling
When Collaborator Goes Offline:
- Status shows "Offline"
- Changes queued locally
- Syncs when back online
- May create conflicts
Best Practice:
- Coordinate editing sessions
- Avoid simultaneous major changes
- Communicate via comments
Field-Level Locks
When a collaborator focuses a field, that field is claimed for them across the session. Other collaborators see a <LockedFieldBadge> next to the input, the input becomes aria-disabled, and editing resumes the moment the focus moves away. Field locks prevent two-at-once edits on the same value without blocking the rest of the page.
How it works
- Locks are per-field on most editors — moving one field lets others edit neighboring fields on the same form simultaneously.
- Locks are rule-level inside the Auto-Gear rule editor — the entire rule is claimed on mount and released on close, because the modal stages a local draft and only commits on Save.
- Locked fields display a small "Locked by <name>" badge and set
aria-disabledso keyboard and screen readers announce the state. - Committed values propagate to all sessions within a couple of seconds; badges clear as soon as the other user blurs the field.
Locked-surface reference
| Surface | Locked fields / behavior |
|---|---|
| Project Info & Settings | Project name, description, settings-tab fields. |
| Schedule | Period editor (dates, shoot-day window), calendar exclusion settings. |
| Camera Package editor | Monitor, wireless transmitter, FIZ motors (×4), FIZ controllers (×4). |
| Project Requirements — Delivery Specs | Global delivery-resolution, base frame rate, aspect ratio. |
| Project Requirements — per-package | Per-field, on every id in project.settings.cameraPackages.<pkg>.*: Camera Specs (6), Rigging (3), Camera Support (the 7-field cascade), Storage & Media (4 per row + the 2-field media plan), Mattebox & Filter (1 + 4 per filter row), Monitoring (all four display surfaces, 19 fields each), User Buttons (1 per button type). Tripod and Briefing extras remain covered only by the coarser per-package card lock. |
| Project Requirements — global | Transportation locks as ONE field over the whole vehicle list (every control there rewrites the list in one write); Carts locks per cart row. Lenses is not wired. |
Billing rental row (ItemCostRow) | rentalOverride, purchaseCost, insurance cluster (insuranceMode ↔ insuranceCost ↔ insurancePercent ↔ insuranceBase), insuredValue. |
| Insurance provider settings | Enabled toggle, rate, estimated value — per-field, on owned / rental / subrental. |
| Auto-Gear rule editor | Rule-level lock (Save button and Enabled toggle disable with a "locked by other" notice). |
| Call Sheets | General Info (title, mode, PDF language, dates, producer, director), Department Call Times editor, Location Detail editor. |
| Pre-production — Location sub-tab | Every editable scout field. Ten on the Overview (name, address, master location, status, INT/EXT, DAY/NIGHT, time zone, primary location, availableDates, sceneIds), all 41 manifest fields via hooks/useLocationFieldLocks.js, coordinates (the Locate-address action) and photos (upload + the grid's captions and deletes, one lock for the whole array). parentId/kind and sketchImagePath/sketchAnnotations are co-edit cells, so claiming either member locks both. |
Contracts tab is intentionally exempt. Each user edits their own contract copy, so cross-session locking would block legitimate parallel work. Don't expect badges there.
A per-row lock is not protection against a peer on a different row. Storage-media rows, mattebox filter rows and cart rows are each persisted as one array, so any save rewrites every row in the list. The row lock keeps two people off the same row; it cannot stop a cross-row overwrite. Transportation is declared at array level for exactly this reason — it is the honest granularity for an array-replacing write, and the two shapes disagree only because the per-row form predates it (v0.150.1).
Presence is not a lock, and the Pre-production tab shows both. Every scene card, the Script and Scene viewers, every moodboard item and every location control carries a
data-entity-id. That attribute anchors a remote collaborator's cursor to that exact element (see Cursor Tracking above) and publishes whoever focuses it, so the control tints and names them. It does not claim the value. A lock is the stronger thing: it disables the control for everyone else, and it engages only on a shared project with edit rights. Where a surface has both, the lock wins. Where it has only the anchor — the scene sidebar, Script, Scene viewer, the moodboard — a tint means "someone is here", not "this is claimed", and the last write still wins.A primary location is one per project, so its lock is partial by construction. Promoting a location clears the flag on every other one — a write that reaches records nobody claimed. The lock keeps two people off this location's toggle; the at-most-one rule is enforced in the mutator (
useLocationScout.setPrimary), which is the only place that can see the whole list. Two peers promoting different locations at the same moment settle on the later write, and the next promotion repairs any project that ended up with several.How the location locks are wired, and why it looks odd. All 41 manifest fields are rendered by one generic component whose field key is a runtime value, and
scripts/validate-field-groups.mjsresolves lock ids by AST — so auseFieldLockcall inside that renderer cannot be resolved, and the guard fails rather than degrading. The calls therefore live inhooks/useLocationFieldLocks.js, one literal key per line, and the resolved lock is threaded down as a prop. It reads like duplication and is not: it is what keeps every id statically checkable.useLocationFieldLocks.test.jsfails if that file drifts from the manifest, or if anyone collapses the calls into a loop.
If an editor isn't in the table above, it isn't wired to the field-lock stream yet — treat simultaneous edits on those surfaces with the classic "who's editing what" etiquette.
Comments & Communication
Comment System
Adding Comments:
- Open project workspace
- Click "Comments" button (sidebar)
- Click "Add Comment"
- Type message
- Add @mentions (if available)
- Click "Post"
Comment Features:
- One thread per target: each project and each item has a single thread; new comments append to the bottom in chronological order. There are no per-comment replies — quote or @mention the person you are answering.
- @mentions: Notify specific users. Pick a collaborator from the chips above
the box to insert
@Name; they get a mention notification, and everyone who already commented in that thread gets a reply notification. - Plain text: line breaks are preserved, but markdown is not rendered.
- Timestamps: When comment was posted, with the author's name.
- Comments are permanent: once posted, a comment cannot be edited or deleted — not by its author, not by the project owner. Read before you post. (Shot and scene comments in the Shot List are a separate system and can be resolved, which strikes them through but also never deletes them — see Shot List.)
Comment Context
General Comments:
- About entire project
- Overall coordination
- High-level discussion
Item-Specific Comments:
- Click item → Comments tab
- Discuss specific gear
- Notes about that item
Comments attach to a project or to an item — those are the only two scopes. Categories have no comment thread of their own; comment on the items inside the category, or on the project.
Notification Settings
Email Notifications:
- New comments
- @mentions
- Project updates
- Invitation received
In-App Notifications:
- Real-time alerts
- Notification bell icon
- Unread count badge
Configure Notifications:
- Go to Settings → General → Notifications
- Choose preferences:
- All activity: Everything
- Mentions only: Only @mentions
- None: No notifications
Communication Best Practices
Effective Commenting:
- Be specific and clear — comments cannot be edited or deleted afterwards
- Use @mentions for action items
- Quote the comment you are answering; there are no per-comment replies
- Keep discussions focused
- Post a closing comment when a discussion is settled
Project Chat
In addition to threaded comments, collaborative projects include a real-time chat panel for quick coordination.
Accessing Chat:
- Open a shared project workspace
- Click the Chat icon in the collaboration panel
- Type a message and press Enter to send
Chat Features:
- Real-time messaging — messages appear instantly for all collaborators
- Lightweight coordination (prefer threads for structured discussion)
- Visible only to project collaborators
- Messages persist across sessions
Edit History Panel
Track exactly what changed in a shared project with the Edit History panel:
- Open the collaboration panel
- Click "Edit History"
- View a field-level revision timeline showing:
- Which field was changed
- Old value → new value
- Who made the change
- When the change occurred
Conflict Resolution
When Conflicts Occur
Common Conflict Scenarios:
- Simultaneous edits: Two users edit same item
- Offline changes: Changes made while offline
- Category moves: Item moved by multiple users
- Deletions: Item deleted while being edited
Conflict Detection
Automatic Detection:
- System detects overlapping changes
- Flags conflicting fields
- Presents both versions
Visual Indicators:
- Warning icon on conflicted items
- Banner notification
- Detailed conflict view
Resolution Options
Merge Dialog:
When conflict detected, choose:
- Keep Local: Your changes win
- Keep Remote: Their changes win
- Merge: Combine changes manually
- Review: See side-by-side comparison
Manual Merge:
- See both versions side-by-side
- Pick fields from each
- Create combined version
- Save merged result
Preventing Conflicts
Best Practices:
- Check presence indicators
- Communicate via comments
- Don't edit same item simultaneously
- Save frequently
- Use project locking (if available)
Project Locking
Cine Power Planner uses two layers of locking to coordinate concurrent edits. There is no global "lock this project" button and no manual force-unlock UI; locking is automatic and always scoped as narrowly as the surface allows.
Layer 1 — One Editing Session Per Project (ProjectLockedOverlay)
ProjectLockedOverlay covers the workspace when you open a project that is already open in another of your own tabs, or on another of your own devices. It is a same-account, single-session guard: useProjectLock(projectId, uid) keeps the lock under your own user id (users/<uid>/projects/<id>/locks/main) and reports a lockReason of TAB or DEVICE. Nothing a collaborator does raises it.
Source: ProjectLockedOverlay.jsx
Behavior:
- Full-surface overlay rendered instead of the workspace — the container returns it before any tab mounts, so nothing behind it is reachable
- Titled "Project Open in Another Tab" or "Project Open on Another Device" depending on
lockReason. It never names a user, because the session it is protecting you from is your own - Offers three explicit choices: Force Open Here, View in Read-Only Mode, Back to Dashboard
- It also clears itself once the other tab or device releases its lock, without you choosing anything
Until v0.778.3 this section said the overlay appears "while another collaborator holds an exclusive project-level lock", that it displays "the owner of the lock", and that "there is no UI to force-release a project-level lock — it clears itself". All three are wrong. The last one is contradicted by this document's own Project Locked Overlay section above, which has listed the three buttons correctly the whole time — the two halves of one file disagreed about one component.
Layer 2 — Per-Field Edit Locks (LockedFieldBadge)
For the much more common case of two people trying to edit different parts of the same project at the same time, the app uses per-field locks: when a collaborator focuses a field, that field is claimed for them for the duration of the focus, while the rest of the form stays editable for everyone else.
Source: LockedFieldBadge.jsx
Behavior:
- Locks are per-field on most surfaces — neighbors on the same form stay unlocked
- Every id under a camera package carries that package, so holding a field on A Cam never disables the same field on B Cam
- Focused fields show a
<LockedFieldBadge>reading "Locked by <name>" to other sessions - Locked inputs set
aria-disabledso keyboard and screen-reader users hear the state - Release happens automatically the moment focus moves elsewhere — no save step required
- Committed values propagate to all sessions within a couple of seconds
The Auto-Gear rule editor is an exception and uses a rule-level lock (acquired on mount, released on close) because the modal stages a local draft and only commits on Save.
See Field-Level Locks above for the full table of locked surfaces.
What Was Removed
- Manual / force-unlock UI — not present in the current build. Locks always self-release.
Managing Collaborators
Collaborators Panel
Access:
- Project workspace → Collaborators button
- Or Share → Manage Collaborators
Panel Contents:
- List of all collaborators
- Current role
- Online status
- Last activity
- Actions (change role, remove)
Collaborator Information
Per User:
- Display name and avatar
- Email (if visible)
- Role/permission level
- Join date
- Activity status
Activity Tracking:
- Last seen
- Recent changes
- Comments posted
- Items edited
Notifications
Types of Notifications
Project Notifications:
- New collaborator joined
- Permission changed
- Project deleted
- Large changes made
Item Notifications:
- Item you're watching changed
- Comment on your item
- @mention in comment
System Notifications:
- Invitation received
- Invitation accepted/declined
- Sync issues
- Conflict detected
Notification Channels
In-App:
- Bell icon in top bar
- Badge count
- Dropdown list
- Mark as read
Email:
- Sent to registered email
- Configurable frequency
- Include summary or details
- Unsubscribe options
Push (PWA):
- Browser notifications
- Enable in settings
- Works when app closed
- Click to open project
Technical Services
The collaboration system is powered by several services in app/src/data/collab/:
| Service | File | Purpose |
|---|---|---|
| Shared Projects | collabProjectService.js | Create/manage shared projects, collaborator roles, permissions |
| Sync | sharedProjectSync.js | Real-time sync, remote change handling, state management |
| Invitations | inviteService.js | Send, accept, reject, cancel invitations |
| Locking | lockService.js | Item/project locking, timeouts, visual indicators |
| Merge Engine | mergeEngine.js | Three-way merge, field-level conflict detection, strategy selection |
| Diff Presenter | diffPresenter.js | Side-by-side change visualization |
| Chat | chatService.js | Real-time project chat |
| Cursors | cursorService.js | Live cursor positions for collaborative editing |
| Activity Log | activityLogService.js | Audit trail of all collaborative actions |
| Edit History | editHistoryService.js | Field-level change tracking with revision timeline |
| Presence | presenceService.js | Idle detection helpers + the participant roster the sync fast lane reads |
| Entity Locks | entityLockService.js | Coarse entity-level locks (project, rule, document) |
| Email Preferences | emailPreferences.js | Per-user notification email settings |
Push notifications live in
app/src/data/notifications/pushNotificationService.js, not undercollab/.
Related Hooks
Hooks in app/src/app/hooks/collab/:
| Hook | Purpose |
|---|---|
useCollaborationModel.js | Orchestrates shared project state and collaborator tracking |
useCollabEditSession.js | Tracks active editing sessions, real-time presence, edit coordination |
useCollabUndo.js | Collaborative undo/redo with conflict-aware state rollback |
useProjectPresence.js | User presence within projects (viewing, activity status) |
useProjectMemberProfiles.js | Loads profile data for the collaborators of a project |
useDisplayNameResolver.js | Resolves collaborator display names |
useFieldLock.js / useFieldBadge.js | Per-field lock acquisition and LockedFieldBadge rendering |
useEntityLock.js / useProjectEntityLocks.js | Coarse entity-level lock claims (project, rule, etc.) |
useActivityLog.js | Exposes activity log data to UI components |
The per-project chat hook (useProjectChat.js) and cursor hook (useProjectCursors.js) live under app/src/features/projects/hooks/ (in collaboration/ and workspace/ respectively), not under app/src/app/hooks/collab/.
Best Practices
For Project Owners
Setting Up Collaboration:
- Plan team structure before inviting
- Assign appropriate roles — Editor only for people who need to change things; Viewer otherwise
- Set clear expectations in project description
- Use comments for coordination
- Monitor activity regularly
Managing Access:
- Regularly review collaborator list
- Remove inactive members
- Update roles as team changes
- Revoke access when project ends
Communication:
- Post welcome message
- Define communication norms
- Encourage @mentions
- Respond to questions promptly
For Collaborators
Being a Good Team Member:
- Check presence before major edits
- Comment on significant changes
- Use @mentions for questions
- Save frequently to release locks
- Resolve conflicts promptly
Workflow Tips:
- Focus on your assigned sections
- Ask before reorganizing
- Flag issues with comments
- Keep discussions on the project or item they concern
- Post a closing comment when an issue is settled
For Large Teams
Organization:
- Assign sections to individuals
- Use categories to divide work
- Regular check-ins
- Version milestones
- Clear change approval process
Troubleshooting
Can't Share Project
"Share button disabled"
- Ensure you're the owner
- Check if cloud sync enabled
- Verify project is saved
"Invalid email"
- Check email format
- Ensure email not already invited
- Try different email
"User not found"
- User must have account
- Ask them to sign up first
- Verify email address
Invitations Not Working
"Didn't receive invitation"
- Check spam folder
- Verify the email address is the one they sign in with
- Check email filters
- If it was revoked, invite the address again — there is no resend
"Can't accept invitation"
- Ensure they are signed in with the invited address — the invite is matched on email
- Confirm the owner has not revoked it (invitations never expire on their own)
- Verify they are not already a member
- Contact the project owner
Sync Issues
"Changes not appearing"
- Check internet connection
- Verify both users online
- Wait a few seconds
- Manual refresh
"Conflict loop"
- Stop editing temporarily
- Communicate with team
- Resolve conflicts one by one
- Coordinate editing sessions
Access Issues
"Can't edit project"
- Check your permission level
- May be Viewer role only
- Ask owner to upgrade role
"Project disappeared"
- Check if removed from project
- May have been deleted
- Contact project owner
- Check archived projects
Performance Issues
"Slow with multiple users"
- Reduce number of active editors
- Work on different sections
- Close unused projects
- Check internet speed
"Too many notifications"
- Adjust notification settings
- Mute specific projects
- Disable non-essential notifications
Related Documentation
- Sync Architecture - Technical sync details
- Authentication - Account setup
- User Guide - General usage
- Troubleshooting - General issues
Last Updated: 2026-09-10 Version: 0.790.2 ← Back to Authentication | Next: Roadmap →
