Skip to content

Pin Ruby, TypeScript and Rust at their masters, re-record their MCP sessions, list the APIs Ruby's send-keys uses - #247

Merged
tony merged 31 commits into
mainfrom
fix/ruby-core-showcase-refresh-20261103
Nov 4, 2026
Merged

tony merged 31 commits into
mainfrom
fix/ruby-core-showcase-refresh-20261103

Conversation

@tony

@tony tony commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Why

libtmux-ruby's own docs build failed in libtmux-ruby#90 (step "Build the selected Ruby documentation tree"): core-apis: ruby/send-keys names LibTmux::Server#open_control, but the program never uses open_control. examples/api/capture.rb stopped attaching a control client when the library began requiring tmux 3.7 for one, and now polls pane.capture, but core-apis.json still listed the five control-mode symbols it used to call. Docs CI passed because it reads the example cache, which is bound to the revision of the Ruby API model, and that pin was a week behind libtmux-ruby master. The port's build reads the live file.

A guard for this already exists. apisUsed is run over every cached showcase program by core-apis.test.ts, and programProblems by core-capabilities.test.ts and portCapabilities. The incident was a stale input, not a missing check: with only the model and cache refreshed, core-apis.test.ts fails on exactly the message above.

What

The Ruby pin moves to libtmux-ruby 20744401e (master). The model, the example cache and everything bound to the pin are regenerated with the repository's generators from the committed bytes of a fresh read-only clone. The refresh brings unrelated API changes, kept as generated:

  • The four gems move from 0.1.0.alpha.1 to 0.1.1.
  • New symbols: LibTmux::Support, Support::Versions, tmux_version and supports_control? on Server and Async::Server, and require_supported and server_version on both support modules. None is removed.
  • version_timeout joins Server.new, Server.open and Async::Server.open; environment joins Endpoint.new and MCP::CLI.run; PaneSnapshot#pid may be nil; Pane#break_out documents ProtocolError; the Endpoint and Server docs gain text.
  • Complete programs now attach to 55 API pages, up from 19.
  • Pane#break_out now also sends list-panes when break-pane prints no window ID, and rename-window after its repaired tmux 3.7 break-pane.
  • The "Discussed in" index gains the mentions of the guides master ships.

ruby/send-keys lists what the program calls. The five control-mode symbols (Server#open_control, ControlConnection#exchange, ControlConnection#subscribe, ControlSubscription#next, ControlEvent#data) are gone. The list is Server, Server.start, Server#new_session, CreationReceipt#pane, Pane#send_text, Pane#send_keys, Pane#capture and CommandResult#stdout, each resolved against the refreshed model. The capability's title is "Send keys" and the data holds no summary, so nothing on the Core page claimed the program watches through control mode, and core-capabilities.json and core-home.json needed no change. The Core home still says Ruby can watch via control mode, which stays true: LibTmux::ControlConnection is in the model.

The guard now covers its own incident. core-apis.test.ts takes the ports to check from the capability table instead of a list of three, and pins the polling program as a frozen fixture: the corrected list resolves against it, and a list that still names any of the five control-mode symbols is refused with that symbol's name.

What the refresh forced (each its own commit):

  • tmux-usage: the Ruby rules cite lines of the old source. 219 citations were carried to where the same text sits at the new revision, and the eight dispatcher chains gain the run to dispatch hop that Server#run now has.
  • API example receipt: 36 new Ruby pages and 19 rebound ones, with native runs (see Verification).
  • Extractor: RBS allows a name after a positional type ((::String value)), which the new Support methods use. rbsSignature kept ::String value as the type; it now reads the name, and a union still ends in its last member.
  • Navigation: LibTmux::Support is recorded as unsettled in nav-config.ts.
  • manifest-packages.json and the destination evidence move to 0.1.1; the evidence is re-verified whole because its tests hold every record to one check date.

Decision for the reviewer

The Ruby cell job now carries if: needs.plan.outputs.run_cells == 'true', and the old workflowStep refused any job-level if, so ruby/create-windows and ruby/send-keys could no longer be shown as run by CI. The commit "Accept a job that waits on its workflow's plan" added plannedRun, and the review follow-up below narrowed it. It now allows one form only. The capability entry names the flag (ci.planOutput, run_cells for both Ruby entries). The job's if must be that flag from a job it needs, compared with 'true'. That plan job must have no needs, if or tolerated failure, and must publish the flag as ${{ steps.<id>.outputs.<flag> }} of a step that exists and carries no condition. Every other condition is still refused, with a test for each. The port's ci-plan selects cells for every push and pull request; this check cannot read that. If you would rather keep the rule strict, drop that commit and the two Ruby capabilities leave the showcase.

For the wave tooling (outside this repository)

The r12 packet's check-core-capabilities-heads.py should also run apisUsed over the programs it reads at each port's head, with the same lists as core-apis.json, so a port-side change that drops a listed symbol is caught before the port's build is. Port the function as it is: comments and strings are blanked first, a method counts only in its call forms (.name, ::name, &:name), and a type or constant counts as a bare word.

Verification

  • Focused suites (core-apis, core-capabilities, core-home, port-landing-page, product-copy, example-sources, ruby-api-examples) passed 182 tests on main. With only the model and cache refreshed they failed 24, among them core-apis on ruby/send-keys names LibTmux::Server#open_control, but the program never uses open_control. On this branch they pass.
  • The whole site unit suite passes (229 files, 8003 tests; the five assembled-tree files are skipped because nothing is assembled) and so does the api-model suite (59 files, 1299 tests). The new extractor test fails without the extractor change, and the two Ruby workflow-step tests failed until plannedRun was added.
  • gen-tmux-usage --check --source --ports ruby regenerates Ruby from the checkout and matches; gen-tmux-usage.negative.mjs, check-example-blocks.negative.mjs, gen-product-copy.negative.mjs and gen-workspace-showcase.negative.mjs pass; check-nav, check-api-links, and the --check modes of the model, example-source, mentions, workspace-showcase, workspace-entities, product-copy, tmux-catalog and shell-ports generators pass. astro check reports no errors and oxlint --type-aware no warnings in the changed files.
  • Native runs with scripts/check-example-prose.py --example api --port ruby, one per program on tmux 3.3a and 3.7c, restoring the gems from rubygems.org with Ruby 4.0.7: 15 programs passed on both releases, and the two control-mode programs passed on 3.7c and stop on 3.3a with control connections require tmux 3.7 or newer.
  • Source checkout: the Ruby generators and tests ran against a read-only clone of libtmux-ruby master, set through LIBTMUX_DOCS_CHECKOUT_RUBY.

Not done or failing:

  • workspace_apply.rb fails on both tmux releases with LibTmux::Workspace::ConfigError: workspace file could not be read. It is new at this pin and loads ../workspace.yaml, which no page displays, so the four pages that show it are not self-contained. Their receipt entries say "Not verified" and list no tmux release. This needs a fix in libtmux-ruby; I made no port change.
  • The intros of the two control-mode pages say "tmux 3.3a or newer" although the library refuses a control client below 3.7. Also a libtmux-ruby fix.
  • gen-manifest-packages --check still reports Elixir, as on main: its committed entry names an older revision than its model.
  • Not run: the site build, the publication audit, check-clipboard, and the browser checks. The full destination run also moved registry versions for other ports, recorded as found.
  • The Rust and Go Core caches were not refreshed or compared with their port heads.

Review follow-ups

An independent review returned changes needed. The findings and what this branch does about each:

The Ruby source-guide cache was stale (high). site/src/data/port-guides/ruby.json still recorded libtmux-ruby 9b1545562 while the model recorded 20744401e. stage-port-docs.mjs refuses that (ruby: guide source ... differs from integrated model ...) and build-site.sh runs it with --cached on every build, so every port's publisher build and this PR's test job failed, at "Stage integrated source guides". The cache is regenerated with --port ruby --from-source --refresh-cache from a clean clone at 20744401e. From the committed bytes, --from-source with the CI checkouts, --cached --check, --cached (a plain build and with LIBTMUX_DOCS_PORT=rs) and gen-mentions --check pass.

The Ruby MCP caches were stale (high). The protocol snapshot, the Ruby rows of the tool matrix and the recorded transcript are regenerated at 20744401e with gen-mcp-protocol.mjs --port ruby, gen-mcp-tools.mjs --port ruby and gen-mcp-transcript.mjs --port ruby, each followed by its --check. The other ports' matrix rows are unchanged.

The Rust and TypeScript replays fell back to "Requests only". After the r12 wave the recorded contracts of both transcripts differed from the tool schemas at each port's master: run_shell_command's output schema for Rust, and run_shell_command's and list_windows's for TypeScript. All input schemas matched. Both are recorded again at libtmux-rs 6942c1e60 and libtmux-ts 073b9d7de, each built and run by the repository's own generators from a clean read-only clone, in a private tmux sandbox. Every contract in each transcript now equals the snapshot's and the matrix row's for that tool, for all three of Ruby, Rust and TypeScript. TypeScript's master also registers five program tools (start_program, read_program, run_program, wait_for_exit, stop_program) that the committed snapshot predates, so the matrix lists them. The first Rust recording caught the split pane's command as tmux, the moment before its shell started; three repeats gave sh, as the previous recording and TypeScript's show, and the committed file is one of those repeats.

plannedRun accepted too much (medium). A cell gated on run_development, a plan output hard-wired to 'false' or reading a step that does not exist, needs: [other] # plan, a plan that needs a conditional job, and a plan whose only step is if: false all counted as run by CI. See the rule under "Decision for the reviewer". Comments are stripped before needs is read. A job-level if whose comment reads continue-on-error: false is refused too; the old check let it through. Each counterexample has a test, needs: other has one that pins the membership check, and the real Ruby compatibility.yml at 20744401e still passes for both entries and is refused once the entry names no flag. Sixteen of the new tests fail against the previous predicate.

A hard-coded count in ruby-api-examples.test.ts (low). toBe(18) is now a comparison, as sets, of the receipt's source files with the files the model's example blocks link, so a mismatch names the file.

Destination-evidence churn (low). Left as is. Keeping only the four RubyGems records and main's other records fails three age and future-date tests in destination-evidence.test.ts, which hold every record to the file's one check date, so the file cannot be split cleanly. The churn is the checkedAt bump on every record plus the versions the registries of other ports now report (Maven Central, crates.io, hex.pm, LuaRocks, the Swift Package Index).

Verification of this follow-up (it supersedes the counts above):

  • The full site unit suite passes with workers capped, run through the heavy-job wrapper: 256 files and 8242 tests passed; the five assembled-tree files and 351 tests are skipped because nothing is assembled. The seven focused suites pass, and so does the api-model suite (60 files, 1305 tests, one skipped).
  • astro check reports no errors or warnings, and oxlint --type-aware is clean on the changed files.
  • The source checks of the publication audit that run offline pass: example sources, mention index, workspace entities, navigation, workspace showcase, product copy, tmux library usage for Ruby (regenerated and compared), shell ports, tmux catalog, destination evidence, API destination evidence, source links, example blocks, example indentation and API links in prose. gen-manifest-packages --check still reports Elixir, as on main.
  • Not run: gen-mcp-tools.mjs --check and gen-mcp-protocol.mjs --check without --port, because this machine's Python, Julia and Go checkouts sit at other heads than their committed snapshots, which fails the same way on main. The --port checks for Ruby, Rust and TypeScript pass. Also not run: the site build, the full publication audit and the browser checks.

Port follow-ups (libtmux-ruby)

Two problems in the Ruby API example pages need a change in libtmux-ruby; this branch makes none. It holds the eleven pages that show the affected programs back (see "Third review follow-ups"), so no page states the wrong text, and they come back when these fixes land. The native runs record the problems.

Four reference pages tell readers to save a program that needs a file they were not shown. Workspace.load, Workspace#plan, Workspace::Plan#steps and Workspace::Plan#apply show examples/api/workspace_apply.rb after a Gemfile. The program calls LibTmux::Workspace.load(File.expand_path("../workspace.yaml", __dir__)), which is examples/workspace.yaml, and no page displays it. Saved alone, the program stops with LibTmux::Workspace::ConfigError: workspace file could not be read. Fix in examples/api/workspace_apply.rb: write the document to a temporary directory inside the program and load it from there, which keeps Workspace.load in use and the expected output (planned steps: 11, applied: true, sessions: work) unchanged.

require "libtmux"
require "libtmux/workspace"
require "tmpdir"

LibTmux::Server.start do |server|
  Dir.mktmpdir do |directory|
    path = File.join(directory, "workspace.yaml")
    File.write(path, <<~'YAML')
      session_name: work
      environment:
        PROJECT_MODE: development
      windows:
        - window_name: editor
          window_index: 1
          layout: tiled
          panes:
            - shell_command: printf 'editor ready\n'
            - {}
    YAML
    workspace = LibTmux::Workspace.load(path)
    plan = workspace.plan(snapshot: server.snapshot)
    puts "planned steps: #{plan.steps.length}"
    result = plan.apply(server: server)
    puts "applied: #{result.success?}"
    puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"
  end
end

Keep the begin, docs:begin main and rescue frame the file has now. The raw heredoc (<<~'YAML') keeps the \n in printf literal. Saved alone in an empty directory, the file at the pin stops with the ConfigError above, and this version prints the expected three lines (checked on tmux 3.8a, not on the receipt's 3.3a and 3.7c). After the port moves its pin, refresh this repository's example receipt so the four entries are verified on both tmux releases.

Two control-mode programs say "tmux 3.3a or newer" although the library needs 3.7. examples/api/control.rb and examples/api/async_control.rb show on seven pages (Server#open_control, ControlConnection#exchange, ControlConnection#subscribe, ControlSubscription#next, Async::Server#open_control, Async::ControlConnection#events, Async::ControlSubscription#next). A control client below tmux 3.7 is refused with control connections require tmux 3.7 or newer (gems/libtmux/lib/libtmux/capabilities.rb, capability control-mode, since: "3.7"). The sentence is not in libtmux-ruby: attachCompleteRubyExamples in packages/api-model/src/languages/ruby-examples.ts writes the same floor into the intro of every complete program, and the port's manifest has nowhere to say otherwise. The fix therefore has two halves. In libtmux-ruby, add the floor to the two manifest entries api_control and api_async_control in examples/manifest.json, as "tmux": "3.7" beside description and output under api; the other programs omit it. In this repository, once that lands, read api.tmux in attachCompleteRubyExamples and use it in place of the fixed 3.3a, defaulting to 3.3a, and regenerate the Ruby model and receipt.

These 11 pages come back when the fixes land. HELD_RUBY_PROGRAMS in packages/api-model/src/languages/ruby-examples.ts lists examples/api/workspace_apply.rb, examples/api/control.rb and examples/api/async_control.rb, each with its reason. When libtmux-ruby merges the workspace_apply.rb change, move the Ruby pin, delete that entry, regenerate the model (gen-api-model --port ruby) and refresh the example receipt for the four Workspace pages. When its manifest declares api.tmux for api_control and api_async_control, read it in attachCompleteRubyExamples in place of the fixed 3.3a, delete the other two entries and refresh the receipt for the seven control-mode pages. An entry left behind after the manifest drops its program fails the generator and ruby-api-examples.test.ts.

Second review follow-ups

A second independent review at f12be07 confirmed the earlier fixes and asked for four more changes. CI at that head also failed in the browser matrix. This section supersedes the statements above where it differs: the Rust Core cache is now refreshed at the Rust pin (Go's is not), and check-clipboard's complete-examples check has been run.

The TypeScript and Rust API models now match the MCP matrix (medium). The matrix and snapshots were recorded at libtmux-ts 073b9d7de and libtmux-rs 6942c1e60, while the models still described 46cccfef2 and a6fc2a656, and a tool page builds its source link from the model's revision. So read_program, start_program and stop_program linked packages/mcp/src/tools/programs.ts, which does not exist at 46cccfef2, and run_program and wait_for_exit cited files that do not register them and kept no line. Both models are regenerated with gen-api-model from clean read-only clones of the two masters, and everything bound to the pin follows, each from its own generator and checked in its --check mode: the example cache, product copy, workspace showcase (which drops its TypeScript pin, as the generator requires once the model has caught up), the manifest index at 0.1.2 and the destination evidence for it (a live read-only run: crates.io lists 0.1.2 for all four crates, the npm pages answer 403 as before), the tmux usage rules and data, the navigation sidecars, the Rust example receipt, the prose-link review lists, and the MCP matrix rows. TypeScript gains 68 symbols and loses none (the Server.spawn program API). Rust gains 13 and loses three constants (PANE_BORDER_FORMAT_PER_PANE, PROMPT_HISTORY, SERVER_ACCESS).

  • New check. registrationFileProblems holds every registration's file to the committed file list of its model, and mcp-source-links.test.ts runs it over the committed matrix, so it runs in CI with no checkout. It also requires the TypeScript and Rust registrations to sit on their model's revision, so it cannot pass by judging nothing. registrationLinkProblems and scripts/check-mcp-source-links.mjs, added to the publication audit, read each registration from a checkout at the revision it cites: the file exists, the line is inside it, and the wire name is a whole word within its own registration. A port with no checkout, or without the revision, is reported as skipped. Negative cases cover a missing file, another revision, a file that does not register the tool, a line past the end or none, a name that is only part of another or belongs to the next registration, and the script run over a repository that lacks the cited file.
  • Measured against the old state. Run on this branch's previous head, the script reports exactly the five tools above for TypeScript (three missing files, two with no line) and nothing for Rust. On this head every registration of the ten ports resolves at the revision it cites.
  • Navigation. The new TypeScript declarations Server.spawn brings (program.Program, types.ProgramPlacement, types.ProgramExit) and common.assertSupportedTmux, and Rust's src.SandboxCanary, are recorded as unsettled with their reasons, as LibTmux::Support was. A reviewer who wants a TypeScript "Programs" bucket would replace those three entries.
  • Native runs. The Rust receipt records seven programs run as displayed with check-example-prose.py --example api --port rs on tmux 3.3a and 3.7c, Rust 1.99.0, cargo add libtmux@0.1.2 resolving to 0.1.2 on crates.io: 14 runs, all exited 0 with their expected output. TypeScript has no complete API programs.

The Rust split-pane answer no longer races (low). The Rust server describes a pane the moment it splits it, and tmux names the forked child tmux and reports no path until the shell has replaced it. Of thirteen recordings, five caught the tmux form; the committed file held the other unsettled form (sh with a null path). A server cannot be waited on from outside its answer, so the recorder now rejects an unsettled answer and records again: verifySettledPanes requires every pane an answer describes to run the shell (cat for the Ruby session that borrows one) and to name a path, and gen-mcp-transcript.mjs records up to six times before it fails, stopping only the servers of its own sandbox between attempts. The Rust transcript is recorded again with a settled pane, and a unit test requires every committed recording to satisfy the check. mcp-replay.test.ts keeps pinning sh, which is now what the recorder guarantees.

plannedRun refuses a cell that waits on a skippable job (low). 54147eb is kept as written and pushed. alwaysRuns follows every other job in needs to the end and refuses an if, a tolerated failure, a job the workflow does not define, and a cycle. Its six new tests fail against the previous predicate (checked by putting the previous file back) and pass with it; the real Ruby compatibility.yml still passes for both entries.

The CI failure was a wrong route in the receipt. checkCompleteApiExamples failed with LibTmux::Workspace#plan: HTTP 404. The nine Ruby programs on Workspace declarations named ports/ruby/reference/<slug>, but the site serves a workspace declaration under its product, ruby/latest/workspace/reference/<slug> (productApiHref). The check reads the route from the receipt and stops at the first failure, so it had not looked at the pages after that one, and ruby-api-examples.test.ts spelled the same core path and agreed with the receipt. The nine entries now name the workspace reference. A test over every receipt entry requires the page to be the one productApiHref gives the declaration, plus one that a workspace declaration is served under the workspace reference. The browser matrix's other checks had passed in that run; the log held no other failure.

Verification of this follow-up

  • Run from this tree against a development server: checkCompleteApiExamples passes for all 295 programs on 285 pages. With the nine old pages put back it fails with LibTmux::Workspace#plan: HTTP 404, as in CI. The server mirrors check-dev.mjs's own setup; the rest of that matrix was not run.
  • Full site suite with the pinned clones of ts, rs and ruby set through LIBTMUX_DOCS_CHECKOUT_<PORT>: 256 files and 8267 tests passed; 5 files and 351 tests are skipped because nothing is assembled. Without them, six tests fail on this machine only because its standard ts and rs checkouts lack the new commits (port-landing and core-capabilities read the workflow at the model's revision); CI has no such checkout and skips them. The api-model suite passes (60 files, 1305 tests, one skipped). astro check reports 0 errors and 0 warnings; it needs --max-old-space-size=8192 here. oxlint reports nothing new in the changed files.
  • Per port (ruby, rs, ts), each with its checkout at the recorded revision: gen-mcp-protocol --check, gen-mcp-tools --check and gen-mcp-transcript --check pass. gen-api-model --check passes for ts and rs; gen-example-sources, gen-product-copy, gen-workspace-showcase, gen-mentions and verify-destinations pass in their --check modes; gen-tmux-usage --check --source --ports ts,rs regenerates both and matches; check-nav, check-api-links, check-source-links for ts, rs and ruby, and check-mcp-source-links pass, and so do the negative controls of tmux usage, the workspace showcase, product copy and example sources. stage-port-docs.mjs --cached passes plain and with LIBTMUX_DOCS_PORT=rs.
  • Not run: the site build, the full publication audit, the rest of the browser matrix, and gen-mcp-tools and gen-mcp-protocol without --port (this machine's other checkouts sit at other heads). gen-manifest-packages --check still reports Elixir, as on main. The line and wire-name half of check-mcp-source-links needs a checkout, so CI runs only its file half.

Third review follow-ups

A third independent review at b2d6807 confirmed the pins, regenerations, transcripts, the retry loop and the receipt routes, and asked for the changes below. Root ruled that text known to be wrong is not published. CI at b2d6807 also failed in the Astro build; that is the last item. This section supersedes the statements above where it differs.

Eleven Ruby pages no longer show a program (medium). The pages of Workspace.load, Workspace#plan, Workspace::Plan#steps and Workspace::Plan#apply told readers to save workspace_apply.rb, which fails alone with ConfigError. The seven control-mode pages (Server#open_control, ControlConnection#exchange and #subscribe, ControlSubscription#next, Async::Server#open_control, Async::ControlConnection#events, Async::ControlSubscription#next) said "tmux 3.3a or newer" while the library refuses a control client below 3.7. attachCompleteRubyExamples now takes a list of held programs, each with a reason, and attaches none of a held program's symbols. A list entry the manifest does not select fails the generator, and ruby-api-examples.test.ts fails if a held program is attached to any page, if a held program is no longer selected by the manifest at the model's revision, or if a program that is not held is missing. The Ruby model is regenerated with gen-api-model --port ruby (only the eleven symbols change, each losing its four example blocks) and the eleven receipt entries are removed. Complete programs now attach to 44 Ruby pages, not 55. The other programs keep their receipt entries.

I did not derive a floor for the control programs. The manifest entries have no tmux field, and the 3.7 the docs hold is prose: the Ruby model says "tmux 3.7 and newer" in the doc of Server#supports_control?, and the cached source guides say "Control connections need tmux 3.7 or newer". Reading a version out of prose would be a guess, so the pages hold back instead.

A link into a neighbouring tool's registration passed (medium). registrationLinkProblems ended a span at the next line that any cited registration named, so a tool that cited its neighbour's line no longer cited its own, and the span ran on into its own registration. get_window_info citing get_session_info's line in TypeScript, contract.rs line 555 for 579 in Rust, and tmux_run citing tmux_snapshot's line in Ruby all passed. A registration now reaches from its cited line to the first line that names its tool, read from the file, and no two reaches in a file may share a line. Negative cases shaped like the three ports (TypeScript's name after registerTool(, Rust's after an attribute whose description names the next tool, Ruby's in constant lists far from the branch) pass at each tool's own line and refuse the neighbour's line and the lines before its name. With the pinned checkouts, in a copy of the matrix with those three citations altered, check-mcp-source-links.mjs --port ts, rs and ruby exited 0 before and exit 1 now; the committed matrix resolves for all ten ports, and moving each tool of each port to each neighbour's cited line is refused in all ten. A line in a neighbour's body, past the line that names it, is not refused: the file alone does not say where that body ends. The existing case of a name that lies in the next registration now reports the overlap in one message naming both tools.

Smaller changes. mcp-source-links.test.ts now requires Ruby, not only TypeScript and Rust, among the registrations on their model's revision (low). neededJobs reads a flow list that wraps onto further lines through its closing bracket, so needs: [plan, with lint] below is read as both (low). The header of gen-mcp-transcript.mjs describes the settle-and-retry (low). The branch history is out of the core-apis.test.ts comment and in the commit message (low). The Rust justfile evidence text in core-capabilities.json changed to script-env examples example-tables; it is true at 6942c1e60, where the check recipe lists script-env examples example-tables in place of the earlier doctest api-examples examples example-tables (low).

The dangling-reference ceiling. The Astro build failed with "62 unresolved mentions exceeds the ceiling of 60". The tally by port in the CI log (fsharp 28, swift 10, ruby 6, scala 6, kotlin 4, lua 3, powershell 2, csharp 1, py 1, ts 1) is the 60 unresolved mentions the committed mentions.json holds plus two PowerShell mentions it does not; main's list holds 57. The Ruby guides cached at the new pin added four spans that name nothing in the Ruby model and dropped one: Enter ("tmux key names such as Enter"), Example.check on two pages ("the Example.check assertion helper from the complete program") and error.message ("With --json, the refusal is in error.message"). The TypeScript and Rust refreshes added none; TypeScript's [Symbol.asyncDispose] is on main too. Each of the four is not API, and the resolver now says so rather than the ceiling moving. Example.<member> is the example fixture, as Example already was. A capitalised word in a sentence about "key names" names a tmux key, and a dotted lowercase path in a sentence about JSON is a field of that output; this is read from the span's own sentence and only after the name failed to resolve, so a name a port defines still links. The ceiling is unchanged at 60. Run through the real linking plugin over the staged Ruby guides, Ruby reports six unresolved before and two after (Enumerable and LibTmux, both on main); the committed list goes from 60 to 56, so the build reports 58. Cases cover each rule, the same spellings without the cue, a cue in the previous sentence and a span shaped unlike what the cue names.

Verification of this follow-up

  • Full site suite with the pinned clones of ts, rs and ruby set through LIBTMUX_DOCS_CHECKOUT_<PORT>, through the heavy-job wrapper with two workers: 256 files and 8272 tests passed; 5 files and 351 tests are skipped because nothing is assembled. The api-model suite passes (60 files, 1316 tests, one skipped). oxlint reports nothing new in the changed files.
  • Before and after, per change: the six neighbour cases of the source-link check failed on the old function and pass; four of the wrapped needs cases failed on the old neededJobs and pass; the first and third of the new tests over the committed Ruby model failed with the eleven programs attached and pass; the Ruby coverage assertion fails when the Ruby model's recorded revision moves, where the old one passed.
  • gen-api-model --port ruby and gen-mcp-transcript --port ruby (a comment-only change, but run) pass in their --check modes against a clean clone of libtmux-ruby at 20744401e, and gen-mentions --check passes; check-mcp-source-links.mjs resolves all ten ports at the revisions the matrix cites, with ts, rs and ruby cloned read-only at their pins and deleted afterwards. The TypeScript and Rust transcript checks were not re-run: the change to that script is a comment. check-api-links, check-nav, check-example-blocks, check-example-indentation and the --check modes of the example-source, workspace-showcase, workspace-entities and product-copy generators pass.
  • Not run: the site build and the publication audit. The dangling count above is measured with the linking plugin over the Ruby guides and read from the CI tally and the committed list for the other ports, not from a build; the full build took 53 minutes in CI.
  • The rest of the CI log of run 41739010615 holds no other failure: the only failed step is the root build, which ended at that hook, and the preview jobs were skipped.

tony added 14 commits November 3, 2026 01:17
why: libtmux-ruby's `LibTmux::Support` methods are typed
`(::String value) -> ::String`. RBS allows a name after a positional
type, but the extractor kept the whole text as the type, so the model
held the type "::String value" and `ruby-layout.test.ts` could not give
it the roles its definition gives the same tokens. The Ruby model could
not move to a revision that has these methods.

what:
- Split a positional parameter into its type and its written name, and
  keep `argN` when it writes none. A union ends in its last member, not
  in a name: `::Integer | nil` is still one unnamed type.
- Cover a named, an optional named, an unnamed union and a generic type
  in `native-adapters.test.ts`; the test fails without the change.
why: Docs CI read the Ruby example cache at 9b1545562, while the port's
own build reads the live file. `examples/api/capture.rb` stopped
attaching a control client at 2156612 ("Require tmux 3.7 before
attaching a control client"), so `core-apis.json` kept naming five
control-mode symbols that the port's build refused
("core-apis: ruby/send-keys names LibTmux::Server#open_control, but the
program never uses open_control", libtmux-ruby#90) and docs CI never
saw it. The cache is bound to the revision of the API model, so the
model has to move with it.

what:
- Regenerate the Ruby model from the committed bytes of master with
  `gen-api-model --port ruby`: the four gems move from 0.1.0.alpha.1 to
  0.1.1. `LibTmux::Support`, `Support::Versions`, `tmux_version` and
  `supports_control?` on both servers, and `require_supported` and
  `server_version` on both support modules are new; no symbol is
  removed. `version_timeout` joins `Server.new`, `Server.open` and
  `Async::Server.open`, `environment` joins `Endpoint.new` and
  `MCP::CLI.run`, `PaneSnapshot#pid` may be nil and `Pane#break_out`
  documents `ProtocolError`. Complete programs attach to the symbols
  they exercise: 55 pages, up from 19.
- Regenerate the example cache. `capture.rb`, `new_window.rb`,
  `async_cancel.rb`, `manifest.json`, `scripts/check`, `scripts/matrix`,
  `installed_gems.rb` and `workspace_apply.rb` changed; `quickstart.rb`
  and `mcp_protocol.rb` only moved revision.
- Regenerate what is bound to the Ruby pin: product copy, the workspace
  showcase (revision only) and the "Discussed in" index, which gains
  the mentions of the guides master ships.
why: The four Ruby gems declare 0.1.1 at the new pin, so the manifest
index and the destination evidence still named 0.1.0.alpha.1 and
`destination-evidence.test.ts` refused four RubyGems records. That
suite also holds every record to one check date, so the evidence has to
be re-verified whole rather than record by record.

what:
- Regenerate the Ruby entries of `manifest-packages.json` at the pinned
  revision. The Elixir entry is not regenerated: it already differs
  from its manifests on main.
- Re-run `verify-destinations` live and read-only. The four
  rubygems.org package pages each list 0.1.1. The same run moved the
  latest version the registries report for other ports (Maven Central
  and crates.io 0.1.1 to 0.1.2, Hex, LuaRocks and Swift Package Index
  0.1.0 to 0.1.1), which this change does not own and records as found.
why: The Ruby usage data cited lines of 9b1545562, so
`gen-tmux-usage --check` refused it once the model moved to 20744401e,
and the dispatcher rules described a call chain that no longer exists:
`Server#run` now hands its arguments to a private `dispatch`, which
calls the executor, and `execute_typed` reaches that through `run`.

what:
- Carry each cited line of the Ruby rules to where the same text sits at
  20744401e, following the diff of each file: 219 citations moved or
  stayed with their text, 8 where the line itself changed were found
  again by their text, and 1 stays because its two-line statement
  spans the cited line.
- Replace the hops `execute_typed -> run -> @executor.run` of the seven
  Ruby dispatchers that pass through them with `execute_typed -> run ->
  dispatch -> @executor.run`, citing the executor call on the line of
  `dispatch`.
- Regenerate `tmux-usage/ruby.json`. The entries differ in the lines
  they cite, and `Pane#break_out` now also sends `list-panes` when
  `break-pane` prints no window ID and `rename-window` after the
  repaired `break-pane` of tmux 3.7.

Checked: `gen-tmux-usage --check --source --ports ruby` regenerates the
port and matches the committed file; `gen-tmux-usage.negative.mjs`
passes.
…s plan

why: libtmux-ruby's `compatibility.yml` now plans its matrix in a `plan`
job, and the `cell` job that runs the Core programs carries
`if: needs.plan.outputs.run_cells == 'true'`. `workflowStep` refuses any
job-level `if`, so `ruby/create-windows` and `ruby/send-keys` stopped
being provable as executed by CI at the new pin. The port's `ci-plan`
selects cells for every push and pull request, which this check cannot
read; it can read that the flag is the plan job's own output.

what:
- Accept a job-level `if` only when it compares an output of a job this
  job `needs` with `'true'`, that job is in the same workflow, declares
  the output and is not itself conditional.
- Keep refusing any other condition, a flag the plan does not declare, a
  different comparison, an added clause, a plan the job does not need,
  a conditional plan and `continue-on-error`. Each refusal has a test.
… uses

why: `examples/api/capture.rb` no longer opens a control connection. It
sends text, presses Enter and polls `pane.capture` until the output
arrives. The send-keys list still named `Server#open_control`,
`ControlConnection#exchange`, `ControlConnection#subscribe`,
`ControlSubscription#next` and `ControlEvent#data`, so the Core page
would have listed five APIs the program never calls, and the port's own
build failed on the first of them. The capability itself is unchanged:
its title is "Send keys" and nothing on the page says the program
watches through control mode.

what:
- Drop the five control-mode symbols from `ruby/send-keys`. The list is
  now Server, Server.start, Server#new_session, CreationReceipt#pane,
  Pane#send_text, Pane#send_keys, Pane#capture and CommandResult#stdout,
  each resolved against the model at the pinned revision.
- Derive the ports `core-apis.test.ts` checks from the capability table
  instead of a list of three, so a port that gains a showcase example
  cannot ship without a list of the APIs it uses.
- Pin the incident in a test on the frozen program: the corrected list
  resolves against it, and a list that still names any of the five
  symbols is refused with the symbol's name.

Checked: with the cache refreshed and this list unchanged,
`core-apis.test.ts` failed on exactly `ruby/send-keys names
LibTmux::Server#open_control, but the program never uses open_control`.
why: The site tests bind every displayed example block and recipe to a
receipt from native runs. The Ruby model moved to libtmux-ruby
20744401e, so every Ruby page names a new revision and a Gemfile for
0.1.1, and 36 more pages carry a complete program than the 19 the
receipt covered.

what:
- Rebind the 19 existing entries (sourceRevision and the hash and copy
  hash of the Gemfile) and add the 36 new ones. Each entry is derived
  from the displayed blocks of its page; the derivation reproduces the
  19 committed entries byte for byte from the previous model.
- Name 18 programs in `ruby-api-examples.test.ts`, up from 8.
- Record what each program did. The two programs that attach a control
  client list tmux 3.7c only: the library refuses one below tmux 3.7.
  `workspace_apply.rb` lists no tmux release and says it is not
  verified: it loads `../workspace.yaml`, which no page displays.

Checked with scripts/check-example-prose.py --example api --port ruby,
one run per program on each of tmux 3.3a and 3.7c, at the committed
revision, each restoring the gems from rubygems.org with Ruby 4.0.7 and
Bundler 4.0.20: 15 programs passed on both releases and the 2 control
programs passed on 3.7c. Both control programs stop on 3.3a with
"control connections require tmux 3.7 or newer". workspace_apply.rb
failed on both releases with LibTmux::Workspace::ConfigError: workspace
file could not be read. That is a defect of the page the port ships
(its program is not self-contained), not of this receipt; it needs a
fix in libtmux-ruby.
why: libtmux-ruby 0.1.1 adds `LibTmux::Support`, the namespace that
checks which tmux releases the library supports. No navigation bucket
claims a module of that kind, so `check-nav` failed with "unmatched
[ruby] — 1 symbols no bucket claims: Support". The bucket named
"Terminal capabilities" is about terminal features, not releases.

what:
- Record `LibTmux::Support` as unsettled with the reason. Its nested
  `Versions` module and its functions are already placed.
- Recompile `ruby.nav.json`; only the diagnostics change.

Checked: `check-nav` reports every symbol of the 16 ports placed or
named.
why: The Ruby API model moved to libtmux-ruby 20744401e but the guide
cache still recorded 9b1545562. stage-port-docs.mjs refuses a cache
whose revision differs from the model, so `--cached` threw "guide
source ... differs from integrated model" on every build: each
port's publisher build and this branch's test job, which failed at
"Stage integrated source guides".

what:
- Regenerate site/src/data/port-guides/ruby.json with
  `stage-port-docs.mjs --port ruby --from-source --refresh-cache`
  from a clean clone at 20744401e.
- The README header now centres its logo with a div instead of a
  paragraph, as the port ships it.
why: The Ruby API model moved to libtmux-ruby 20744401e, but the MCP
protocol snapshot, the Ruby rows of the tool matrix and the recorded
transcript still described 9b1545562. The tools' schemas changed in
between, so the Ruby MCP pages documented a server that no longer
exists at the pin.

what:
- gen-mcp-protocol.mjs --port ruby: snapshot of the server built from
  a clean clone at 20744401e.
- gen-mcp-tools.mjs --port ruby: the Ruby rows follow the snapshot's
  schemas and the new source lines; other ports' rows are unchanged.
- gen-mcp-transcript.mjs --port ruby: the two-step replay recorded
  against that server, with its contracts equal to the snapshot's.
- Each generator's --check passes.
why: After the r12 wave the recorded contracts of the rs and ts
transcripts no longer equal the tool schemas at each port's master,
so the live replay falls back to "Requests only". rs differs in the
output schema of run_shell_command; ts in those of run_shell_command
and list_windows. The input schemas still match.

what:
- gen-mcp-protocol.mjs --port rs and --port ts: snapshots of the
  servers built from clean clones at libtmux-rs 6942c1e60 and
  libtmux-ts 073b9d7de.
- gen-mcp-tools.mjs --port rs and --port ts: their rows follow the
  snapshots. ts registers five program tools more than before
  (start_program, read_program, run_program, wait_for_exit,
  stop_program), so the matrix lists five more tools.
- gen-mcp-transcript.mjs --port rs and --port ts: the worked example
  replayed against those servers. Each transcript's contracts equal
  its snapshot's and the matrix rows' schemas.
- Each generator's --check passes.
why: plannedRun accepted any `needs.X.outputs.Y == 'true'` whose job
declared an output of that name, so it proved less than it claimed. A
cell gated on `run_development` counted as run by CI; so did a plan
whose output was hard-wired to 'false' or read a step that does not
exist, a plan whose only step was `if: false`, a `needs: [other] # plan`
whose plan lived in a comment, and a plan that itself needed a
conditional job. A job-level `if` with `# continue-on-error: false` in
its comment also passed the check that lets a tolerant job through.

what:
- The capability entry records the one flag a job may wait on
  (`ci.planOutput`, `run_cells` for both Ruby entries). A job-level
  `if` is accepted only as `needs.<plan>.outputs.<flag> == 'true'`
  with that flag, and an entry that names none accepts no `if`.
- The plan job must list no `needs`, `if` or tolerated failure, and
  publish the flag as `${{ steps.<id>.outputs.<flag> }}` of a step
  that exists, has that id and carries no `if` or tolerated failure.
- Comments are stripped before `needs` is read, so a name that only a
  comment holds is not a dependency.
- `continue-on-error` is false only when its value is false, not
  when a comment says so.
- Tests: one case for each counterexample, a `needs: other` case that
  pins the membership check, the entry's flag, the name validation,
  and the real Ruby workflow at 20744401e refused without its flag.
  Against the previous predicate sixteen of these fail.
why: `toBe(18)` was a bare count of the distinct source files, which
a refresh that adds or drops a program silently invalidates, and it
could not say which file was missing.

what:
- Compare the receipt's source files with the files the model's
  example blocks link, as sets, so a mismatch names the file.
why: plannedRun checked the plan job and then ignored the other jobs
a cell needs. A cell with `needs: [plan, release_artifacts]` counted
as run by CI although release_artifacts has an `if` and does not run
on a push or pull request, and a skipped dependency skips the cell.

what:
- Every job in `needs` other than the plan must run whenever the
  workflow does: no `if`, no tolerated failure, and every job it
  needs the same, followed to the end. A job the workflow does not
  define, or a cycle, is refused.
- The plan keeps its own rule: no `needs`, `if` or tolerated failure.
- Tests: a conditional job beside the plan in a flow list and a block
  list, a tolerated one, one that needs a conditional job, one the
  workflow does not define, one that needs the cell, and an
  unconditional sibling that is accepted. Six of them fail without
  the check.
tony added 8 commits November 3, 2026 17:16
why: The MCP matrix and snapshots were recorded at libtmux-ts
073b9d7de and libtmux-rs 6942c1e60, but the API models still described
46cccfef2 and a6fc2a656. A tool page builds its source link from the
model's revision, so read_program, start_program and stop_program
linked packages/mcp/src/tools/programs.ts, which does not exist at
46cccfef2, and run_program and wait_for_exit cited files that do not
register them. Everything bound to the pin lagged the same way.

what:
- Regenerate the TypeScript and Rust models with
  `gen-api-model --port ts` and `--port rs` from clean read-only clones
  of the two masters. TypeScript gains 68 symbols and loses none (the
  program API: Server.spawn, Program, ProgramStartError and their
  options). Rust gains 13 and loses three constants
  (PANE_BORDER_FORMAT_PER_PANE, PROMPT_HISTORY, SERVER_ACCESS).
- Regenerate the example cache, product copy and workspace showcase.
  The showcase drops its TypeScript pin: the model now records a
  revision whose tests load tmuxp's examples, and the generator refuses
  a pin once the model has caught up with it.
- Follow the Rust justfile in core-capabilities.json: `script-env` now
  sits between `api-examples` and `examples` in `check`, so the
  evidence string starts there.
- Update what the refresh moved in the tests: the prose-link review
  lists for the new TypeScript and Rust summaries, TypeScript constants
  that now carry their own descriptions and the line of
  ResizeAdjustmentDirection, the showcase test for an unpinned
  TypeScript, and the Rust payload-field count in nav-config.test.ts,
  which becomes a guard against an empty model, not a copy of the
  model's size.

Checked: `gen-api-model --check --port ts` and `--port rs`,
`gen-example-sources`, `gen-product-copy` and
`gen-workspace-showcase` in their `--check` modes, and their
negative controls.
why: `check-nav` refuses a symbol that no bucket claims and that is not
named as unsettled. The TypeScript program API and a Rust doctest
marker arrived with the refreshed models and fit no bucket.

what:
- Record `common.assertSupportedTmux`, `program.Program`,
  `types.ProgramPlacement` and `types.ProgramExit` as unsettled for
  TypeScript, each with the reason: the handle and its options belong
  to `Server.spawn`, whose pane sits with Pane, and the release check
  has no tmux object of its own.
- Record `src.SandboxCanary` as unsettled for Rust: it is the doctest
  sandbox marker, test support and not tmux API.
- Recompile `ts.nav.json` and `rs.nav.json` with `gen-api-model --nav`.

Checked: `check-nav` reports every symbol of the 16 ports placed or
named and no rule dead; `check-nav.negative.mjs` passes.
… 0.1.2

why: libtmux-ts and libtmux-rs declare 0.1.2 at the new pins, so the
manifest index and the destination evidence still named
0.1.0-alpha.12 for the four npm packages and 0.1.0-alpha.15 and
0.1.0-alpha.16 for the four crates.

what:
- Regenerate the TypeScript and Rust entries of `manifest-packages.json`
  at the pinned revisions. The Elixir entry is not regenerated: it
  already differs from its manifests on main.
- Run `verify-destinations` live and read-only. crates.io lists 0.1.2
  for all four crates. The four npm package pages answer 403 to this
  client as before, and the record keeps saying so. No other record
  changed.

Checked: `verify-destinations --check` agrees with the inventory, and
`gen-manifest-packages` run to a separate file differs from the
committed one only in the Elixir lines.
why: The TypeScript and Rust rules cite lines of the old pins, so
`gen-tmux-usage --check --source` refused them once the models moved,
and some of the descriptions no longer matched the code.

what:
- Carry each cited line to where the same text sits at libtmux-ts
  073b9d7de and libtmux-rs 6942c1e60. The negative controls' expected
  messages follow the moved lines.
- TypeScript: the MCP launch no longer starts the server or reads its
  options. `inspectServerStartup` asks whether the server is alive and
  reads the socket path only from one that is, so the `start-server`
  and `show-options` claims go and `display-message` is now sent when
  the server is already running. `Server.validateLayouts` probes the
  daemon's version through `checkDaemonVersion`, which sends
  `run-shell -C`, not `display-message`. The break-pane quirk of
  `Pane.breakOut` is named by its tag, `break-pane-misnames-windows`.
- Rust: the version gate in `internal/support/versions.rs` is now a hop
  of the dispatcher chains: `dispatch_checked`, then
  `versions::dispatch`, then the executor. The exclusion for
  `Plan::run_over_control_mode` is gone: it now reads the version the
  connection attached under and no longer asks with `display-message`.
- Regenerate `tmux-usage/ts.json` and `tmux-usage/rs.json`.

Checked: `gen-tmux-usage --check --source --ports ts,rs` regenerates
both ports and matches the committed files, and
`gen-tmux-usage.negative.mjs` passes.
why: The site tests bind every displayed example block and recipe to a
receipt from native runs. The Rust model moved to libtmux-rs 6942c1e60,
so each Rust page names a new revision and a recipe that adds
libtmux 0.1.2, and the receipt's record of the runs described the
previous pin.

what:
- Rebind the Rust entries: sourceRevision, the `cargo add` line of the
  recipe, and the verification scope. The program files are
  byte-identical at the new revision, so no hash changes.
- Record the runs of this revision. Each of the Rust programs
  (construction, listings, creation, query, cardinality, input,
  capture) ran as displayed with `scripts/check-example-prose.py
  --example api --port rs` on tmux 3.3a and 3.7c, in a new directory
  each, with Rust 1.99.0. `cargo add libtmux@0.1.2` resolved to 0.1.2
  from crates.io on 2026-11-03, and all programs exited 0 with their
  expected output on both releases.
why: A tool page links `file#Lline` at the revision of the port's API
model, and nothing checked that the file was there. With the matrix
ahead of the TypeScript model, read_program, start_program and
stop_program linked packages/mcp/src/tools/programs.ts at 46cccfef2,
where it does not exist, and run_program and wait_for_exit cited files
that do not register them and kept no line. The link audit reads
declarations, not registrations.

what:
- Regenerate the matrix rows of TypeScript and Rust so their source
  links name the revisions the models now record.
- Add `registrationFileProblems`, which holds every registration's file
  to the committed file list of its model (`<port>.paths.json`), and
  run it from `mcp-source-links.test.ts` over the committed matrix. It
  needs no checkout, so it runs in CI. The same test requires the
  TypeScript and Rust registrations to be on their model's revision,
  so the check cannot pass by judging nothing.
- Add `registrationLinkProblems` and `scripts/check-mcp-source-links.mjs`,
  which read each registration from a checkout at the revision it
  cites: the file exists, the line is inside it, and the tool's wire
  name is written as a whole word within its own registration. A port
  with no checkout, or without the revision, is reported as skipped.
  Add it to the publication audit after the matrix check.
- Negative cases: a missing file, a revision that lacks the file, a
  file that does not register the tool, a line past the end or none,
  a name that is only part of another or lies in the next
  registration, and the script run over a repository that lacks the
  cited file (exit 1, naming the tool).

Checked: on the pushed state of this branch the script reports the five
tools above for TypeScript and nothing for Rust; on the regenerated
matrix every registration of the ten ports resolves at the revision
it cites. `gen-mcp-tools --check` passes for ts, rs and ruby.
…arted

why: The Rust server describes a pane the moment it splits it. tmux
names the forked child `tmux` and reports no path until the shell has
replaced it, so the split answer came back as `tmux` with a path, or
`sh` with none, depending on timing. The committed recording held the
second form, and a fresh recording of the first failed
`mcp-replay.test.ts`, which draws the pane list from the recorded
split. Of thirteen recordings, five caught the `tmux` form.

what:
- `verifySettledPanes` requires every pane an answer describes to run
  the shell (`sh`, or `cat` for the Ruby session that borrows one) and
  to name a path, whatever spelling the server uses for the keys. A
  pane described earlier throws `UnsettledPaneError`.
- `gen-mcp-transcript.mjs` records again on that error, stopping the
  servers of its own sandbox first, up to six recordings, and throws if
  the last is still unsettled. A borrowed session cannot be restarted
  from the recorder, so it fails at once. Nothing is written for an
  unsettled answer.
- Record the Rust transcript again: `split_window` now answers `sh`
  with `/home/user/project`. The other fields are unchanged.
- Tests: the check accepts settled panes and refuses `tmux`, a null or
  empty path, another spelling of the keys and a pane inside a list;
  it leaves alone an object that is not a pane; an unsettled pane is not
  a tool error; and every committed recording satisfies it.

Checked: `gen-mcp-transcript --check` passes for ts, rs and ruby. The
repeated Rust recordings that were unsettled recorded again and the
file they wrote equals the committed one.
why: CI failed at f12be07 in `checkCompleteApiExamples`
(site/scripts/check-clipboard.mjs) with
`LibTmux::Workspace#plan: HTTP 404`. The nine Ruby programs on
`Workspace` declarations named `ports/ruby/reference/<slug>`, but the
site serves a workspace declaration under its product,
`ruby/<version>/workspace/reference/<slug>` (`productApiHref`). The
browser check reads the route from the receipt and stops at the first
failure, so the program pages after that one had not been checked.
`ruby-api-examples.test.ts` spelled the same core path, so it agreed
with the receipt and not with the site.

what:
- Point the nine receipt entries at `ports/ruby/workspace/reference/`.
- Replace the hard-coded core path in `ruby-api-examples.test.ts`.
- Add a test over every receipt entry of every port that its page is
  the one `productApiHref` gives the declaration, and one that a
  workspace declaration is served under the workspace reference. The
  first fails on the old pages with the symbol that failed in CI.

Checked: a development server built from this tree answers every
program page of the receipt, and `checkCompleteApiExamples` passes for
all of them. With the nine old pages put back it fails with
`LibTmux::Workspace#plan: HTTP 404`, as in CI.
@tony tony changed the title Pin Ruby at libtmux-ruby master and list the APIs its send-keys program uses Pin Ruby, TypeScript and Rust at their masters, re-record their MCP sessions, list the APIs Ruby's send-keys uses Nov 3, 2026
tony added 6 commits November 3, 2026 19:07
why: The check that keeps the MCP matrix on the revisions of its
models required only TypeScript and Rust. The Ruby rows are recorded
at the revision of the Ruby model too, so a Ruby matrix that ran
ahead of its model would have been judged by neither check and the
test would still have passed.

what:
- Require ruby beside ts and rs in the `covered` assertion of
  `mcp-source-links.test.ts`. With the revision the Ruby model
  records moved, the assertion now fails (received the other ports
  but not ruby); the previous one passed.
why: `registrationLinkProblems` ended a tool's registration at the
next line that any cited registration named. A tool that cited its
neighbour's line no longer cited its own, so the span ran on through
the neighbour into the tool's own registration, found the tool's name
there and passed. A tool page would link another tool's code:
get_window_info citing get_session_info's line in TypeScript
(target.ts#L190), in Rust (contract.rs#L555 for #L579), and tmux_run
citing tmux_snapshot's line in Ruby (catalog.rb#L6).

what:
- Read each registration's reach from the file: the cited line to the
  first line that names the tool. No two reaches in a file may share a
  line, so a citation of the neighbour's line, or of a line above the
  neighbour's name, overlaps it. The reach does not depend on the
  neighbour's citation, so moving one citation cannot stretch it into
  the next. An overlap is reported once, naming both tools and their
  reaches.
- Add cases shaped like the three ports (a name after `registerTool(`,
  a name after an attribute whose description mentions the next tool,
  and names in constant lists far from the branch that describes
  them): each registration at its own line passes, and a tool that
  cites its neighbour's line, or a line before the neighbour's name,
  is refused. Before, the six refusals passed; the existing case of a
  name that lies in the next registration now reports the overlap.
- A line in a neighbour's body, past the line that names it, is not
  refused: the file alone does not say where that body ends.
- Measured with the checkouts at the revisions the matrix cites: the
  committed matrix resolves for all ten ports as before. In a copy of
  the matrix where the three citations above were altered,
  `check-mcp-source-links.mjs --port ts|rs|ruby` exited 0 before and
  exits 1 now. Moving every tool of every port to the cited line of
  each neighbour is refused in all ten.
why: `neededJobs` read the first line of a flow list and then only
`- ` block items, so `needs: [plan,` with `lint]` on the next line
was read as `plan` alone. A cell that waits on a skippable job named
on the wrapped line counted as run by CI.

what:
- Read a flow list that opens a bracket without closing it through the
  line that closes it.
- Add cases: a wrapped list that names the plan on either line is
  accepted, and a skippable or tolerated job on the second or third line
  of a wrapped list is refused, as is a wrapped job that itself needs a
  skippable one. Before, four of the cases failed: the wrapped accepts
  and three refusals.
why: The header of `gen-mcp-transcript.mjs` listed the ways a
recording fails but not that an unsettled pane is recorded again, so a
reader of the script met the retry loop without its reason.

what:
- State that a pane caught before its shell started is never written,
  that the recorder stops the tmux servers of its sandbox and records
  again up to ATTEMPTS times, and that a server which borrows a running
  session fails on the first attempt.
why: The comment above the fixture told how the stale cache once hid a
refused list. That belongs here, not in the source: a maintainer needs
what the test guards.

what:
- Replace the comment with the guard: the list is held to a frozen
  copy of the program that polls Pane#capture, it must resolve against
  it, and a list that names a control-mode symbol the program never
  calls is refused.

The history, for the record: examples/api/capture.rb stopped attaching
a control client when libtmux-ruby began requiring tmux 3.7 for one.
The cache still held a program that called open_control, exchange,
subscribe, next and data, and the list for the example named all five.
The port's build read the live file and refused the list while this
suite read the stale cache and passed (libtmux-ruby#90).
why: The Astro build failed its dangling-reference ceiling at 62
unresolved mentions, over a ceiling of 60 (CI run 41739010615). Of the
committed list, the Ruby guides cached at the new pin add four spans
that name nothing in the Ruby model and drop one: Enter, Example.check
(on two pages) and error.message. Each is not API, and the build and
`gen-mentions` could say so: they only lacked the words that decide it.

what:
- Treat `Example.<member>` as the example fixture, as `Example` already
  is. The guide calls it "the `Example.check` assertion helper from the
  complete program".
- Add `notApiInContext`, asked only after a span resolves to nothing,
  so a name a port defines still links wherever it is written:
  a capitalised word in "key names such as `Enter`" names a tmux key,
  and a dotted lowercase path in a sentence about JSON output
  ("With `--json`, the refusal is in `error.message`") is a field of
  that output. The cue is read in the span's own sentence, so the next
  sentence's spans are unaffected.
- Regenerate `mentions.json`: the four spans leave its unresolved
  list; its mentions are unchanged.
- Cases for each rule and for what stays dangling: the same spellings
  without the cue, a cue in the previous sentence, and a span shaped
  unlike what the cue names.
- The ceiling is not raised. Measured with the real linking plugin
  over the staged Ruby guides, Ruby reports six unresolved before and
  two after (Enumerable and LibTmux, both already on main). The CI
  tally of 62 is the 60 of the committed list plus two PowerShell
  mentions the list does not hold; the list now holds 56, so the build
  reports 58.
why: Three complete programs attach to eleven Ruby reference pages and
two of them cannot be followed as written. `workspace_apply.rb` loads
`../workspace.yaml`, which no page shows, so saved alone it stops with
Workspace::ConfigError. `control.rb` and `async_control.rb` open a
control client, and the intro the generator writes for every program
says "tmux 3.3a or newer" while the library refuses a control client
below tmux 3.7. Publishing text known to be wrong is worse than
publishing no program, so the pages hold back until libtmux-ruby is
fixed.

No floor for the control programs can be read from data the docs hold:
the manifest entries have no tmux field, and the 3.7 in the model is
prose ("tmux 3.7 and newer" in the doc of `Server#supports_control?`),
so the 3.3a stays out of the pages rather than being replaced by a
guess.

what:
- Add `HELD_RUBY_PROGRAMS`, one list of programs by path with the
  reason each is held and what brings it back.
  `attachCompleteRubyExamples` attaches none of a held program's
  symbols, and fails when the list names a program the manifest does
  not select, so a hold cannot outlive its program. `extractRuby`
  passes the list; a test export passes its own.
- Regenerate the Ruby model with `gen-api-model --port ruby`: the
  eleven symbols lose their four example blocks and nothing else
  changes. Drop their eleven entries from the example receipt, which
  the test over the model requires to match.
- Test the generator (held program unattached, attached once released,
  neighbours unaffected, unknown program and blank reason refused) and
  the committed data: no held program is attached to any page, every
  held program is still selected by the manifest at the model's
  revision, and every selected program that is not held is attached.
  Before, with the committed model, the first and third failed.
…covers it

why: libtmux-ruby 20744401e rewrote the libtmux-workspace README into
task sections. Its "Command-line interface" heading is gone, and the
`libtmux-workspace load` command is now documented under "Create the
session". The Ruby Workspace guides page still linked the old anchor,
the one broken internal link CI found at fa7b989.

what:
- Point the source-owned CLI guide link at #create-the-session.
why: The new pins add API surface. Ruby's model moves to libtmux-ruby
20744401e and gains declarations whose signatures name Ruby's core
classes, and Rust's moves to 6942c1e60. The CI type-name gate at
db000ee (run 41760766017) counted ruby 2109 -> 2129, led by String,
Integer, Numeric, Symbol and Array, and rs 420 -> 421, led by the impl
keyword. Those names were plain before this change too; the count
grows with the surface, not with a resolution that broke. The gate
says to state that and raise deliberately, as the Python and Lua
ceilings were raised before.

what:
- ruby ceiling 2109 -> 2129, rs ceiling 420 -> 421.
@tony
tony marked this pull request as ready for review November 4, 2026 09:15
@tony
tony merged commit 747f719 into main Nov 4, 2026
3 checks passed
@tony
tony temporarily deployed to docs-preview-cleanup November 4, 2026 09:15 — with GitHub Actions Inactive
tony added a commit to libtmux/libtmux-ruby that referenced this pull request Nov 5, 2026
why: The docs site shows each API example as a program a reader saves
in an empty directory with a one-gem Gemfile, and states one tmux floor,
3.3a, for every page. examples/api/workspace_apply.rb loaded a file
from the checkout, and control.rb and async_control.rb need tmux 3.7,
so libtmux/docs#247 held all three back (#92).

what:
- workspace_apply.rb writes the workspace file it loads, so it runs
  saved alone; a test keeps its document equal to
  examples/workspace.yaml.
- Version 2 of examples/manifest.json lets an API example list the tmux
  capabilities it needs in api.capabilities, checked against the
  library's capability registry; the control programs list
  control-mode. The docs export adds tmux_releases, the release each
  API page states. A version 1 manifest from an earlier release is
  checked and exported as before, and the check loads the registry
  only for version 2, so docs rebuilds of earlier releases are
  unchanged.
- capabilities.json lists more routes to each refusal: the Async
  server's open_control, delete_buffer, new_session, run and run_group,
  Server#run_group, ControlConnection.new and
  Async::ControlConnection#exchange.
- The installed-example test expects UnsupportedFeatureError for the
  listed capabilities a daemon lacks, by the library's own available?.

This branch was previously deployed

2 inactive deployments
docs-preview-cleanup — c45b387b Deployed Nov 4, 2026 by tony via cleanup #207
docs-preview — c45b387b Deployed Nov 4, 2026 by tony via publish-preview / publish #1158
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant