Changelog
Source:NEWS.md
blockr.dock (development version)
The add and append block browsers are each pre-rendered once into a dedicated sidebar (
add_block_sidebar/append_block_sidebar) and merely toggled open, instead of being rebuilt on every open (the append rebuild was ~500 ms with a large registry, dominated by instantiating every block to compute the linkable filter). The add / append / prepend handlers are thin adapters overblockr.ui::block_browser_server(), which now returns ready-to-applyblocks/linksobjects (target port resolved menu-side); the dock-sidebuild_block_from_spec(),valid_block_id()andvalid_link_id()helpers are removed. Requires the matching blockr.ui (block_browser_server()ready-objects contract).Adding a block before the dock view has finished initialising no longer throws
argument is of length zero. While the dock is uninitialised its layout isNULL;determine_active_views()now treats that as an empty dock, so the block’s panel is placed freely instead of being stranded in the offcanvas.add_link_action()now mounts theblockr.uilink-menu module and is bidirectional: right-clicking a downstream block now lets you pick an upstream source, not just a target. The handler passes the board and anchor as reactives, so the menu owns link-id validation and keeps a pinned menu in sync with the board itself - removing a link frees a target whose card reappears live, and removing a block drops its card, both without a re-render. The per-field link inputs (create_link/add_link_input/add_link_id/add_link_confirm) and the dock-sidevalid_link_idvalidator are gone in favour of a single committed-spec reactive fromblockr.ui::link_menu_server().link_sidebar_body()is removed (no in-tree callers remain; out-of-tree consumers migrate toblockr.ui::link_menu_ui()/link_menu_server()).The add / edit stack action handlers now mount the
blockr.uistack-menu module: a multi-select card-list block picker with search, per-category icons, an inline hue / lightness colour picker, and a panel-level form for the stack name / colour / id. The per-field Shiny inputs (stack_id/stack_name/stack_color/stack_block_selection/stack_confirmand theedit_stack_*equivalents) are gone in favour of a single committed-stack reactive returned byblockr.ui::stack_menu_server().stack_sidebar_body()is removed (no in-tree callers remain; out-of-tree consumers migrate toblockr.ui::stack_menu_ui()). Drops theshinyWidgets::colorPickrfloating popover; the new colour picker renders inline in the sidebar. The handlers now pass the board as a reactive, so a pinned stack menu stays in sync with board changes (removing a block drops its card live); spec validation moved intoblockr.ui(the dock-sidevalid_stack_*validators are gone). The menu buildsdock_stackobjects itself viablockr.dock::new_dock_stack()(gated behindpkg_avail("blockr.dock"), a Suggests back-edge), so the handlers apply the committedstacksobject as-is.Layout deserialization now routes on the producing blockr.dock version rather than sniffing the payload shape.
blockr_deser.dock_board()reads the producer version off the savedconstructor$versionand threads it down (throughblockr.core’s...-forwardingblockr_deser.list()) toblockr_deser.dock_layout(), which picks the wire-format reader from a version-keyed registry. Shape discrimination (legacy dockviewgridvs. flattened spec) stays as the fallback for version-less payloads — very old saves or hand-crafted JSON (#153).The dock “manager” object is gone.
apply_board_update.dock_board()is now a pure reducer overboard_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 throughdot_args. Also makesaugment_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 runtimedock_mgr$docksregistry) are keyed by id; the name is an attribute read / written viaview_name()/view_name<-()(withview_names()for a whole collection), andactive_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 theviewsdelta gains arenameslot. Naming constraints relax to display concerns (non-empty, unique label). Serialization round-trips ids. Innew_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 viadock_layout(name = )and falls back to a label derived from the id when unset. Theviewsdelta addresses existing views by id (the only stable handle) —mod/rm/activecarry ids;addsupplies a display name and mints the id. Producers that addressed views by name (e.g.blockr.assistant) must switch to ids (#166).dock_layoutobjects gainformat()/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 theirblock_panel-/ext_panel-prefixes by default; passbare = FALSEfor the canonical IDs (#161).The add / append / prepend block action handlers now mount the
blockr.uiblock-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 (<mode>_block_selection/<mode>_block_id/<mode>_block_name/<mode>_link_id/<mode>_block_input/<mode>_block_confirm) are gone in favour of a single committed-block reactive returned byblockr.ui::block_browser_server().block_sidebar_body()is removed (nothing in-tree calls it; link / stack flows uselink_sidebar_body()/stack_sidebar_body()unchanged). Requiresblockr.uiwith the block-browser module.
blockr.dock 0.1.2
The
viewsslot in theboard_updatepayload is now a structured delta (add/mod/rm/active) instead of a wholesaledock_layoutsreplacement. Mentioned views are touched, omitted views keep their current state, and the four sub-slots compose atomically withblocks/links/stacksin the same lifecycle tick. See?dock_board_update_lifecyclefor 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 theupdate()lifecycle path;rm_blocks.dock_board()does the same surgically (rather than nuking the active layout) for direct callers likeclear_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 throughupdate(list(views = ...))and applied viavalidate_board_update.dock_board()andapply_board_update.dock_board(). Writes are debounced (250 ms) so drag-resize doesn’t thrash. Requiresblockr.core (>= 0.1.3)for the update lifecycle generics.
blockr.dock 0.1.1
CRAN release: 2026-04-29
Added prepend block action.
-
Define multi-view boards by passing a named list to
new_dock_board(layouts = ...):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_layoutnormalises each slot to adock_layout(storing the arrangement only), and stores the result as adock_layoutscollection. -
Breaking changes to the layout API:
- Renamed
new_dock_board()’slayoutparameter tolayouts(and the corresponding board field), since boards now hold adock_layoutscollection. - Renamed the board accessor
dock_layout(brd)(and setterdock_layout(brd) <-) toactive_layout(brd)/active_layout(brd) <-. - Renamed
board_views(brd)toboard_layouts(brd), and the corresponding setterdock_layouts(brd) <- valuetoboard_layouts(brd) <- value. -
dock_layoutis now the per-view arrangement type.dock_layout(...)constructs one from a nested list of block / extension IDs and acceptsorientation,sizes, andactivearguments. 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, andgroup(..., sizes = NULL)for nested branches with explicit ratios.dock_layout()itself also acceptssizes =for root-level ratios andorientation =for the top-level split direction. - Removed
dock_view(),dock_grid(),is_dock_grid(), andas_dock_grid(). Usedock_layout(...)(or the newpanels()/group()) for the per-view spec. - Renamed
default_layout()→ and kept the namedefault_layout(). It now returns adock_layout(arrangement only) — the previously-exporteddefault_grid()(panel-ID form) is gone. - Unexported the high-level resolver
create_dock_layout()(renamed internally toresolve_dock_layout()). - Unexported the
dock_layouts()constructor. The user-facing input shape fornew_dock_board(layouts = ...)is a plain named list — thedock_layoutstype is the resolved collection that the board holds internally.is_dock_layouts(),as_dock_layouts(), andvalidate_dock_layouts()remain exported. - Unexported
new_dock_layout(); usedock_layout()instead. - Unexported
view_ids()andview_can_crud(). Both were internal helpers exposed by accident; renamed tolayout_ids()andviews_can_crud()respectively to align with what they operate on. - Wire format for serialised
dock_layoutdecoupled from dockview’s internal tree. The persisted shape is a flattened recursive spec: the top object carriesorientation,children, optionalsizes, and optionalfocus(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 omitsizes; the default open tab omitsactive; focus on the first leaf omitsfocus.focusround-trips the focused group (dockview’sactiveGroup) by naming a stable panel rather than the regenerated group id. Legacy payloads (with the dockview-shapegridfield) load via a shape-discriminated reader. Producer-version routing is tracked in #153 (depends on blockr.core forwarding...inblockr_deser.list). - Layout conversion API split by boundary. The R object ↔︎ R list boundary uses coercion:
as_dock_layout()coerces adock_layout(identity), aboard(its active layout), or a spec list to adock_layout;as.list()of adock_layoutreturns that spec list. The R object ↔︎ JSON string boundary uses explicit verbs:layout_to_json()/layout_from_json(). Bothas_dock_layout(<list>)andlayout_from_json()take optionalblocks/extensionsto 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 thedock_layoutobject, our JSON, and the spec list are;as_dock_layout()rejects a dockview grid-shaped list.
- Renamed