PrimeBrickPrimeBrick
  • Docs
  • Contact
  • MIT License
  • Documentation
  • MCP Server
  • API Catalog
  • Services
  • Libraries
PrimeBrickPrimeBrick

© 2026 PrimeBrick. MIT License. v3.8.0

github
Getting Started
    IntroductionQuick StartArchitectureConfig modulesInfrastructureCollaboration & Visual Merge
Compliance & Policy
API Reference
powered by Zudoku
Getting Started

Collaboration & Visual Merge

Overview

Primebrick includes a built-in visual collaboration system that lets multiple users work on the same record simultaneously — safely. When two colleagues open the same entity, they see each other's presence in real time. When one saves, the other's form updates field by field, not whole-entity. If both edited the same field, a conflict resolution panel helps them pick which value wins.

The entire system is automatic for any entity decorated with @AuditTrail(). No per-entity configuration, no manual SSE wiring, no custom merge logic. The framework handles presence, change propagation, diffing, and conflict detection.

How it works

1. Live presence

When a user opens an entity form, the frontend opens an SSE connection to GET /api/v1/entities/:entity/:uuid/presence/events and sends a JOIN signal via POST /api/v1/entities/:entity/:uuid/presence. The backend stores the user in a Redis presence hash with a 30-second TTL. The frontend sends a heartbeat every 20 seconds to keep the entry alive.

Other users on the same entity see the new user's avatar appear in real time via the SSE stream. Avatars are split into two groups:

  • Editors (left, warning-colored ring) — users who are actively editing a field. The tooltip shows which field and the in-progress value.
  • Readers (right, plain avatar) — users who have the form open but are not editing.

2. Entity-changed awareness

When any user saves an auditable entity, the backend's audit port adapter fires a hook after the audit log row is committed. The hook builds an EntityChangedMarker containing:

FieldDescription
entity_typeThe table name (e.g. customers)
entity_uuidThe entity UUID
versionThe new version number after the save
audit_log_idThe audit log row ID (for the diff endpoint)
changed_byThe user UUID of the writer
changed_atEpoch milliseconds of the save

The marker is published two ways:

  1. Redis — stored with a 5-minute TTL so late-joining clients can see the most recent change without scanning the audit log.
  2. NATS — published on entity.{entityType}.{entityUuid}.changed so connected SSE clients on any backend instance receive the event in real time.

3. Field-level merge

When the frontend receives an entity-changed SSE event, it:

  1. Fetches the field-level diff: GET /api/v1/entities/:entity/:uuid/audit/:auditLogId
  2. For each field in the diff:
    • If the user has not touched the field → silent merge: the field updates with a transient green badge (3 seconds).
    • If the user is currently editing the field → conflict: the field is marked with a conflict indicator and the resolution panel opens.
  3. Sets is_stale = true (a persistent banner until the user reloads).
  4. Updates the internal loaded_version to the new version (aligning the optimistic lock for the next save).

4. Conflict resolution

When two users edit the same field and one saves, the other user sees a conflict panel showing:

  • Your value — what the user has in their form
  • Their value — what the other user saved
  • Original value — what was there before either started editing

The user picks per field:

  • Keep DB value — accept the other user's change
  • Keep my value — discard the other user's change for this field
  • Overwrite — force my value (starts a new version chain)

5. Optimistic locking (the safety net)

Every auditable entity has an integer version column that auto-increments on each write. When a user saves, the backend checks that the version in their request matches the current version in the database. If another user saved in between, the versions don't match and PostgreSQL raises ERR01 → HTTP 409 Conflict.

This prevents lost updates — the last-writer-wins problem. The frontend catches the 409, fetches the latest diff, and opens the conflict resolution panel.

See Optimistic Locking & Concurrency for the DAL-level implementation details.

Technology

ComponentTechnologyRequired?
Real-time pushSSE (Server-Sent Events)Yes (HTTP/1.1 or HTTP/2)
Presence storeRedisBest-effort (system works without it)
Cross-instance fanoutNATSBest-effort (system works without it)
Audit logPostgreSQLYes (required for @AuditTrail)

Does collaboration require HTTP/2?

No. SSE (Server-Sent Events) works over both HTTP/1.1 and HTTP/2. HTTP/2 is recommended for high-concurrency scenarios (many simultaneous SSE connections) because it multiplexes streams over a single TCP connection, avoiding the per-connection limit of HTTP/1.1. But the feature is fully functional on HTTP/1.1 — no protocol upgrade is required.

Why SSE instead of WebSockets?

SSE is simpler, works through proxies and CDNs without special configuration, and is sufficient for the collaboration use case (server → client push of presence and entity-changed events). The client → server signals (JOIN, LEAVE, HEARTBEAT, FIELD_FOCUS) use standard HTTP POST requests. No bidirectional full-duplex channel is needed.

Automatic enablement

Collaboration is automatic for any entity decorated with @AuditTrail():

Code
import { Entity, AuditTrail } from "@primebrick/dal-pg"; @Entity("customers") @AuditTrail() export class CustomerEntity { // ... fields }

The backend's assembleMeta() utility auto-injects a collaboration fragment into the entity meta response:

Code
{ "entity": "customer", "collaboration": { "enabled": true, "expose_editing_value": true } }

The frontend reads collaboration.enabled from the meta to decide whether to activate the presence channel and merge logic for that entity.

What the user sees

ScenarioWhat happens
Open a form aloneNo avatars, normal editing
Another user opens the same formTheir avatar appears (reader)
Another user clicks a fieldAvatar ring turns warning color, tooltip shows field + value
Another user savesYour untouched fields update silently (green badge 3s)
You both edited the same fieldConflict panel opens — pick which value wins
You save after someone else savedIf versions don't match → 409 → conflict panel
Someone closes their tabTheir avatar disappears (LEAVE on key expiry)

Infrastructure requirements

Redis (best-effort)

Redis stores the presence hashes and entity-changed markers. If Redis is unavailable, the system continues to function — presence is simply not tracked, and entity-changed events are not published. The audit log remains the source of truth.

Enable Redis keyspace notifications for automatic LEAVE on expiry:

Code
redis-server --appendonly yes --notify-keyspace-events Exg

NATS (best-effort)

NATS provides cross-instance fanout — when user A on backend instance #1 saves, user B on backend instance #2 receives the entity-changed event. If NATS is unavailable, SSE events only propagate within the same backend instance.

PostgreSQL

PostgreSQL is required — the audit tables ({entity}_audit) store the field-level diffs that power the merge logic. The @AuditTrail() decorator triggers automatic audit table creation during database patch generation.

API endpoints

MethodPathDescription
POST/api/v1/entities/:entity/:uuid/presenceSend a presence signal (JOIN/LEAVE/HEARTBEAT/FIELD_FOCUS/FIELD_BLUR)
GET/api/v1/entities/:entity/:uuid/presenceGet the current presence snapshot
GET/api/v1/entities/:entity/:uuid/presence/eventsSSE stream (real-time presence + entity-changed events)
GET/api/v1/entities/:entity/:uuid/audit/:auditLogIdField-level diff for merge/conflict resolution

Error codes

CodeHTTPDescription
ERR01409Optimistic concurrency violation — version mismatch
ERR02400Missing version field on an auditable-entity write
ERR03404Record vanished — hard-deleted by another writer

See Error Handling for the full RFC 7807 error format.

Next steps

  • Optimistic Locking & Concurrency — how the DAL prevents lost updates
  • Audit Trail — how audit tables store field-level diffs
  • Backend Collaboration Module — BE-side implementation details
  • Error Handling — RFC 7807 error format and codes
Last modified on July 26, 2026
InfrastructureAutomated Compliance Assessment
On this page
  • Overview
  • How it works
    • 1. Live presence
    • 2. Entity-changed awareness
    • 3. Field-level merge
    • 4. Conflict resolution
    • 5. Optimistic locking (the safety net)
  • Technology
    • Does collaboration require HTTP/2?
    • Why SSE instead of WebSockets?
  • Automatic enablement
  • What the user sees
  • Infrastructure requirements
    • Redis (best-effort)
    • NATS (best-effort)
    • PostgreSQL
  • API endpoints
  • Error codes
  • Next steps
TypeScript
JSON