Skip to content

Project Management

Overview

Projects are the top-level organizational unit of the Progressive World-Building Platform. Every piece of content -- timelines, characters, factions, lore entries -- lives within a project. A project represents a single creative endeavor: a D&D campaign, a novel series, a shared worldbuilding exercise. The project boundary defines who can see and edit content, how public IDs are generated, and what collaboration rules apply.

Project management encompasses the full lifecycle: creation with a unique project key, visibility configuration, member invitation and role assignment, the atomic sequence counter that powers all public IDs within the project, and eventual archival or deletion. The project key system is particularly central -- keys like VNO become the prefix for every public-facing ID in the project (VNO-1, VNO-42, VNO-317), making content easily referenceable across the platform.

The member management subsystem handles the human side: inviting collaborators by email, managing a role hierarchy from Owner down to Viewer, transferring ownership, and removing members. Because the permission model is entirely application-layer (see Permission & Visibility), the project's member list and role assignments are the source of truth for all authorization decisions across the platform.

Goals

  • Provide full CRUD for projects with validation and ownership tracking
  • Implement a globally unique, immutable project key system that powers public IDs
  • Support three visibility levels (public, unlisted, private) with clear behavioral differences
  • Enable project theming with primary and accent colors
  • Maintain an atomic sequence counter for generating deterministic, gapless public IDs
  • Support a complete member invitation lifecycle (invite, accept, decline, revoke)
  • Enforce a clear role hierarchy (Owner > Storyteller > Co-Creator > Player > Viewer) with defined capabilities
  • Enable ownership transfer and safe member removal
  • Support project archival (reversible) and deletion (irreversible)
  • Provide a project dashboard endpoint with content statistics

User Stories

As a Game Master, I want to create a project with a memorable key (like DRAK for my Draken campaign) so all my content gets clean, readable IDs like DRAK-1, DRAK-42.

As a Game Master, I want to invite my players by email so they can join the campaign and see the content I share with them.

As a Game Master, I want to set my project to "unlisted" so only people with the link (or an invitation) can find it, but it is not listed publicly.

As a Writer, I want to configure my project's theme colors so the workspace feels cohesive with my story's aesthetic.

As a Writer, I want to see a dashboard of my project showing how many timelines, characters, and factions I have created so I can track my progress.

As a Project Owner, I want to invite additional co-creators and assign them roles so I can delegate world-building tasks while keeping overall control.

As a Player, I want to accept an invitation and immediately see the project content that my GM has shared with me.

As a Project Owner, I want to transfer ownership to another member if I am stepping away, so the project continues without me.

As a Project Owner, I want to archive a finished campaign so it is preserved but no longer actively editable.

Functional Description

Project CRUD

Create Project

Creating a project establishes a new workspace with a unique key, initial configuration, and the creator as the owner.

Required fields:

  • name (3-100 characters)
  • key (2-10 characters, alphanumeric only, uppercase, globally unique, immutable after creation)

Optional fields:

  • description (max 2000 characters, markdown supported)
  • visibility (public / unlisted / private, default: private)
  • theme.primaryColor (hex color, default: platform default)
  • theme.accentColor (hex color, default: platform default)

Creation behavior:

  1. Validate all fields
  2. Normalize key to uppercase
  3. Check key uniqueness (case-insensitive, including soft-deleted projects)
  4. Create the project record with nextSequenceNumber = 1
  5. Create a ProjectMember record for the creator with role Owner, status accepted, joinedAt = now
  6. Return the created project

Key validation rules:

  • 2-10 characters
  • Alphanumeric only (A-Z, 0-9)
  • Must start with a letter
  • Case-insensitive uniqueness (stored as uppercase)
  • Cannot be changed after creation
  • Cannot reuse keys from soft-deleted projects (keys are permanently reserved)

Read Project

  • GET /api/projects -- list projects the current user is a member of (plus public projects, paginated)
  • GET /api/projects/{key} -- get a single project by key
  • Public projects are visible to everyone; unlisted projects are visible only via direct URL or membership; private projects require membership
  • Response includes member count, content statistics summary, and the user's role in the project (if a member)

Update Project

  • Only the Owner can update project details
  • Updatable fields: name, description, visibility, theme
  • key is NOT updatable (immutable)
  • Visibility changes have cascading effects (see Visibility section below)

Delete Project

  • Only the Owner can delete a project
  • Deletion is a two-step process: archive first, then delete
  • Archived projects can be restored; deleted projects cannot
  • Deletion permanently removes all content (timelines, characters, factions, etc.)
  • The project key remains reserved and cannot be reused

Project Key System

The project key is the cornerstone of the platform's public ID system. Every content entity within a project receives a public ID formed as {KEY}-{sequenceNumber} (e.g., VNO-42). This provides human-readable, project-scoped references that users can share and discuss.

Key characteristics:

  • Globally unique across the entire platform
  • Immutable after project creation
  • Reserved permanently (even after project deletion)
  • Used as the URL segment: /api/projects/{key}/...
  • Case-insensitive for lookups but stored as uppercase

Reserved keys: The system reserves a set of keys that conflict with API routes or have special meaning: API, AUTH, ADMIN, HELP, NEW, EDIT, DELETE, etc. These cannot be used as project keys.

Project Visibility

Visibility controls who can discover and access a project.

VisibilityListed in search/browseAccessible via direct link (non-member)Accessible to members
PublicYesYes (read-only)Yes (role-based)
UnlistedNoYes (read-only)Yes (role-based)
PrivateNoNo (404)Yes (role-based)

Visibility change rules:

  • Changing from public to private does not remove existing members
  • Changing from private to public makes all non-private content visible to the world
  • A confirmation step should be enforced on the frontend when making a project more public
  • Content marked as private within the project remains hidden from non-members regardless of project visibility (see Permission & Visibility)

Theme Configuration

Projects support a simple theming system that frontends can use to visually distinguish workspaces.

Theme fields:

  • primaryColor -- hex color code (e.g., #1A1A2E)
  • accentColor -- hex color code (e.g., #E94560)

Validation:

  • Must be valid 6-digit hex color codes (with or without # prefix)
  • Stored with # prefix
  • Defaults provided by the platform if not specified

Project Dashboard

The dashboard endpoint returns aggregate statistics about a project's content.

Endpoint: GET /api/projects/{key}/dashboard

Response includes:

  • Total timeline count
  • Total event/entry count across timelines
  • Total character count
  • Total faction count
  • Member count (by role breakdown)
  • Recent activity summary (last 10 actions)
  • Project creation date and last updated date

Statistics are computed on-demand (no denormalized counters) for Phase 1. Caching or materialized views can be introduced later if performance requires it.

Member Invitation System

The invitation system allows project members (with sufficient roles) to invite new collaborators by email. This integrates closely with the authentication system (see Authentication & User Management).

Sending an Invitation

Endpoint: POST /api/projects/{key}/members/invite

Fields:

  • email (required)
  • role (required, must be lower than Owner)

Invitation rules:

  • Only the Owner can invite
  • Cannot invite someone who is already a member
  • Cannot invite someone who already has a pending invitation for this project
  • The assigned role must be any role below Owner (Storyteller, Co-Creator, Player, or Viewer)
  • Each invitation gets a unique token and an expiration (7 days)

Invitation behavior:

  1. Validate permissions and input
  2. Create a ProjectMember record with status pending and the assigned role
  3. Generate a unique invitation token
  4. Send an invitation email with a link containing the token
  5. Return the created invitation

Invitation Lifecycle

Accepting an invitation:

  • POST /api/invitations/{token}/accept
  • Requires authentication (the logged-in user's email must match the invitation email)
  • Updates status to accepted, sets joinedAt
  • Idempotent: accepting an already-accepted invitation returns success

Declining an invitation:

  • POST /api/invitations/{token}/decline
  • Requires authentication
  • Updates status to declined
  • The user can be re-invited later (creates a new invitation record)

Revoking an invitation:

  • DELETE /api/projects/{key}/members/invitations/{invitationId}
  • Only the Owner can revoke
  • Only pending invitations can be revoked
  • Revocation invalidates the invitation token

Role Hierarchy & Capabilities

The platform uses a strict role hierarchy. Higher roles inherit all capabilities of lower roles.

CapabilityOwnerStorytellerCo-CreatorPlayerViewer
Delete projectYes
Transfer ownershipYes
Archive/restore projectYes
Update project settingsYes
Manage membersYes
Invite membersYes
Create/edit timelinesYesYesYes
Create/edit charactersYesYesYes
Create/edit factionsYesYesYes
Set content visibilityYesYes
Add comments/reactionsYesYesYesYes
View shared contentYesYesYesYesYes

Role assignment rules:

  • Only the Owner can assign or change roles
  • The Owner role is unique per project (only one owner at a time)
  • Role changes take effect immediately

Member Removal

Removing a member:

  • DELETE /api/projects/{key}/members/{userId}
  • Only the Owner can remove members
  • Cannot remove the Owner (ownership must be transferred first)
  • Content created by the removed member remains in the project, attributed to them
  • Removal is immediate; the member loses access on their next API request

Leaving a project:

  • POST /api/projects/{key}/members/leave
  • Any member except the Owner can leave voluntarily
  • The Owner must transfer ownership before leaving
  • Same content retention rules as removal

Ownership Transfer

Endpoint: POST /api/projects/{key}/transfer-ownership

Fields:

  • newOwnerId (required, must be an existing accepted member)

Transfer behavior:

  1. Validate that the current user is the Owner
  2. Validate that the target user is an accepted member
  3. In a single transaction: set the target user's role to Owner, set the current user's role to Storyteller
  4. The former owner becomes a Storyteller (not removed from the project)
  5. Send a notification to both users

Constraints:

  • Only the current Owner can initiate a transfer
  • The target must be an existing accepted member
  • The transaction must be atomic (no window where the project has zero or two owners)

Atomic Sequence Counter

The nextSequenceNumber field on the project is an atomic counter used to generate public IDs for all content within the project.

How it works:

  • When any entity (timeline, character, faction, etc.) is created within a project, it needs a project-scoped public ID
  • The creation operation atomically increments nextSequenceNumber and assigns the current value as the entity's sequence number
  • The public ID is then {projectKey}-{sequenceNumber} (e.g., VNO-42)

Atomicity guarantee:

  • The increment MUST be atomic to prevent duplicate sequence numbers under concurrent writes
  • Implementation uses a database-level atomic increment (UPDATE projects SET next_sequence_number = next_sequence_number + 1 ... RETURNING next_sequence_number - 1)
  • This runs within the same transaction as the entity creation
  • If the entity creation fails, the transaction rolls back and the sequence number is not consumed

Properties of the counter:

  • Monotonically increasing (never decreases)
  • Gapless under normal operation (gaps only occur if a database transaction partially commits, which should not happen)
  • Shared across ALL entity types within the project (a timeline and a character might be VNO-1 and VNO-2 respectively)
  • The counter value itself is never exposed via API; only the formatted public ID is returned

Project Archival & Deletion

Archival

  • POST /api/projects/{key}/archive -- archives the project
  • POST /api/projects/{key}/restore -- restores an archived project
  • Only the Owner can archive or restore
  • Archived projects are read-only: all write operations return 403
  • Archived projects remain visible to members but do not appear in default project listings
  • Members can still view content, but cannot create, edit, or delete anything
  • Real-time collaboration connections are terminated on archival

Deletion

  • DELETE /api/projects/{key} -- permanently deletes the project
  • Only the Owner can delete
  • Project must be archived first (cannot delete an active project)
  • Deletion is irreversible and removes all associated data:
    • All timelines, events, characters, factions, and their relationships
    • All project members and invitation records
    • All comments, version history, and collaboration data
  • The project key remains permanently reserved
  • A confirmation mechanism is required (e.g., the request must include the project name as confirmation)

Data Flow

Project Creation Flow

Member Management Flow

Role Hierarchy Decision Flow

Key Components

Project Service

The primary service for project CRUD operations. Handles creation (including key validation and uniqueness checks), updates, visibility changes, and deletion. Coordinates with the database to ensure atomic operations, particularly for key reservation and sequence number initialization.

Project Key Validator

A dedicated validation component for project keys. Enforces format rules (alphanumeric, 2-10 chars, starts with letter), checks against the reserved key list, and verifies global uniqueness including soft-deleted projects. This is separated from general validation because key rules are complex and referenced during both creation and key availability checks.

Sequence Number Service

Encapsulates the atomic sequence number increment logic. Called by all content creation services (timeline, character, faction, etc.) when a new entity needs a public ID. Ensures the increment and entity insertion happen within the same database transaction.

Member Service

Manages the full member lifecycle: invitation creation, acceptance, decline, revocation, role changes, removal, and voluntary departure. Enforces role hierarchy rules on every operation. Coordinates with the email service for invitation delivery.

Ownership Transfer Service

Handles the atomic ownership transfer operation. Ensures exactly one owner exists at all times by performing the role swap in a single transaction. Validates preconditions (current user is owner, target is accepted member).

Project Dashboard Service

Aggregates statistics from across content features to build the dashboard response. Queries timeline, character, and faction counts, and assembles the response. Designed to be extended as new content types are added in later phases.

Feature Interactions

FeatureInteraction
AuthenticationProjects reference ownerUserId. Invitation acceptance requires authenticated identity matching.
Content Timeline SystemTimelines belong to a project. Timeline creation consumes a sequence number from the project counter.
Character SystemCharacters belong to a project. Character creation consumes a sequence number.
Faction SystemFactions belong to a project. Faction creation consumes a sequence number.
Permission & VisibilityProject membership and roles are the foundation for all permission checks. Content visibility rules are layered on top of project visibility.
Real-time & CollaborationSignalR groups are organized by project. Member changes affect real-time connection authorization.
Version HistoryProject-level changes (settings, members) are tracked in the changelog.

Edge Cases & Error Handling

Project key collision: The database unique constraint is the final guard. If two requests attempt to create the same key simultaneously, one will succeed and the other will receive a 409 Conflict.

Sequence number under high concurrency: The atomic database increment (UPDATE ... RETURNING) handles concurrent entity creation correctly. Two concurrent creates will receive different sequence numbers. This is guaranteed by PostgreSQL's row-level locking on the UPDATE.

Owner tries to leave without transferring ownership: Return 409 Conflict with a clear message: "Transfer project ownership before leaving."

Inviting an email that later registers with a different email: The invitation is tied to the specific email address. If the invitee registers with a different email, they cannot accept the invitation. The inviter must revoke and re-invite with the correct email.

Changing project visibility from private to public: This is a significant action. The API should require an explicit confirmation field (e.g., confirmVisibilityChange: true) to prevent accidental exposure.

Deleting a project with active real-time users: Archival (which precedes deletion) terminates all real-time connections. By the time deletion is possible, no active connections exist.

Member role change race condition: Use optimistic concurrency (a version/timestamp column on ProjectMember). If two role changes for the same member arrive simultaneously, one will fail with a 409 Conflict and can be retried.

Invitation to an email that is already a member: Return 409 Conflict: "This user is already a member of the project."

Re-inviting after decline: Allowed. A new invitation record is created. The old declined record is retained for audit.

Project key that looks like a number: Keys must start with a letter, so purely numeric keys are rejected. This prevents ambiguity with numeric IDs in URL routing.

Phase & Priority

Phase 1: Core Timeline & Projects

Project management is a Phase 1 feature, implemented immediately after authentication. All content features depend on projects existing.

Priority order within this feature:

  1. Project CRUD with key system and validation
  2. Visibility settings (default to private)
  3. Atomic sequence counter
  4. Member invitation and acceptance
  5. Role hierarchy and permission enforcement
  6. Role management (change, remove)
  7. Ownership transfer
  8. Project dashboard
  9. Theme configuration
  10. Archival and deletion