Changes in version 0.1.2 - The dock "manager" object is gone. apply_board_update.dock_board() is now a pure reducer over board_layouts(); all live view surgery (instantiate / tear down / restore / rename / switch) runs in a single closure-resident reconcile pass driven by the committed board, replacing the duplicated delta-driven and UI-driven view CRUD. View init is just the empty-registry case of that pass (create every view, show the active one), so there is no separate init path. The per-session dock state is ordinary closure-private state passed explicitly, not a handle threaded back from the board callback through dot_args. Also makes augment_board_update.dock_board() idempotent — a view id is minted once rather than re-minted on every augment pass — fixing a view-add loop (#164). - Views now carry a stable, immutable id decoupled from their editable display name, mirroring the id / name split used for blocks. dock_layouts (and the runtime dock_mgr$docks registry) are keyed by id; the name is an attribute read / written via view_name() / view_name<-() (with view_names() for a whole collection), and active_view() now returns the active view's id. Renaming a view is a pure name-attribute write — the id, dock module and DOM element are untouched, so no structure is ever re-keyed (and the live-sync rename no longer leaks as remove-then-add). The dock module / DOM ids derive deterministically from the view id (no random per-render minting), and the views delta gains a rename slot. Naming constraints relax to display concerns (non-empty, unique label). Serialization round-trips ids. In new_dock_board(layouts = list(...)) the list name is the view's id (the container's key, like a block id — minted when absent); the display name is set on the view via dock_layout(name = ) and falls back to a label derived from the id when unset. The views delta addresses existing views by id (the only stable handle) — mod / rm / active carry ids; add supplies a display name and mints the id. Producers that addressed views by name (e.g. blockr.assistant) must switch to ids (#166). - dock_layout objects gain format() / print() methods that render the arrangement as an indented tree: orientation, nested groups with their sizes, tabbed leaves with the active tab, and the focused panel. Panel IDs print without their block_panel- / ext_panel- prefixes by default; pass bare = FALSE for the canonical IDs (#161). - The add / append / prepend block action handlers now mount the blockr.ui block-browser module: a card-list block picker with search, per-category icons, and a per-card expand for tweaking the id / title / link / port before adding. Repeated single clicks on a card produce distinct blocks (suggested ids are seeded against the board so they never collide), replacing the old single-select selectize form. The per-field Shiny inputs (_block_selection / _block_id / _block_name / _link_id / _block_input / _block_confirm) are gone in favour of a single committed-block reactive returned by blockr.ui::block_browser_server(). block_sidebar_body() is removed (nothing in-tree calls it; link / stack flows use link_sidebar_body() / stack_sidebar_body() unchanged). Requires blockr.ui with the block-browser module. - The views slot in the board_update payload is now a structured delta (add / mod / rm / active) instead of a wholesale dock_layouts replacement. Mentioned views are touched, omitted views keep their current state, and the four sub-slots compose atomically with blocks / links / stacks in the same lifecycle tick. See ?dock_board_update_lifecycle for the contract (#150). - Removing a block no longer clears the active view's layout. Instead, every view containing the removed block has the block's panel dropped surgically from its layout, preserving the rest of the grid. augment_board_update.dock_board() performs the cleanup for the update() lifecycle path; rm_blocks.dock_board() does the same surgically (rather than nuking the active layout) for direct callers like clear_board() (#150). - board_layouts(rv$board) now stays in sync with UI-driven layout changes (panel close/add, drag-resize/rearrange, view CRUD). UI-driven mutations are routed through update(list(views = ...)) and applied via validate_board_update.dock_board() and apply_board_update.dock_board(). Writes are debounced (250 ms) so drag-resize doesn't thrash. Requires blockr.core (>= 0.1.3) for the update lifecycle generics. Changes in version 0.1.1 (2026-04-29) - Added prepend block action. - Define multi-view boards by passing a named list to new_dock_board(layouts = ...): layouts = list( Analysis = list("block_1", "block_2"), Overview = list("dag_extension") ) Mark an arrangement as initially active with dock_layout(..., active = TRUE): layouts = list( Analysis = list("block_1", "block_2"), Overview = dock_layout("dag_extension", active = TRUE) ) If none is marked, the first one is used. The board's initialise_layout normalises each slot to a dock_layout (storing the arrangement only), and stores the result as a dock_layouts collection. - Breaking changes to the layout API: - Renamed new_dock_board()'s layout parameter to layouts (and the corresponding board field), since boards now hold a dock_layouts collection. - Renamed the board accessor dock_layout(brd) (and setter dock_layout(brd) <-) to active_layout(brd) / active_layout(brd) <-. - Renamed board_views(brd) to board_layouts(brd), and the corresponding setter dock_layouts(brd) <- value to board_layouts(brd) <- value. - dock_layout is now the per-view arrangement type. dock_layout(...) constructs one from a nested list of block / extension IDs and accepts orientation, sizes, and active arguments. The previously-exported "fully-resolved" dock_layout (grid + panels wire shape) is gone — panel content is derived from the board's blocks and extensions on demand at the dockview boundary, so per-view storage shrinks to just the arrangement and panel definitions no longer duplicate across views. - Added panels(..., active = NULL) for tabbed leaves with an explicit open tab, and group(..., sizes = NULL) for nested branches with explicit ratios. dock_layout() itself also accepts sizes = for root-level ratios and orientation = for the top-level split direction. - Removed dock_view(), dock_grid(), is_dock_grid(), and as_dock_grid(). Use dock_layout(...) (or the new panels() / group()) for the per-view spec. - Renamed default_layout() → and kept the name default_layout(). It now returns a dock_layout (arrangement only) — the previously-exported default_grid() (panel-ID form) is gone. - Unexported the high-level resolver create_dock_layout() (renamed internally to resolve_dock_layout()). - Unexported the dock_layouts() constructor. The user-facing input shape for new_dock_board(layouts = ...) is a plain named list — the dock_layouts type is the resolved collection that the board holds internally. is_dock_layouts(), as_dock_layouts(), and validate_dock_layouts() remain exported. - Unexported new_dock_layout(); use dock_layout() instead. - Unexported view_ids() and view_can_crud(). Both were internal helpers exposed by accident; renamed to layout_ids() and views_can_crud() respectively to align with what they operate on. - Wire format for serialised dock_layout decoupled from dockview's internal tree. The persisted shape is a flattened recursive spec: the top object carries orientation, children, optional sizes, and optional focus (the panel with current focus); a child is a bare string (single-panel leaf), a {panels, active?} object (tabbed leaf), or a {children, sizes?} object (nested branch). Sizes are ratios (auto-normalised from dockview's pixel sizes); even splits omit sizes; the default open tab omits active; focus on the first leaf omits focus. focus round-trips the focused group (dockview's activeGroup) by naming a stable panel rather than the regenerated group id. Legacy payloads (with the dockview-shape grid field) load via a shape-discriminated reader. Producer-version routing is tracked in #153 (depends on blockr.core forwarding ... in blockr_deser.list). - Layout conversion API split by boundary. The R object ↔ R list boundary uses coercion: as_dock_layout() coerces a dock_layout (identity), a board (its active layout), or a spec list to a dock_layout; as.list() of a dock_layout returns that spec list. The R object ↔ JSON string boundary uses explicit verbs: layout_to_json() / layout_from_json(). Both as_dock_layout() and layout_from_json() take optional blocks / extensions to resolve bare IDs and validate. layout_panel_ids() / panel_obj_ids() inspect the panel / object IDs a layout references. The dockview wire format and its converters are not part of the public API — only the dock_layout object, our JSON, and the spec list are; as_dock_layout() rejects a dockview grid-shaped list. Changes in version 0.1.0 (2025-12-11) - Initial CRAN submission