Haiku BShelf: Replicant Admission, Persistence, and Recovery
Manage Haiku replicants with BShelf by defining type policy, persistence, zombie recovery, and ownership across a host view's lifecycle.
BShelf attaches to a BView and hosts replicants, which are archived view objects that can be dragged between compatible containers. It is a general Interface Kit facility behind desktop-style widgets, not just a Deskbar API. A shelf can load and save replicant archives through an entry or a BDataIO stream, enforce a shelf type, and decide how to display a replicant whose originating application is unavailable.
Replicants cross an application and persistence boundary. The host must decide which types it accepts, where their serialized state lives, and how missing or incompatible implementations are represented. A saved archive is not proof that the original add-on can still be instantiated or that its code is safe to run.
Attach one shelf to one host view
Construct the shelf for a specific container view and choose whether it accepts drag-and-drop. A shelf can use an entry_ref as its load/save location or a BDataIO stream. Keep the host view alive as long as the shelf is attached, and do not treat a raw BShelf* as a detached data model. The shelf participates in the view and message lifecycle.
BShelf* shelf = new BShelf(hostView, true, "dashboard-item");
shelf->SetAllowsZombies(true);
shelf->SetDisplayZombies(true);
This is an initialization sketch. A production view should own the shelf for the right lifetime, check any persistence load status available from the selected constructor path, and decide when state is saved. Review the current header and implementation for the exact ownership behavior of the stream and view attachment. Avoid deleting the shelf while callbacks or replicant views still reference it.
BShelf’s destructor calls Save() and detaches itself from the container view, according to the current source documentation. That makes destruction an observable persistence event. Do not assume destroying a temporary shelf is side-effect free; use an explicit save policy and inspect status so failed writes are visible before shutdown.
Define a shelf type and admission rule
The shelf’s type is a filter-like value associated with its handler. A shelf can reject replicants with a nonmatching type. By default, replicants without a type may be accepted; SetTypeEnforced() makes matching stricter according to the API documentation. Define an application-specific type string and use it consistently in the replicant archive. Do not use a display label that can be translated or changed as the compatibility identifier.
CanAcceptReplicantMessage() and CanAcceptReplicantView() provide admission hooks for archive data and a live view. Validate dimensions, expected message fields, and the intended replicant type before accepting it. A malicious or malformed archive should not be allowed to trigger unbounded allocation or a complex view tree. Keep policy checks in the shelf subclass or hosting application rather than relying only on the replicant author’s claims.
Type enforcement is not a versioning mechanism. A replicant can match the shelf type while its archived fields have changed. Include your own schema version in archived state and define how old versions are migrated or rejected. If compatibility fails, prefer a clear placeholder and a recovery message over crashing the host or silently discarding the saved data.
Load, save, and handle unavailable add-ons
Replicant state is archived as messages and can be stored in an entry or stream. Treat persistence as a real I/O operation: the backing volume may be read-only, full, unmounted, or inaccessible when the host exits. Check Save() status and surface a failure before closing the owner view when user data could be lost. If saving frequently, avoid blocking the UI on large serialization or storage activity; use the documented API in the appropriate lifecycle and keep the state bounded.
When the originating application or add-on cannot be found, BShelf may display a “zombie” placeholder. SetAllowsZombies() controls whether these placeholders may be created, and SetDisplayZombies() controls whether they are shown. Decide whether the host should preserve unknown data for recovery or omit it from the display. Hiding a zombie should not be mistaken for deleting its saved archive.
The host can enumerate replicants and remove them by view, archive, or index. Indices are positional and can shift after removal; do not store an index as a permanent replicant identity. Keep a stable application-level key in the archive when you need to associate a view with model state. If removal is user initiated, update the model and save policy together so a deleted widget does not reappear after restart.
Coordinate drag-and-drop and layout
A shelf can accept user-dropped replicants when dragging is enabled. Check the view’s CanAcceptReplicantView() and AdjustReplicantBy() behavior for placement. A shelf should reject objects that overlap reserved controls, exceed its bounds, or violate the product’s layout rules. Drag feedback is part of the user interface; do not assume that a successful drop means the item has been durably saved.
Replicant views are child views and participate in the host’s layout, drawing, and locking rules. Avoid doing network access, large archive parsing, or synchronous package discovery while holding the window’s lock. If a replicant needs background work, keep it bounded and deliver UI changes through the owning looper. During shutdown, stop callbacks and detach observers before the host view disappears.
The shelf’s message and scripting behavior can expose operations beyond the immediate C++ call. Validate message fields and target identity before mutating replicant state. Unknown messages should be forwarded through the normal handler chain. Do not create a custom protocol that assumes an archive originated in a trusted local application simply because it appears on the desktop.
Recovery and lifecycle tests
Test an empty shelf, a valid replicant, a mismatched shelf type, an archive with missing fields, an unavailable source application, a read-only or full save location, and host view destruction. Verify that a zombie placeholder does not erase serialized state and that Save() failure is reported. Exercise drag and remove operations while the owner window is open and during teardown.
Back up or export the backing entry/stream before schema migrations. Test restoration into a clean environment where the original replicant add-on is absent. Confirm that the host can still open, display a placeholder, preserve the archive, and later recover when a compatible add-on is installed. Never assume that a successful parse of a message implies the archived class is available.
When changing the archive schema, keep a representative fixture from every released version and test migration from those fixtures, not just a save/load round trip using the newest build. A round trip can conceal a migration bug because both ends share the same assumptions. Give recovery tooling a way to copy the raw saved archive before attempting reconstruction, and include diagnostics that identify shelf type, replicant archive class, and load status without dumping private user data. This turns an unavailable replicant from an opaque blank widget into a supportable compatibility event.
If the shelf’s backing stream is supplied by another component, document who owns it and whether it remains open until the shelf is destroyed. A shutdown order that closes the stream first can make the destructor’s save fail even though the earlier explicit save succeeded. Prefer an explicit save at a user-visible checkpoint, inspect its status, and only then tear down the dependent stream and host view. Test both normal shutdown and forced application exit because their available time for persistence may differ.
Acceptance criteria
Accept a shelf implementation when its parent view and save location have explicit lifetimes, admission rules use a stable shelf type, archived state is versioned, unavailable replicants have a documented recovery policy, and save/remove failures are observable. Verify all supported drag, restart, migration, and teardown paths.
BShelf is a useful container for Haiku replicants, but persistence, compatibility, admission, and add-on availability remain application responsibilities.
Related:
- Haiku BDeskbar: Adding and Removing Desktop Replicants
- Haiku BArchivable: Object State, Versioning, and Safe Reconstruction
Sources: