Skip to content

feat(detail): collaborative editing defaults — auto-subscribe + lock state composables#192

Merged
rubenvdlinde merged 2 commits into
betafrom
feat/collaborative-editing-defaults
May 10, 2026
Merged

feat(detail): collaborative editing defaults — auto-subscribe + lock state composables#192
rubenvdlinde merged 2 commits into
betafrom
feat/collaborative-editing-defaults

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Implements v1 of the collaborative-editing-defaults spec.

Composables (public API):

  • useObjectSubscription(store, type, id, opts) — Vue scope-bound subscribe / unsubscribe lifecycle around objectStore.subscribe(). Reactive id flips re-subscribe automatically; enabled: ref(false) disables; tryOnScopeDispose + onBeforeUnmount clean up.
  • useObjectLock(store, register, schema, id, opts) — derives reactive { locked, lockedByMe, lockedBy, expiresAt } from objectStore.objects[type][id]['@self'].locked (kept fresh by liveUpdatesPlugin); exposes acquire() / release() against OR's lock REST endpoints; auto-renew while editing + visible; beforeunload beacon + scope-dispose release.
  • Typed errors: LockConflictError (409 / 423 with lockedBy info) and PermissionError (401 / 403).

Component:

Auto-wiring (defaults):

  • CnDetailPage gains subscribe (default true) + objectStore props. When set, auto-subscribes via useObjectSubscription and renders CnLockedBanner when a remote pessimistic lock is active.
  • CnObjectSidebar mirrors 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() into CnAdvancedFormDialog / 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-existing CnMapWidget leaflet-missing suite failure is unrelated).
  • 4 new tests for useObjectSubscription (mount/unmount, reactive id flips, enabled gate).
  • 10 new tests for useObjectLock (cache-derived state, acquire/release HTTP semantics, typed errors, beforeunload beacon, scope-dispose release).
  • npm run check:docs green; new docs pages added for both composables, both errors, and CnLockedBanner; existing detail-page + object-sidebar docs updated with the new defaults section.
  • CI green.
  • Browser verification once decidesk picks up the bump.

Related

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.
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.0.0-beta.31 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant