Changelog
Source:NEWS.md
blockr.dock (development version)
New exports for downstream consumers that translate between bare block / extension ids and dock’s panel-id scheme: the class predicates
is_dock_panel_id(),is_block_panel_id()andis_ext_panel_id(), andas_panel_ref(), which resolves a bare id (or an already wire-prefixed one) to ablk()/ext()ref against a board’s block / extension id sets, block-first with a hard error on a cross-namespace clash (#374).“Show code” on a deferred board (
background_construction_delay = Inf) no longer permanently blanks the cards of a view first visited after it. The exporter marks every block required so the script covers the whole board, and the dock’s card ledger – read off that samerequiredchannel – mistook the export’s demand for a built card, so the first visit to the view skipped the build and left its panels empty. The dock now reads its build ledger off core’svisiblechannel, which blockr.core makes a logical three-state axis (NAnever built /FALSEbuilt off screen /TRUEpainted), so arequiredwrite can no longer masquerade as a built card (#377).New
edit_inputs_action: a per-block sidebar listing every incoming link as an ordered list, so a block’s inputs can be managed together rather than one edge at a time. For a variadic block each row is a positional slot - drag to reorder (each row’s source and name move together to its new slot vialinks$mod, so...argscomes out in the new order), rename inline (positional <-> named), or remove; an “Add input” block picker below the list appends another positional slot when a source is chosen. A finite block’s rows are its declared ports, each a block-browser selectize that picks, redirects or disconnects that port’s source. Reorder keeps every link’s id, so there is no id churn and no new core verb. Surfaces such as blockr.dag can trigger it for a selected node.A panel id in a
dock_grid()or view that resolves to no block or extension on the board now aborts atnew_dock_board()rather than being dropped in silence. Such an id – a typo, or an extension’s old class-derived name (extensions are keyed by their mount name since the ids became container-owned) – used to empty the view, booting the board blank with no error, warning, or log entry. Restoring a saved board is unchanged: a member or grid leaf whose block is genuinely gone still self-heals (#375).Block card sections (inputs / outputs / control) are styled from the stylesheet, keyed on each panel’s stable
data-value, rather than rebuilt per card withhtmltools::tagQuery()at UI-build time. The markup renders identically but the cards build about a third faster – a cost that scales with the number of blocks on the active view, so it cuts noticeably into the initial app render (#214).Closing a dock panel no longer aborts the board update when the same tab is closed twice in quick succession. The manual-close plugin leaves a tab’s
xin place until the server round-trip removes it, so a rapid double click re-fires the close for a panel that is already gone; that stale removal is now dropped rather than failing view-membership validation (#362).Blocks on a dock group’s background tabs render again. dockView mounts a group’s non-front tabs lazily, so a block card’s
move-elementcould arrive before its panel existed and be dropped, stranding the card in the offcanvas; and a bare tab switch did not mark the newly-fronted block visible. Both left non-front tabs blank. A dropped move is now stashed and replayed when dockView reports the panel active (dockViewR’sdockview:active-panelevent), and the visible mark follows the client’s live active-panel signal, so selecting a tab paints its block (#361).Dock extensions can now carry structured, model-facing metadata: the
descriptionargument ofnew_dock_extension()accepts anew_ext_meta()object documenting each externally controllable variable (with an optional type schema and worked examples) and how to drive the extension, read back withext_meta()and the per-componentext_desc(),ext_args(),ext_examples()andext_guidance()accessors. A bare string keeps working as the free-text summary; the earlierextension_description()accessor is deprecated in favour ofext_desc()(#359).The navbar busy spinner keeps turning when the browser reports
prefers-reduced-motion: reduce, at a slower 1.6s turn instead of 0.7s. It previously dropped the animation entirely, which left a fully styled ring frozen in the navbar and reading as a hung session. Windows maps its “Animation effects: off” setting (a common managed / VDI default) onto that preference, so on those machines the spinner never moved.Idle, the navbar busy spinner is now a faint, closed ring rather than a gapped three-quarter circle that read as an oversized “C” wherever it sits. The darker arc that signals motion is painted on only while the board is busy; at rest the ring is a single muted colour and recedes into the navbar.
Views (pages) can now be reordered from the nav dropdown: each item carries up / down controls beside its rename and remove actions. Order is board content, so the move travels through the update lifecycle as a new
views$orderdelta (a total permutation of the view ids) and survives save / restore; the server applies it and pushes the settled order back to the nav (#351).A served board now honours a
?view=<id>URL query parameter: it opens on the named view instead of the board’s default active view (matching by stable view id, the immutable handle). An absent or unknown id falls back to the default, so existing links are unaffected (#323).New
edit_link_action: a sidebar editor for a single existing link. It can rename the link’s input (turning an unnamed positional slot into a named one, or changing an existing name), switch the input slot on a finite target, and redirect the link’s source or target - under the same acyclicity, eligibility and input-name uniqueness rules the “Connect …” menu enforces, with the block pickers rendered like the add-panel picker. The link keeps its id across the edit. Surfaces such as blockr.dag can trigger it for a selected edge.The “Connect …” sidebar and the block browser’s append / prepend forms now let a link into a variadic block carry a name: an optional “Input name” field appears for variadic targets (leave it blank for a positional slot). A supplied name must be unique among the target’s inputs, per blockr.core’s name-or-position model.
resolve_free_input()now resolves a variadic link target to an empty (positional) slot instead of a generated"1","2", … name, aligning the “Connect …” sidebar with blockr.core’s name-or-position variadic model (and with blockr.ui’s link menu). An integer input name is treated by core as a named argument, and downstream consumers such as blockr.io’s Download/Export blocks name files and Excel sheets by the link input verbatim, which surfaced as1.csv/2.csvexports.Off-screen views’ block cards – built on first visit rather than at startup (#272) – are now built with the plugin set passed to
serve(), not the board default (#331).ensure_block_ui()re-derived the edit / control UI fromboard_plugins(), which omits any customctrl_block, so a block’s control toggle (the AI “sparkle” when the served plugin isblockr.ai::ai_ctrl_block) showed on the initially-active view but was absent on every other view’s cards. The served plugin set now ridesactive_dockalongside the visibility channel, so the deferred build – the view switch and the add-panel path – sees the servedctrl_block.Locked mode is now a server-side trust boundary, not just UI hiding (#127, #135, #136).
is_dock_locked()reads blockr.core’sblockr.lockedoption (renamed fromblockr.dock_is_locked), so one flag drives both core’s update / option gate and dock’s UI hides; a deployment setting the old option must switch toblockr.locked. Hiding a block’s input section – and every block on a locked board – now drives core’s per-blockfrozenchannel: the block’s expression is pinned and its inputs are no longer consumed, so a forgedShiny.setInputValuebehind the hidden or read-only controls reaches nothing (upstream data still flows, and showing the section again thaws it). View switching on a locked board is driven client-side, as core’s gate rejects the active-view update; the board-options accordion (#135) and the empty view’s “Add panel” prompt (#136) are dropped when locked.At startup the board now builds only the active view’s dockView; off-screen views’ docks are created on first visit rather than all up front (#304), mirroring the card deferral below. Building every view’s dockView left the active group pointing at an off-screen view during the startup restore burst, transiently dropping the visible view’s blocks and starving first paint.
reconcile_views()now builds only the active view’s dock and defers the rest; a view without a live dock contributes its board-stored grid toview_data()rather than blocking it.A view’s
visible-axis mark – the client-confirmed paint blockr.core’s background-construction gate waits for – now rides the active view’s live dockView layout echo, the same signal therequiredaxis already tracks, rather than a one-shot snapshot of the grid’s stored active tab (#304, #328). A group’s front tab is client-owned (the last-added tab wins) and can disagree with the grid, so marking the snapshot left the on-screen block suspended and blank on first load while a hidden back tab was marked painted. Sourcing the mark from the painted layout follows whichever tab dockView actually fronts and re-marks on a tab switch, so the on-screen block renders.At startup the board now builds only the active view’s block cards; off-screen views’ cards are built on first visit rather than all up front (#272).
board_ui()rendered an edit card for every block across every view into the static offcanvas mount, so first paint scaled with the total block count, not with what is on screen (~20s ofrenderTagson a 99-block, 12-view board). It now renders only the active view’s cards and defers the rest;switch_active_view()(and the active-dock panel-op path) inserts a view’s cards the first time it is shown, and core’srequiredchannel doubles as the build ledger so a revisit never doubles a card.Block visibility is coordinated with blockr.core (>= 0.1.4) over its two-channel
visibilityinterface: a per-blockrequiredchannel the dock drives (TRUEon screen,FALSEbuilt but off screen) and avisiblechannel it writes with a view id once the client has painted that view. This gives blockr.core an explicit “the initial view is painted” signal to gate its background block-server construction on, instead of inferring readiness from result-quiescence.
blockr.dock 0.1.2
CRAN release: 2026-07-13
- The block-browser, link-menu, stack-menu and sidebar UI components are now bundled directly into blockr.dock instead of imported from the (non-CRAN) blockr.ui package, so blockr.dock installs from CRAN with no remote dependencies.
- Panel operations are now first-class verbs in the
views$modupdate payload (add/rm/move/select), with panels named by the new typed referencesblk()/ext()instead of wire-id prefixes. Breaking: the old set-replace membership form is retired. - The
views$modgrammar gains aresizeverb –resize = list(blk("a", size = 0.3))sets a panel’s group size along its splitview axis (a ratio in(0, 1)), delivered through dockViewR’sset_sizeproxy. Likemove/selectit is client-owned geometry: pure delivery, captured by the grid mirror, no board write (#320). - A saved dock layout now round-trips exactly through an export / import cycle – the reloaded board re-exports the same tab groups, active tabs and sizes. Previously a transient frame the client reports while a restore is still settling could be captured into the stored geometry, flattening a tab group to separate leaves or emptying a view (#343).
-
Breaking: an extension’s id is now owned by its container (mirroring blocks), serving as its single identity everywhere – the wire panel id, DOM handle, module namespace and
ext()target. -
Breaking (extension authors): an extension’s live result now reaches actions, callbacks and peer extensions as an
extensionsbundle keyed by extension id; the new exportedextension_ids()resolves a class to the runtime id(s) the container assigned. - A dock extension opens a block’s panel through the
viewsgrammar (composeactive+select) rather than the retired livedockhandle; the exportedshow_panel()is removed. - The block status badge is now one exported helper,
block_status_badge(), shared with blockr.dag so the dock card icon and the DAG node badge render identically; the dock dot now also reflects render-phase errors. - Renaming a block no longer crashes a board where that block is absent from some view – placed in only one view, or parked in the offcanvas with no panel.
- A board’s per-view layout splits into two independent slots: a server-authoritative
dock_viewsstructure collection (read withboard_views()) and a separateNULL-validdock_gridsgeometry slot (board_grids()), each grid validated as a subset of its view’s membership. - Block eval status is now a first-class panel affordance: a
waitingorunsetblock shows a dedicated placeholder instead of a generic warning, and a colour-coded dot in the card header markswaiting,unsetandfailedblocks at a glance. - Per-block condition UI (warnings, messages, errors) is updated surgically by the stable condition id, so a persistent warning no longer flashes on every re-evaluation.
- The intrusive page-wide busy pulse is replaced by a small, unobtrusive spinner in the navbar, just left of the board-options gear. It always occupies its slot as a dim, static ring and, while the session does real block evaluation, rises to full contrast and spins – scoped (as the pulse was) to a genuinely recomputing output in the visible view, so startup and block evaluation liven it while a bare panel switch does not. Because it is always present it never shifts its neighbours and never blinks in or out; a configurable minimum-busy delay (
blockr.spinner_delay_ms, default 200 ms;0to disable) holds the transition to the busy state so a sub-threshold flush no longer flashes it (#345, #355, #360). - The “Edit board” extension no longer churns on a board re-emit – it re-syncs its staged working copy only when links or stacks actually change, stops flickering the manage-links cell inputs, and overlays half-finished staged edits instead of clobbering them.
- Dock extensions now receive
view_data, the live all-views layout reactive that serialization also reads (NULLuntil every view has reported once, soreq()it); the active-viewdockhandle is retired from the extension surface. - Multi-view boards no longer emit a burst of redundant board updates at startup; the fold that mirrored every dockview focus tick back into
board_layoutsis removed, and the live layout is read on demand instead. - The dock no longer loops or tears its panels down on a slow client: a view’s arrangement is now client-owned and flows dock -> board only, removing the reconcile push whose echo could restore an impoverished layout.
- Live panel rearrangements are no longer lost on save –
view_data(), the live layout serialization reads, no longer stays stuck atNULLfor the whole session. - Adding a block before the dock view has finished initialising no longer throws
argument is of length zero. - The block, link and stack action handlers now mount the corresponding
blockr.uimenu modules (card-list pickers with search and inline editing), replacing every per-field Shiny input; the link menu adds bidirectional source / target picking and the stack menu an inline colour picker. The block browsers are pre-rendered and toggled rather than rebuilt on each open, and the dock-side*_sidebar_body()bodies and spec helpers are removed. - Layout deserialization now routes on the producing blockr.dock version (read off
constructor$version) rather than sniffing the payload shape, keeping shape discrimination only as the fallback for version-less payloads. - The dock “manager” object is gone:
apply_board_update.dock_board()is a pure reducer and all live view surgery runs in one reconcile pass driven by the committed board.augment_board_update.dock_board()is now idempotent, fixing a view-add loop. - Views now carry a stable, immutable id decoupled from their editable display name (mirroring blocks):
dock_layoutsis keyed by id, the name is read and written viaview_name()/view_name<-(), and theviewsdelta gains arenameslot. Innew_dock_board(layouts = list(...))the list name is the view’s id, so producers that addressed views by name (e.g. blockr.assistant) must switch to ids. - A
dock_layoutnow hasformat()/print()methods that render its arrangement as an indented tree; panel ids print without their wire prefixes unlessbare = FALSE. - The
viewsslot of theboard_updatepayload is a structured delta (add/mod/rm/active) instead of a wholesaledock_layoutsreplacement, composing atomically withblocks/links/stacks. UI-driven layout changes (panel close / add, drag-resize, view CRUD) route through this lifecycle and are debounced (250 ms), and removing a block drops its panel surgically rather than clearing the active view. Requiresblockr.core (>= 0.1.3).
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