Features
MarkdownCollaborative item editing
When two editors open the same collection item, Externa shows who else is on the form, optional soft lock (read-only for others until takeover), and a stale-save warning if the row changed on the server. This is a Directus-style collaboration signal — not CRDT field sync.
Requires Reverb
Presence and live lock hints use Laravel Echo on presence channel collection-item.{collectionId}.{itemId}. Configure Reverb & Echo (BROADCAST_CONNECTION=reverb, reverb:start, and Vite VITE_REVERB_*). When realtime is off, the item form still sends expected_updated_at on save so the API can reject stale overwrites; avatars and lock whispers are degraded.
Related
Item CRUD: Collection items. Team chat (separate channel): Chat. Global online users: Reverb & Echo.
Behavior
| Feature | Realtime on | Realtime off (degraded) |
|---|---|---|
| Other editors’ avatars | Echo here / joining / leaving on collection-item.* | Hidden; short banner notes limited mode |
| Soft lock | Whisper item-lock + heartbeat (~30s TTL) | Not enforced client-side; server ACL unchanged |
| Stale overwrite | Whisper item-updated-at + dialog before submit | expected_updated_at hidden field + server validation |
Editors with collection access join the presence channel when the item form mounts. The first active editor claims a soft lock; others see read-only fields until Take over editing. Saving after a remote change prompts Reload or Save anyway; matching expected_updated_at is required unless the user confirms.
Authorization
Channel auth: routes/channels.php — active user, can-show-collections, item exists, CollectionPermissionEnforcer::isItemReadable. Same read gate as opening the item editor.
Manual test checklist
- [ ] Two browsers, same item: each sees the other’s avatar in the form header.
- [ ] Second browser: fields read-only until takeover; takeover restores edit.
- [ ] Browser A saves; browser B save shows stale dialog (with Reverb) or validation error (without).
- [ ] User without collection access cannot authorize the presence channel (403 on
/broadcasting/auth).
Code map
| Area | Location |
|---|---|
| Channel | routes/channels.php (collection-item.{collectionId}.{itemId}) |
| Client presence | resources/js/lib/item-presence.ts, hooks/use-item-presence.ts |
| UI | components/collections/item-editing-presence.tsx, pages/collections/items/form.tsx |
| Stale guard | ItemController::update + expected_updated_at on UpdateCollectionItemRequest |
| Tests | tests/Feature/Collections/CollectionItemPresenceChannelTest.php, CollectionItemStaleUpdateTest.php |