Collaboration | Cine Power Planner

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

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

IDRequirementAcceptance CriteriaPrioritySource
REQ-COL-001The 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 closedMustdata/collab
REQ-COL-002The system shall enforce permission levels: Owners manage everything, Editors add/edit/delete items and comment, Viewers read-onlyViewer attempts edit or comment → blocked; Editor edits → succeeds; Owner changes roles → succeedsMustdata/collab
REQ-COL-003The system shall show real-time presence indicators (avatars, online status, cursor positions) via Supabase Realtime channelsOpen project on 2 devices → both show presence dots; close one → presence removed within 10sMustdata/collab
REQ-COL-004The system shall provide automatic edit locking when a user is actively editing, preventing conflicting editsUser A edits item → User B sees lock indicator; User A saves → lock releases within 30s timeoutMustacquire_project_lock
REQ-COL-005The 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 chronologicallyMustdata/collab
REQ-COL-006The 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 versionsMustfeatures/conflict-resolution
REQ-COL-007The system shall provide real-time project chat for quick coordinationType message → appears for all members; messages ordered chronologically; persist across page reloadShoulddata/collab
REQ-COL-008The system shall maintain a field-level edit history showing who changed what and whenEdit item → history entry created; view history → shows user, field, old/new values, timestampShoulddata/collab
REQ-COL-009The system shall support the invitation lifecycle Pending → Accepted / Declined / Revoked, with owner-side revokeInvite 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 resendMustaccept_invite
REQ-COL-010The system shall support real-time cursor presence across the project workspace (all tabs), showing collaborator positionsTwo users open same project → cursor positions visible; move cursor → other user sees movement; label shows user nameShoulduseProjectCursors
REQ-COL-011The system shall support a per-project chat for team communicationOpen project → chat panel available; send message → visible to collaborators; timestamps shown; offline → queuedShoulduseProjectChat

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', and addComment is absent from READ_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, buildComment has no parentId and buildThread creates one thread per target. Adding them is a comment-schema change: STORAGE_VERSION bump, 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

IDRequirementMetricPriority
NFR-COL-001Presence updates shall propagate to all collaborators within 2 secondsRealtime latencyShould
NFR-COL-002Edit lock acquisition/release shall complete within 500msLock latencyMust
NFR-COL-003Conflict resolution UI shall render diffs for projects with ≤500 items within 1 secondDiff render timeShould
NFR-COL-004Collaboration features shall degrade gracefully when Realtime channel disconnectsGraceful degradationMust

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:

  1. Create an Account (all collaborators)

    • Sign up in Settings → Account
    • Verify email
    • Complete profile
  2. 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
  3. 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:

  1. Owner creates project and adds initial gear
  2. Owner shares project with team members
  3. Team accepts invitations
  4. Everyone collaborates in real-time
  5. Changes sync automatically

Sharing Projects

Inviting Collaborators

Steps to Share:

  1. Open Project:

    • Navigate to the project workspace
    • Ensure you're the project owner
  2. 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
  3. 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)
  4. Set Permissions:

    • Choose Editor or Viewer. Ownership cannot be granted this way — project_invites.role is check (role in ('editor','viewer')), and the picker offers only those two. The guide offered "Owner" as a third choice until 2026-09-07.
  5. 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_invites has no expiry column, and its status is check (status in ('pending','accepted','declined','revoked')) — there is no expired value 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:

  1. Find the pending invitation in Collaborators
  2. Click "Revoke Invite"
  3. 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

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

  1. Open project
  2. Scroll to the Collaborators section
  3. Find collaborator
  4. Click current role
  5. Select new role
  6. Confirm change

Remove Collaborator:

  1. Find collaborator in list
  2. Click "Remove"
  3. Confirm removal
  4. 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:

FamilySharedNotes
characters (Cast)One canonical row per cast member
scripts
scenes
shots (Shot List)
episodes
floorPlans
floorPlanNodesMarkers; 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_id is 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-disabled so 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

SurfaceLocked fields / behavior
Project Info & SettingsProject name, description, settings-tab fields.
SchedulePeriod editor (dates, shoot-day window), calendar exclusion settings.
Camera Package editorMonitor, wireless transmitter, FIZ motors (×4), FIZ controllers (×4).
Project Requirements — Delivery SpecsGlobal delivery-resolution, base frame rate, aspect ratio.
Project Requirements — per-packagePer-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 — globalTransportation 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 settingsEnabled toggle, rate, estimated value — per-field, on owned / rental / subrental.
Auto-Gear rule editorRule-level lock (Save button and Enabled toggle disable with a "locked by other" notice).
Call SheetsGeneral Info (title, mode, PDF language, dates, producer, director), Department Call Times editor, Location Detail editor.
Pre-production — Location sub-tabEvery 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.mjs resolves lock ids by AST — so a useFieldLock call inside that renderer cannot be resolved, and the guard fails rather than degrading. The calls therefore live in hooks/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.js fails 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:

  1. Open project workspace
  2. Click "Comments" button (sidebar)
  3. Click "Add Comment"
  4. Type message
  5. Add @mentions (if available)
  6. 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:

  1. Go to Settings → General → Notifications
  2. 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:

  1. Open a shared project workspace
  2. Click the Chat icon in the collaboration panel
  3. 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:

  1. Open the collaboration panel
  2. Click "Edit History"
  3. 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:

  1. Simultaneous edits: Two users edit same item
  2. Offline changes: Changes made while offline
  3. Category moves: Item moved by multiple users
  4. 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:

  1. Keep Local: Your changes win
  2. Keep Remote: Their changes win
  3. Merge: Combine changes manually
  4. 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-disabled so 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/:

ServiceFilePurpose
Shared ProjectscollabProjectService.jsCreate/manage shared projects, collaborator roles, permissions
SyncsharedProjectSync.jsReal-time sync, remote change handling, state management
InvitationsinviteService.jsSend, accept, reject, cancel invitations
LockinglockService.jsItem/project locking, timeouts, visual indicators
Merge EnginemergeEngine.jsThree-way merge, field-level conflict detection, strategy selection
Diff PresenterdiffPresenter.jsSide-by-side change visualization
ChatchatService.jsReal-time project chat
CursorscursorService.jsLive cursor positions for collaborative editing
Activity LogactivityLogService.jsAudit trail of all collaborative actions
Edit HistoryeditHistoryService.jsField-level change tracking with revision timeline
PresencepresenceService.jsIdle detection helpers + the participant roster the sync fast lane reads
Entity LocksentityLockService.jsCoarse entity-level locks (project, rule, document)
Email PreferencesemailPreferences.jsPer-user notification email settings

Push notifications live in app/src/data/notifications/pushNotificationService.js, not under collab/.

Hooks in app/src/app/hooks/collab/:

HookPurpose
useCollaborationModel.jsOrchestrates shared project state and collaborator tracking
useCollabEditSession.jsTracks active editing sessions, real-time presence, edit coordination
useCollabUndo.jsCollaborative undo/redo with conflict-aware state rollback
useProjectPresence.jsUser presence within projects (viewing, activity status)
useProjectMemberProfiles.jsLoads profile data for the collaborators of a project
useDisplayNameResolver.jsResolves collaborator display names
useFieldLock.js / useFieldBadge.jsPer-field lock acquisition and LockedFieldBadge rendering
useEntityLock.js / useProjectEntityLocks.jsCoarse entity-level lock claims (project, rule, etc.)
useActivityLog.jsExposes 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:

  1. Plan team structure before inviting
  2. Assign appropriate roles — Editor only for people who need to change things; Viewer otherwise
  3. Set clear expectations in project description
  4. Use comments for coordination
  5. 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:

  1. Check presence before major edits
  2. Comment on significant changes
  3. Use @mentions for questions
  4. Save frequently to release locks
  5. 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


Last Updated: 2026-09-10 Version: 0.790.2 ← Back to Authentication | Next: Roadmap →