Skip to content

Add native Clojure facade - #20

Merged
tony merged 49 commits into
masterfrom
clojure
Nov 8, 2026
Merged

tony merged 49 commits into
masterfrom
clojure

Conversation

@tony

@tony tony commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Java tmux-support foundation

This PR targets master and builds on the core minimum change (tmux 3.3a floor and hook-free version checks) and the optional-feature change (control requires tmux 3.7, exact-3.7 name validation, and pinned control sockets).

Summary

  • Add native Clojure facade: Provide libtmux-clojure with captured persistent maps and vectors, explicit Java refs for tmux effects, native collection processing, bounded tasks, and owned control streams. The artifact is pre-release.
  • Add optional core.async support: Provide libtmux-clojure-core-async without adding core.async to the core module.
  • Extend captured Java support: Server.captureServer returns metadata and entity handles from one capture. Reserved-capacity batches and chains preserve the transport's checked waiting path.
  • Execute the documentation examples: The module READMEs link to a Clojure guide, captured-field reference, and generated native API references. Module checks compare var arities and docstrings with source, resolve authored Clojure wikilinks, and reject stale output or unknown public targets. Both Maven documentation classifiers contain native API HTML and an offline source browser, including linked dependency documentation for the optional adapter. The native documentation runner discovers Clojure fences, checks displayed results against isolated real tmux fixtures, and verifies resource ownership. Examples cover local filtering, Java predicates, links, layout creation, input/capture, cancellation, streaming, core.async, and CompletionStage interoperability.
  • Add complete Clojure programs: The common API catalog provides native CLI entry points, public imports, Tools.deps dependencies, and the private-server launcher. Programs traverse one capture, create sessions/windows, compare native and Java predicates, verify shell input through captured output, follow moved window identity, run bounded concurrent captures, observe output, and check command exit status. Clojure metadata uses its own catalog so the existing shared JVM importer retains its schema. Tests execute every native entry point, check its output, and resolve its public API targets. Streaming programs use the default upstream event bound and fail on reported gaps.
  • Preserve lifecycle outcomes: Structured Clojure errors retain typed Java closure and dispatch certainty. Managed callbacks reject wrapper I/O after runtime close, cancellation still permits local resource cleanup, and timed stream reads drain final queued output before publishing termination.
  • Include the Clojure artwork: Both module READMEs use the sibling centered logo/title/introduction layout, with module-local SVG/PNG assets and source-kit provenance.
  • Keep CI focused: Use Clojure 1.12.6 from the Gradle catalog on JDK 25/27. Live Clojure jobs test only the supported tmux endpoints, 3.3a and 3.7c, on Linux and macOS. General CI leaves the live Clojure suite to those endpoint jobs. Linux jobs validate native cache entries against the build environment before using them.

The tested README quick start creates a session, acquires one graph, and traverses it locally. The documentation fixture supplies the explicit socket property:

(require '[libtmux.core :as tm]
         '[libtmux.data :as data])

(let [socket-path (java.nio.file.Path/of
                   (System/getProperty "libtmux.docs.socket")
                   (make-array String 0))]
  (with-open [server (tm/open! {:socket-path socket-path})]
    (tm/new-session! server {:name "docs-quick-start"})
    (let [snapshot (tm/snapshot! server)
          session (first (filter #(= "docs-quick-start" (:session/name %))
                                 (:tmux/sessions snapshot)))]
      [(:session/name session)
       (count (data/windows session))
       (count (data/panes session))])))
;; => ["docs-quick-start" 1 1]

The Clojure guide explains which calls perform I/O, how edited maps differ from actionable refs, and how to close runtimes, subscriptions, and channel adapters.

Contract

  • Floor: Live operations use the Java core's connected-daemon checks and refuse unsupported versions before mutating dispatch. The supported tmux range is 3.3a through 3.7c; JDK 25 is required.
  • Control: control/attach! goes through Server.control and requires tmux 3.7 or newer before a client attaches. An older daemon raises :tmux/unsupported-feature, reported as not dispatched.
  • Streams: subscribe! accepts :encoding (:bytes or :utf-8), :capacity, :max-bytes, and :decoder-capacity. Queued unescaped payload defaults to 16 MiB. Each text subscription decodes preserved bytes per pane and resets after its own gaps, escaping malformed bytes as \xHH. next! reports gaps before the retained suffix, cumulative byte/event loss, and decoder resets. The single terminal map includes trailing partial text and preserves the original :cause; :reason distinguishes closure, completion, control exit, transport/protocol failure, and unknown EOF. A control exit does not prove daemon death.
  • Resources: with-open releases owned client/runtime resources. Closing the client does not kill the tmux daemon; optional adapters leave borrowed connections open. Acquisition and mutation end in !, while captured traversal stays local. Adapter events-channel closure rejects I/O inside dosync before changing lifecycle state. Raw Clojure resource constructor vars are private; public constructors establish ownership.

Verification (Linux/WSL)

  • Current Clojure module checks, namespace guards, test inventories, dependency metadata, and BOM coverage pass locally with Clojure 1.12.6 on JDK 25 and 27.

  • :integration-tests:clojureIntegrationTest passes on tmux 3.3a and 3.7c, including the executable Clojure documentation. The floor checks control refusal; the upper endpoint exercises control and streaming.

  • Repository documentation, Java Javadoc generation, the captured-server integration test, operation-reference freshness, Kotlin API validation, CI planner/check tests, and native-cache regressions pass locally. The split read-back test removes the reported pane before confirmation and checks the lost-target failure.

  • A copied Tools.deps consumer resolves locally installed library JARs and runs the complete API and capability programs through the copied launcher, including the optional core.async alias, with no library source directories or test classes on its classpath. Native documentation classifiers and the staged verifier have deliberate-break coverage for absent HTML, missing links, and Clojure-only sources.

  • Hosted workflows exercise JDK 25/27, the tmux endpoints, and macOS; the Checks tab records their current conclusions. Core and optional-adapter consumers pass through Tools.deps, Gradle, and Maven against the signed local stage on the supported runtime. Native binary, source, and documentation archives reproduce byte for byte. The native JARs include their module-local artwork and changelogs. No public registry installation or publication is claimed.

  • Indexed benchmarks compare Java/Clojure serial capture, bounded process workers, control, batches, and chains with the same output. Separate measurements cover acquisition, map conversion, local queries, fresh-JVM startup, observed output, cancellation, allocation and cleanup. Raw samples retain bounds and uncertainty; the results make no production-capacity claim.

Test plan

  • Clojure unit, namespace-guard and real-tmux suites on the supported tmux endpoints
  • Documentation examples run on the same lanes
  • Hosted JDK, tmux-version, and macOS matrix

@tony

tony commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Brings the draft Clojure facade in line with the Java core and feature changes (core minimum change: #69, optional features: #72). The PR stays a draft; its history is kept and this is a fast-forward push to clojure.

What changed

  • Merged the features branch (it carries the core branch and current master). The first merge took master's control line reader, ControlClient and EventSubscription, replacing this branch's byte subscriptions, UTF-8 decoder and termination reasons that master now provides. The history shows two merges because the features branch was rebased between them; the second kept this branch's Server additions. CapturedServer stays, reached through Server.captureServer because master gave capture() to a package-private reader. batchReservingCapacity and chainReservingCapacity now go through the admitted waiting path instead of the raw transport.
  • Facade: master's exception types, :tmux/unsupported-feature for a refused floor (reported not dispatched), Server.shell for run-shell!.
  • Control: attach! uses Server.control, so an older daemon than a stable 3.7 is refused before any client attaches. subscribe! keeps :encoding and :capacity; :max-bytes, :malformed and :max-panes are gone because the Java client decodes per pane. next! returns a :gap ahead of the events that remain and a terminal map with :closed or :ended plus the original cause. The core.async adapter drops the byte options and forwards gaps.
  • Build and CI: JDK 25 (the core's baseline; the facade's JDK 21 claim could not load the core), tmux matrix from 3.3a, dependency verification metadata for the Clojure artifacts, pinned actions, release attestation and version baselines required by master's documentation checks.

Verification (Linux/WSL, not the hosted matrix)

  • Clojure namespace load, namespace guard and unit tests for libtmux-clojure and libtmux-clojure-core-async via Gradle: pass.
  • :integration-tests:clojureIntegrationTest with -PlibtmuxTmuxExpected: 41 tests, 0 failures on tmux 3.3a, 3.5 and 3.7, and on master (next-3.9, which control refuses). Control scenarios run on stable 3.7+; every older daemon asserts the refusal with no attached client.
  • Negative control: reporting the floor refusal as :tmux/transport fails every control case on 3.3a.
  • :docs:test, clojureDependencyMetadata, platformCoversEveryPublishedModule, spotlessCheck, :libtmux:test: pass. The Java/Kotlin/Scala checks of the other PRs do not qualify this facade; hosted Clojure workflow results are not claimed here.

Not done: the diff against master still includes the unmerged core and feature commits until those PRs land. The hosted tmux 3.4/3.6/3.7x lanes and macOS are untested by me.

@tony

tony commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Update: the core branch was rebased onto master and the version grammar now follows the canonical vectors (candidates and development builds rank below their release), so control qualifies on any daemon at or above 3.7 (3.8-rc2 and next-3.8 included, next-3.7 and 3.7-rc2 excluded). The facade's control gate asks the Java core; a third merge of the features branch (rewritten again) is pushed as a fast-forward. Re-verified: Clojure namespace load, namespace guard, unit tests, documentation checks, dependency metadata and spotless pass, and :integration-tests:clojureIntegrationTest passes (41 tests, 0 failures) on tmux 3.3a, 3.5, 3.7 and master (next-3.9, where the control cases now run). Hosted Clojure workflow results are still not claimed.

@tony
tony changed the base branch from master to bump-min-tmux-001 October 17, 2026 14:44
@tony
tony force-pushed the bump-min-tmux-001 branch from 6988620 to e57f5e0 Compare October 17, 2026 14:57
@tony
tony force-pushed the bump-min-tmux-001 branch from e57f5e0 to e295b16 Compare October 17, 2026 15:44
@tony
tony force-pushed the clojure branch 2 times, most recently from dfea48c to ea138df Compare October 17, 2026 16:25
@tony
tony force-pushed the bump-min-tmux-001 branch from f8faa9f to e77e759 Compare October 17, 2026 18:53
@tony
tony force-pushed the bump-min-tmux-001 branch from e77e759 to 80f1754 Compare October 17, 2026 20:07
@tony
tony force-pushed the bump-min-tmux-001 branch from 80f1754 to f7a8e83 Compare October 17, 2026 22:26
@tony
tony force-pushed the bump-min-tmux-001 branch 15 times, most recently from 6d9297a to d41e105 Compare October 24, 2026 08:09
tony added 17 commits November 7, 2026 19:33
why: Text must follow the bytes this subscriber actually observed.
Loss and partial-character eviction need visible outcomes, and every
reader must see the same completed terminal map.

what:
- Apply byte bounds and incremental decoding after each reported gap
- Report decoder resets, trailing text and the Java terminal cause
- Fence stalled-consumer proofs after all subscriber offers
- Run byte, Unicode, failure and competing-reader regressions
why: Explicit close must release a subscription's queued payload even
when the control client ended first. Keeping that buffer prevents a
stalled adapter from completing shutdown without consuming it.

what:
- Discard buffered events on close after an earlier remote end
- Preserve the first terminal cause and existing loss counters
- Verify release without replacing termination or counting new loss
why: Stopping an adapter blocked on a channel put replaced observed
upstream loss with zero and discarded the decoder's incomplete tail.

what:
- Read the final owned outcome after closing its subscription
- Preserve upstream loss, cause and trailing text separately
- Check stop during pending puts, UTF-8 tails and remote termination
- Make the live anchor follow the adapter's subscription
why: A rejected transactional close marked the adapter stopped before
releasing its subscription, so a later ordinary stop skipped cleanup.

what:
- Reject I/O before changing events-channel or adapter state
- Keep resource factory vars private and expose observe! for ownership
- Reproduce the failed stop and verify subsequent resource release
why: Clojure callers need native arities, ownership contracts and source
links; Java reference pages do not describe the facade's vars.

what:
- Generate each module's reference from public source metadata
- Reject stale output in module checks without regenerating it
- Document resource and creation options through ordinary docstrings
- Keep raw task and runtime factories private
- Link the native references from READMEs and guide navigation
why: Readers need complete programs with imports, entry points and an
install recipe, rather than excerpts that depend on unseen fixtures.

what:
- Add eight Clojure programs and a Tools.deps consumer configuration
- Reuse the private-server launcher and source-owned API manifest
- Execute native entry points and check output, imports and targets
- Validate consumer dependency versions against the current baseline
- Hint open!'s return type for reflection-free with-open calls
why: Readers need complete programs for layout, movement, task cleanup
and observation as well as the basic collection and creation calls.

what:
- Add workspace, bounded capture, command and observation programs
- Execute control refusal examples on the oldest supported tmux
- Keep core.async behind an explicit consumer dependency alias
- Validate output and cleanup through installed artifacts
- Bound demo JVM memory and report CLI verification durations
why: The shared JVM importer rejects unknown variants before selecting
a language. Clojure entry points would break existing port imports.

what:
- Give native namespaces and Tools.deps their own source-owned catalog
- Restore the shared JVM catalog and its inventory contract
- Validate native setup files, language and source destinations
why: Empty documentation classifiers omit the native API, and the
release verifier rejects archives containing only Clojure sources.

what:
- Generate native HTML with signatures and offline source links
- Package references in each module's javadoc classifier
- Accept Clojure sources while rejecting resource-only archives
why: Standalone failures otherwise send stream status and causes to
uncollected temporary reports, preventing hosted failure diagnosis.

what:
- Send complete child Clojure exception reports to stderr
why: Related operations and ownership rules need navigable native
references at the REPL and in both documentation formats.

what:
- Link public vars and namespaces with Clojure wikilinks
- Resolve authored references and preserve inline code and punctuation
- Bundle linked dependency documentation for offline navigation
- Order documentation checks after explicit reference updates
why: The generated documentation index has an empty title and heading.
what:
- Link the optional adapter directly to the documented core.async namespace
why: A terminal line can arrive as more than eight events before a reader runs.
what:
- Remove undersized event bounds from the output and core.async examples
- Preserve gap checks, byte bounds and deadlines
why: A fast process can survive the confirming read and make a race assertion fail.
what:
- Remove the newly created pane between its reply and the confirming snapshot
- Decode its reported identity with the existing RowFormat helper
- Preserve the TargetGoneException contract assertion
why: General CI also ran the Clojure live suite against distro tmux outside its endpoint policy.
what:
- Exclude the duplicate live suite from the general check command
- Retain module checks and the ten dedicated Clojure compatibility jobs
why: Keep the deterministic lost-pane test aligned with Spotless.

what:
- Apply the configured Java formatter to the exception assertion
why: Cancellation must stop later tmux effects without preventing owned
resources from closing. Final queued output and dispatch certainty must
survive connection shutdown.

what:
- Fence managed continuations and allow local closure during cancellation
- Drain terminated subscriptions before caching their final outcome
- Preserve typed closure outcomes and command-group positions
- Cover shutdown races and clarify the example's fixture socket input
tony added 2 commits November 7, 2026 19:55
why: Clojure companion READMEs lack the logos used by JVM siblings.

what:
- Add module-local Clojure SVG and PNG marks
- Center their README titles and record artwork provenance
why: The Clojure module READMEs need the same recognizable artwork
and centered introduction as the sibling JVM packages.

what:
- Add module-local SVG and PNG logos with source artwork provenance
- Center the README titles and introductions while preserving examples
- Include the supplied logos through the existing JAR resource convention
@tony
tony marked this pull request as ready for review November 8, 2026 02:09
tony added a commit that referenced this pull request Nov 8, 2026
why: Readers need the shipped capabilities rather than the sequence of
development fixes.

what:
- Describe native values, owned tasks and streams, and optional core.async
- Record captured Java graphs, output limits, native docs, and Gradle CI
- Link the consolidated Unreleased entries to PR #20
tony added 2 commits November 7, 2026 20:32
why: Cold JVM startup can exhaust the resource probe deadline before
the namespace guard runs on a hosted runner.

what:
- Initialize JDK resource classes before arming each probe
- Check every intercepted operation in the existing test JVM
- Keep cold public namespace loading under its independent guard
why: Readers need the shipped capabilities rather than the sequence of
development fixes.

what:
- Describe native values, owned tasks and streams, and optional core.async
- Record captured Java graphs, output limits, native docs, and Gradle CI
- Link the consolidated Unreleased entries to PR #20
@tony
tony merged commit 873f3ac into master Nov 8, 2026
28 checks passed
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