---
title: "The link menu"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{The link menu}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include=FALSE}
knitr::opts_chunk$set(eval = FALSE, comment = "#>")
```

`blockr.ui::link_menu_ui()` is a **bidirectional** card-list link
picker. It lives inside the [sidebar primitive](sidebar.html) and
replaces the legacy selectize-style chooser used by `blockr.dock`'s
add-link flow. Each card represents a board *instance* that the new
link can attach to, and the menu computes eligibility against a fixed
*anchor* block (the right-clicked block in the dock flow).

## Two directions, one anchor

`link_menu_ui(id, board, anchor)` renders up to two category-grouped
sections relative to `anchor`. When both render, INPUT FROM appears
above OUTPUT TO so the order matches the natural left-to-right data
flow (sources feed the anchor; the anchor feeds targets):

* **INPUT FROM** (INCOMING, card -> anchor): blocks that can link
  *into* the anchor. Only rendered when the anchor itself has at
  least one free named input port or is variadic. Eligibility =
  every other board block, excluding any that the anchor already
  reaches (would close a cycle).
* **OUTPUT TO** (OUTGOING, anchor -> card): blocks the anchor can
  link *to*. Eligibility = every other board block with at least one
  free named input port or variadic arity, excluding any that
  already reach the anchor (would close a cycle).

If both pools are empty the panel renders an in-place empty-state
instead of returning `NULL`. This means right-clicking a downstream
block with free inputs no longer surfaces the legacy "No inputs are
currently available" warning - the menu opens with its INCOMING
section populated.

## Commit contract

The JS binding publishes `list(source, target, link_id, block_input,
nonce)` on `input$commit`. `source` / `target` are derived from the
clicked card's `data-direction` and the panel's `data-anchor`:

* `data-direction = "outgoing"` -> `source = anchor`, `target = card`
* `data-direction = "incoming"` -> `source = card`,   `target = anchor`

`link_menu_server(id)` strips the internal `nonce` and returns a
`reactive` whose value is the spec. The consumer doesn't have to know
the orientation up front - the spec carries both ends explicitly.

## Multi-link sessions

Single click commits one link, but the menu is designed for repeated
adds: the consumer pushes a typed `pool-update` payload to the open
panel via `session$sendInputMessage()` after each commit, and the
binding's `receiveMessage` handler hides the just-wired card
client-side. Card-expansion state, scroll position, and the search
filter value are preserved. If both pools drain to zero, the consumer
calls `hide_sidebar()` and the panel closes itself.

`link_eligible_pools(board, anchor)` is exported so the consumer
recomputes the post-commit pool against the same eligibility logic
the menu uses for its initial render - one source of truth shared
across both call sites.

## Minimal example

```{r}
library(shiny)
library(blockr.ui)
library(blockr.core)

board <- new_board(
  blocks = as_blocks(list(
    a = new_dataset_block(),
    h = new_head_block(),
    m = new_merge_block()
  ))
)

ui <- fluidPage(
  sidebar_ui("panel", side = "right", width = "420px"),
  actionButton("open", "Connect a"),
  verbatimTextOutput("spec")
)

server <- function(input, output, session) {
  committed <- link_menu_server("menu")

  observeEvent(input$open, {
    show_sidebar(
      "panel",
      title = "Connect a",
      ui = link_menu_ui(session$ns("menu"), board, anchor = "a")
    )
  })

  output$spec <- renderPrint(committed())
}

shinyApp(ui, server)
```

A runnable copy is shipped at:

```{r}
system.file("examples", "link-menu", "app.R", package = "blockr.ui")
```

The bundled example exercises all four anchor cases (source-only,
arity-1 target, arity-N target, variadic) and demonstrates the
live `pool-update` flow.
