# Views and grids

    library(blockr.core)
    library(blockr.dock)

A `dock_board`’s arrangement is two independent things, and it takes two constructor arguments to match:

- `views` — the structure: which panels belong to each view (the global tabs across the top of the app) and the view’s display name. One `dock_view` per view.
- `grids` — the geometry: how each view’s panels are split, nested, tabbed and sized. One `dock_grid` per view.

This vignette covers the grid syntax you use to describe geometry, how the two slots fit together at construction, and the patterns you’ll reach for most.

# The big picture

A board stores structure and geometry as two aligned slots, keyed by view id — read them with `board_views()` and `board_grids()`. They are kept apart on purpose: membership is owned by the update lifecycle (add / remove a block), geometry by the client (you drag a sash, the board records it). A panel is placed in a view when it is in both slots; the two are reconciled only where the placement is read.

There is a third representation you should know the name of but will rarely touch: `dock_layout`. It is dockView’s own form — the `{grid, panels, activeGroup}` payload the front end renders and echoes back. It is the wire type at the client boundary, not something you author with. Our model is `dock_view` + `dock_grid`; `dock_layout` is what crosses to dockView (`?dock_layout`).

The constructor is forgiving about which slot you fill:

You pass | Result
--- | ---
`grids` only | one view per grid (id = the list name), members taken from the grid’s panels
`views` only | structure with no explicit geometry; each view renders a flat default over its members
both | aligned by id: `views` sets membership + names, `grids` sets geometry
neither | a single default view (sidebar + main when there are extensions)

So for a layout with real geometry you usually just pass `grids`; the views come along for free. Reach for `views` when you want a display name that differs from the id, or a view without an explicit arrangement.

# Grid syntax

You describe geometry with `dock_grid()` (and its helpers `panels()` and `group()`). Two rules cover everything:

1. List nesting alternates orientation. `dock_grid()`’s top level lays its children out horizontally; one level of `list()` nesting flips to vertical; another flips back, and so on.
2. Character vectors create tabs. A vector of IDs shares one DockView panel as tabs; a `list()` of IDs is split into separate panels.

## One panel

A single ID fills the whole view:

    new_dock_board(
      blocks = c(a = new_dataset_block()),
      grids = list(Page = dock_grid("a"))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├────────────────────────────┤
    │                            │
    │            a               │
    │                            │
    └────────────────────────────┘

The view is named after the grid’s list name (`Page`), and its members are that grid’s panels (`a`).

## Two panels side by side

Two top-level children → two columns split horizontally:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      grids = list(Page = dock_grid("a", "b"))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├─────────────┬──────────────┤
    │             │              │
    │      a      │      b       │
    │             │              │
    └─────────────┴──────────────┘

## Two panels stacked vertically

Wrap the children in one extra layer of `list()` to introduce a vertical split:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      grids = list(Page = dock_grid(list("a", "b")))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├────────────────────────────┤
    │            a               │
    ├────────────────────────────┤
    │            b               │
    └────────────────────────────┘

The outer level still describes a horizontal split, but with a single child that “split” is one full-width column. The inner `list("a", "b")` is at depth 1, so it splits vertically: `a` stacks on top of `b`.

## Tabs (multiple panels in one)

Use a character vector (not a list) to put several panels in one DockView panel as tabs:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      grids = list(Page = dock_grid(c("a", "b")))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├────────────────────────────┤
    │ ┌──────┐┌──────┐           │
    │ │  a   ││  b   │           │
    │ └──────┘└──────┘           │
    │                            │
    │  (a is shown, b is a tab)  │
    │                            │
    └────────────────────────────┘

`dock_grid(list("a", "b"))` (panels split) and `dock_grid(c("a", "b"))` (panels tabbed) read almost the same but produce very different UIs — the list/vector distinction flips between split a panel and tabify a panel.

# Nested grids

Combine the two rules to build any arrangement.

## Two columns, the right one stacked

    new_dock_board(
      blocks = c(
        a = new_dataset_block(),
        b = new_head_block(),
        c = new_head_block()
      ),
      grids = list(Page = dock_grid("a", list("b", "c")))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├─────────────┬──────────────┤
    │             │      b       │
    │      a      ├──────────────┤
    │             │      c       │
    └─────────────┴──────────────┘

## Two columns, both stacked

    new_dock_board(
      blocks = c(
        a = new_dataset_block(),
        b = new_head_block(),
        c = new_head_block(),
        d = new_head_block()
      ),
      grids = list(Page = dock_grid(list("a", "b"), list("c", "d")))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├─────────────┬──────────────┤
    │      a      │      c       │
    ├─────────────┼──────────────┤
    │      b      │      d       │
    └─────────────┴──────────────┘

## Three rows in one column

A third level of nesting flips back to horizontal inside the vertical stack — a row holding two panels side by side:

    new_dock_board(
      blocks = c(
        a = new_dataset_block(),
        b = new_head_block(),
        c = new_head_block()
      ),
      grids = list(Page = dock_grid(list("a", list("b", "c"))))
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├────────────────────────────┤
    │            a               │
    ├─────────────┬──────────────┤
    │      b      │      c       │
    └─────────────┴──────────────┘

## Sidebar + tabs

A common dashboard shape: a narrow column on the left holding an extension, a tabbed panel on the right with several blocks:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      extensions = new_edit_board_extension(),
      grids = list(
        Page = dock_grid("edit_board", c("a", "b"))
      )
    )

    ┌────────────────────────────┐
    │  Page  [+]                 │
    ├─────────┬──────────────────┤
    │         │ ┌────┐┌────┐     │
    │  edit   │ │ a  ││ b  │     │
    │         │ └────┘└────┘     │
    │         │                  │
    │         │   (tabs)         │
    └─────────┴──────────────────┘

This is also the shape `new_dock_board()` gives you by default when both blocks and extensions are present, and you pass neither slot: extensions on the left, blocks on the right.

# Multiple views (pages)

Give `grids` more than one entry; each list name becomes a separate page in the view-nav dropdown, and each becomes its own view:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      extensions = new_edit_board_extension(),
      grids = list(
        Analysis = dock_grid("a", "b"),
        Editor = dock_grid("edit_board")
      )
    )

`Analysis` view (active by default — the first view wins):

    ┌────────────────────────────┐
    │ Analysis  [+]   ← view nav │
    ├────────────────────────────┤
    │      a       │      b      │
    └────────────────────────────┘

Switching to `Editor` via the dropdown:

    ┌────────────────────────────┐
    │ Editor  [+]                │
    ├────────────────────────────┤
    │                            │
    │           edit             │
    │                            │
    └────────────────────────────┘

Each grid follows exactly the same syntax as the single-view form: character vectors for tabs, nested lists for splits.

## Display names distinct from the id

A grid’s list name is the view id, and it doubles as the display label. When you want a label that differs from the id — or a view with no geometry at all — fill the `views` slot too. A `dock_view()` carries the membership and the display name; align it to the grid by id:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      views = list(
        main = dock_view(c("a", "b"), name = "Analysis")
      ),
      grids = list(
        main = dock_grid("a", "b")
      )
    )

The nav shows Analysis; the stable id `main` is what `active`, renames and updates address.

## Choosing the initially active view

The first view is active by default. To start elsewhere, name it with `new_dock_board(active = )`, using its view id:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      extensions = new_edit_board_extension(),
      grids = list(
        Analysis = dock_grid("a", "b"),
        Editor = dock_grid("edit_board")
      ),
      active = "Editor"
    )

    ┌────────────────────────────┐
    │ Editor  [+]   ← starts here│
    ├────────────────────────────┤
    │           edit             │
    └────────────────────────────┘

Which view is active is a single field on the views collection, keyed by id — it belongs to the collection, not to any one view, so it is always exactly one. Change it at runtime with `active_view(board) <- id`, and read it back with `active_view(board)`; index `board_grids(board)[[id]]` if you then need that view’s geometry.

## Views without geometry

Pass `views` alone (bare member vectors coerce to `dock_view`s) for structure with no explicit arrangement. Each such view renders a flat default over its members until the client arranges it (which the board then records):

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      views = list(
        Analysis = c("a", "b"),
        Empty = character()
      )
    )

An empty view shows the same watermark prompt as an empty board, scoped to that tab.

# Putting it all together

A pot-pourri: multiple views, nested grids, tabbed panels, an extension sidebar, and an explicit active view.

    new_dock_board(
      blocks = c(
        raw = new_dataset_block(),
        cleaned = new_head_block(),
        summary = new_head_block(),
        plot1 = new_scatter_block(),
        plot2 = new_scatter_block()
      ),
      extensions = new_edit_board_extension(),
      links = list(
        new_link("raw", "cleaned", "data"),
        new_link("cleaned", "summary", "data"),
        new_link("cleaned", "plot1", "data"),
        new_link("cleaned", "plot2", "data")
      ),
      grids = list(
        Data = dock_grid(
          "edit_board",
          panels("raw", "cleaned", active = "cleaned"),
          sizes = c(0.25, 0.75)
        ),
        Analysis = dock_grid(
          group("summary", "plot1", sizes = c(0.4, 0.6)),
          "plot2",
          sizes = c(0.55, 0.45)
        ),
        Charts = dock_grid(panels("plot1", "plot2"))
      ),
      active = "Charts"
    )

Three views; `Charts` is active, so the user lands there first.

Charts (active on load): one tabbed panel, `plot1` shown, `plot2` a tab.

    ┌────────────────────────────┐
    │  Charts  [+]               │
    ├────────────────────────────┤
    │ ┌───────┐┌───────┐         │
    │ │ plot1 ││ plot2 │         │
    │ └───────┘└───────┘         │
    │  (plot1 shown, plot2 tab)  │
    └────────────────────────────┘

Data: a slim extension sidebar on the left, a wide right column with `raw` / `cleaned` tabbed. `active = "cleaned"` opens the cleaned tab; `sizes = c(0.25, 0.75)` carves out the narrow sidebar.

    ┌────────────────────────────┐
    │  Data  [+]                 │
    ├──────┬─────────────────────┤
    │      │┌─────┐┌─────────┐   │
    │ edit ││ raw ││ cleaned │   │
    │      │└─────┘└─────────┘   │
    │      │  (cleaned shown)    │
    └──────┴─────────────────────┘
      25%          75%

Analysis: two columns at 55/45. The left is a nested `group()` with a 40/60 vertical split (summary over plot1); the right is `plot2`.

    ┌────────────────────────────┐
    │  Analysis  [+]             │
    ├─────────────┬──────────────┤
    │   summary   │              │   40% / 55%
    ├─────────────┤    plot2     │
    │    plot1    │              │   60% / 45%
    └─────────────┴──────────────┘
         55%             45%

# Custom split ratios and active tabs

The bare list-of-IDs form splits space evenly and opens the first tab. For non-default ratios or a non-default open tab, use the typed helpers:

- `dock_grid(..., sizes = c(...))` — sizes parallel to the root children. They are relative; they need not sum to `1` (they are normalised for you).
- `group(..., sizes = c(...))` — the same, for a nested branch below the root.
- `panels(..., active = "...")` — a tabbed leaf with an explicit open tab.

## A 30/70 split

`sizes` runs parallel to the children in `...`. Two children split horizontally; two sizes make the split uneven:

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      grids = list(
        Main = dock_grid("a", "b", sizes = c(0.3, 0.7))
      )
    )

    ┌────────────────────────────┐
    │  Main  [+]                 │
    ├──────────┬─────────────────┤
    │          │                 │
    │    a     │        b        │
    │          │                 │
    └──────────┴─────────────────┘
       30%             70%

## Stacking with sizes

Same idea, vertical: `orientation = "vertical"` makes the root split run top-to-bottom.

    new_dock_board(
      blocks = c(a = new_dataset_block(), b = new_head_block()),
      grids = list(
        Main = dock_grid("a", "b",
                         orientation = "vertical",
                         sizes = c(0.25, 0.75))
      )
    )

    ┌────────────────────────────┐
    │  Main  [+]                 │
    ├────────────────────────────┤
    │            a               │   25%
    ├────────────────────────────┤
    │                            │
    │            b               │   75%
    │                            │
    └────────────────────────────┘

## Choosing the open tab

`panels()` builds a tabbed leaf. Without `active`, the first ID opens — same as a bare character vector. Pass `active` to open a different tab:

    new_dock_board(
      blocks = c(
        a = new_dataset_block(),
        b = new_head_block(),
        c = new_head_block()
      ),
      grids = list(
        Main = dock_grid(panels("a", "b", "c", active = "b"))
      )
    )

    ┌────────────────────────────┐
    │  Main  [+]                 │
    ├────────────────────────────┤
    │ ┌────┐┌──────┐┌────┐       │
    │ │ a  ││  b   ││ c  │       │
    │ └────┘└──────┘└────┘       │
    │         ↑                  │
    │   b is open by default     │
    └────────────────────────────┘

## Sizes inside a nested branch

`group()` is an inner `list(...)` with its own `sizes` — use it for ratios on any non-root branch:

    new_dock_board(
      blocks = c(
        a = new_dataset_block(),
        b = new_head_block(),
        c = new_head_block()
      ),
      grids = list(
        Main = dock_grid(
          "a",
          group("b", "c", sizes = c(0.6, 0.4)),
          sizes = c(0.3, 0.7)
        )
      )
    )

    ┌────────────────────────────┐
    │  Main  [+]                 │
    ├────────┬───────────────────┤
    │        │      b            │   inner: 60% top
    │        ├───────────────────┤
    │   a    │      c            │   inner: 40% bottom
    │        │                   │
    └────────┴───────────────────┘
       30%          70%

Outer `sizes = c(0.3, 0.7)` controls the root split; inner `group(..., sizes = c(0.6, 0.4))` controls the right column’s stack.

# Cheat-sheet

Geometry (`dock_grid(...)`):

Goal | Syntax
--- | ---
One panel | `dock_grid("a")`
Two side-by-side | `dock_grid("a", "b")`
Two stacked | `dock_grid(list("a", "b"))`
Tabbed panel | `dock_grid(c("a", "b"))`
Sidebar + main | `dock_grid("ext", "main")`
Two columns, both stacked | `dock_grid(list("a", "b"), list("c", "d"))`
Custom split ratio | `dock_grid("a", "b", sizes = c(0.3, 0.7))`
Custom open tab | `dock_grid(panels("a", "b", active = "b"))`
Vertical top-level split | `dock_grid("a", "b", orientation = "vertical")`

Board (`new_dock_board(...)`):

Goal | Syntax
--- | ---
One view with geometry | `grids = list(Page = dock_grid(...))`
Several views | `grids = list(A = dock_grid(...), B = dock_grid(...))`
A display name != id | `views = list(id = dock_view(members, name = "..."))`
Structure, no geometry | `views = list(Page = c("a", "b"))`
Start on view `B` | `new_dock_board(grids = list(A = ..., B = ...), active = "B")`
Empty starter view | `views = list(Page = character())`

# Where to go from here

- `?dock_grid` for the geometry syntax (with `panels()` and `group()`).
- `?dock_view` for the structure type, and `?active_view` for switching the active view at runtime.
- `?dock_layout` for the dockView boundary form, and `as_dock_grid()` / `as_dock_layout()` for the casts across it.