feat(detail): collaborative editing defaults — auto-subscribe + lock state composables#192
Merged
Merged
Conversation
Proposes auto-subscribe-on-detail-view + lock-on-edit as default behaviour for CnDetailPage / CnObjectSidebar. ADDED requirements: - REQ-CO-LOCK-001..004 (composables): useObjectSubscription + useObjectLock lifecycle, reactive lock state from store cache, acquire/release + typed errors, auto-release on unmount + beacon on beforeunload. - REQ-DPG-COLLAB-001..004 (detail-page-grid): default subscribe + default lock-on-edit + manifest opt-out (subscribe: false / lock: false) + save/cancel/navigation release. Implementation, tests, and docs follow in subsequent commits per tasks.md.
Implements the v1 deliverable from the collaborative-editing-defaults
spec.
Composables:
- useObjectSubscription(store, type, id, options) — wraps
objectStore.subscribe with a Vue scope-bound lifecycle (mount /
unmount / reactive id flips). No global state; idempotent.
- useObjectLock(store, register, schema, id, options) — derives
reactive lock state from the store cache (kept fresh by
liveUpdatesPlugin) and exposes acquire/release that hit OR's
/api/objects/{r}/{s}/{id}/lock endpoint. Exports typed
LockConflictError and PermissionError.
Component:
- CnLockedBanner — presentation-only locked-by-X notice strip.
Auto-wiring:
- CnDetailPage gains subscribe + objectStore props (default true /
null) and renders CnLockedBanner when @self.locked indicates a
remote user holds the lock.
- CnObjectSidebar mirrors the auto-subscribe (no banner — sidebars
host editor surfaces per tab; consumers handle lock UX inside
individual tabs via useObjectLock directly).
Tests:
- useObjectSubscription.spec.js — 4 tests covering subscribe on mount,
unsubscribe on unmount, reactive id flips, enabled gate.
- useObjectLock.spec.js — 10 tests covering REQ-CO-LOCK-002 / 003 / 004
(cache-derived state, acquire/release HTTP semantics, typed errors,
beforeunload beacon, scope-dispose release).
Full suite: 893 / 893 (one pre-existing CnMapWidget leaflet-missing
suite failure is unrelated). check:docs green.
Lock-on-edit auto-acquire (wiring acquire/release into the form
dialogs) is intentionally deferred to a follow-up cycle; the
composables are public so early adopters can wire it themselves.
This was referenced May 10, 2026
Contributor
|
🎉 This PR is included in version 1.0.0-beta.31 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements v1 of the collaborative-editing-defaults spec.
Composables (public API):
useObjectSubscription(store, type, id, opts)— Vue scope-bound subscribe / unsubscribe lifecycle aroundobjectStore.subscribe(). Reactiveidflips re-subscribe automatically;enabled: ref(false)disables;tryOnScopeDispose+onBeforeUnmountclean up.useObjectLock(store, register, schema, id, opts)— derives reactive{ locked, lockedByMe, lockedBy, expiresAt }fromobjectStore.objects[type][id]['@self'].locked(kept fresh byliveUpdatesPlugin); exposesacquire()/release()against OR's lock REST endpoints; auto-renew while editing + visible;beforeunloadbeacon + scope-dispose release.LockConflictError(409 / 423 withlockedByinfo) andPermissionError(401 / 403).Component:
CnLockedBanner— presentation-only "Locked by X" notice strip.Auto-wiring (defaults):
CnDetailPagegainssubscribe(default true) +objectStoreprops. When set, auto-subscribes viauseObjectSubscriptionand rendersCnLockedBannerwhen a remote pessimistic lock is active.CnObjectSidebarmirrors the auto-subscribe (no banner — sidebars host editor surfaces per tab).Why this matters
This makes the live-update + lock pattern that we just shipped (PRs #186, #189, OR #1453, #1457) the default behavior of every consumer's detail surface. No per-app code: decidesk, mydash, opencatalogi, procest, pipelinq, openregister itself all inherit auto-subscribe on next lib bump.
v1 vs follow-up
v1 ships the live-update half end-to-end (subscribe defaults + lock state visible to readers). The lock auto-acquire on Edit-mode toggle (wiring
acquire()/release()intoCnAdvancedFormDialog/CnFormDialog) is intentionally deferred to a follow-up cycle so this PR stays small and the composables can be adopted standalone today.Test plan
npm test— 893 / 893 passing (one pre-existingCnMapWidgetleaflet-missing suite failure is unrelated).useObjectSubscription(mount/unmount, reactive id flips, enabled gate).useObjectLock(cache-derived state, acquire/release HTTP semantics, typed errors, beforeunload beacon, scope-dispose release).npm run check:docsgreen; new docs pages added for both composables, both errors, andCnLockedBanner; existing detail-page + object-sidebar docs updated with the new defaults section.Related