# 06 - Public API And Refactoring (https://jackin.tailrocks.com/reference/research/shared-tui-extraction/06-public-api-and-refactoring/)



# 06 - Public API And Refactoring [#06---public-api-and-refactoring]

The extraction must produce a deliberately small library API, not preserve every current `pub` item. The donor root and component facade currently contain 39 public module declarations, 29 public re-export statements, and 112 public declaration/re-export lines in total. This count was produced on 2026-07-15 with `rg` over <RepoFile path="crates/jackin-tui/src/lib.rs">crates/jackin-tui/src/lib.rs</RepoFile> and <RepoFile path="crates/jackin-tui/src/components.rs">crates/jackin-tui/src/components.rs</RepoFile>. It measures declaration lines rather than unique Rust API items, but it demonstrates that the existing root is already a large compatibility surface.

At donor revision `33896a504e19ef13adb8692550c1845cb86a9504`, the two donor source trees contain 77 Rust files and 17,033 lines, measured with `find ... -name '*.rs'` and `wc -l`. There are 246 `#[test]` or `#[tokio::test]` attributes in `jackin-tui`, 15 in the lookbook, and 195 Rust consumer files outside the donor crates that reference `jackin_tui`. The migration therefore needs an API inventory and compatibility program, even though both projects are pre-release.

## Target dependency layers [#target-dependency-layers]

```text
consumer domain models and effects
              |
              v
termrock::crossterm optional integration
  crossterm event conversion + terminal session ownership
              |
              v
termrock components
  widgets + interaction state + semantic theme
              |
              v
termrock foundations
  text + input + focus + geometry + scrolling
              |
              v
ratatui-core

termrock-lookbook -> termrock + crossterm adapter + SVG renderer
docs             -> termrock-lookbook generated catalog
```

Ratatui 0.30 split its workspace so widget-library authors can depend on `ratatui-core` for fewer dependencies and a narrower API surface, while applications may continue to use the umbrella `ratatui` crate. The Ratatui documentation explicitly recommends `ratatui-core` for widget-library authors. [`termrock` should follow that boundary](https://docs.rs/ratatui/0.30.2/ratatui/index.html#crate-organization): the base crate renders into Ratatui buffers and does not initialize a terminal.

The initial manifest should expose only one optional integration feature:

```toml
[features]
default = []
crossterm = ["dep:crossterm", "dep:ratatui-crossterm"]
```

The dependency split is fixed. The initial tested cell comes from the donor lock: `ratatui-core` 0.1.2 in `termrock`, `ratatui-crossterm` 0.1.2 plus `crossterm` 0.29.0 behind the feature, and umbrella `ratatui` 0.30.2 only where the lookbook/application examples need it. Manifest requirements may use compatible ranges, but <RepoFile path="Cargo.lock" /> and `compatibility.toml` record that exact cell, and the graph must contain no duplicate Ratatui core or Crossterm version. Other focused dependency patch versions remain bounded Stage 2 implementation finalization under Decision 17. The fixed policy is:

* base `termrock` contains no Tokio, async runtime, stdout/stderr, filesystem, network, process, telemetry, or secret dependency;
* Crossterm types do not appear in the default API; conversion from Crossterm events and terminal-session ownership are feature-gated adapters;
* `termrock-lookbook` enables the adapter because its interactive CLI owns a real terminal;
* add no `serde`, `tokio`, or testing feature until a concrete second consumer requires the same contract.

Cargo features unify across a dependency graph and should be additive. Optional dependencies should be named through `dep:` so dependency names do not accidentally become permanent public feature names. Removing or changing a feature can be a compatibility break, so `default = []` keeps the foundation predictable. See the [Cargo feature reference](https://doc.rust-lang.org/cargo/reference/features.html) and [Cargo SemVer guidance](https://doc.rust-lang.org/cargo/reference/semver.html#cargo).

## Terminal capability baseline [#terminal-capability-baseline]

`termrock` targets modern truecolor terminals (Ghostty-class). Assumed capabilities: 24-bit SGR color, alternate screen, SGR mouse reporting, OSC 8 hyperlinks, OSC 22 pointer shapes, and OSC 52 clipboard write. Components emit truecolor styles unconditionally; the library contains no 256/16-color fallback, `NO_COLOR` handling, light-background adaptation, RTL/BiDi shaping, or capability-detection code. A consumer that needs degradation implements it above the library. The supported-terminal statement ships in the README and the component catalog. Terminal size is explicitly not a capability assumption: tiny and empty rectangles remain valid, panic-free inputs, and the minimum/normal/wide render gates stay required.

## Typed terminal requests [#typed-terminal-requests]

The modern-terminal baseline makes OSC 8 hyperlinks, OSC 22 pointer shapes, and OSC 52 clipboard writes first-class library concerns, but rendering stays side-effect free. The donor's current shape — the error and container-info dialogs returning raw overlay byte vectors for the host loop to write after the frame — is replaced by one typed surface in the `osc` module:

* widgets return typed link regions (stable ID, rect, URL) derived from the same layout function that painted them;
* pointer-shape and clipboard-write requests are plain data values;
* pure encoders turn a request into escape bytes without performing any I/O;
* the consumer decides whether, when, and where to emit — after the Ratatui frame, through the Capsule socket protocol, or not at all, according to its own URL/clipboard policy.

Because the encoders are pure and the regions come from the shared layout function, both are unit-testable and parity-checkable even though buffer and SVG fixtures cannot capture post-frame bytes.

## Base API and Crossterm convenience API [#base-api-and-crossterm-convenience-api]

The `crossterm` feature is first-class support, not a separate widget system. It
adds a convenient integration layer over the complete base API:

| Available without features                                  | Added by `crossterm`                                                               |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| all widgets and widget builders                             | Crossterm event-to-logical-input conversion                                        |
| component interaction states, logical actions, and outcomes | key, mouse, resize, focus, and paste adapters where supported                      |
| semantic themes and component style overrides               | `CrosstermBackend` construction/re-export through the selected Ratatui integration |
| display-width, layout, hit-test, scroll, and text helpers   | partial-initialization-safe managed terminal session/builder                       |
| `Widget`/`StatefulWidget` rendering into Ratatui buffers    | raw mode, alternate screen, cursor, and mouse-capture setup/restoration options    |
| direct `Buffer` and `TestBackend` testing                   | concise reference helpers for a normal Crossterm application                       |

Conceptually, consumers choose between these paths:

```rust
// No TermRock feature: use every component with Ratatui buffers or a backend
// selected and managed entirely by the application.
use termrock::{style::Theme, widgets::Tabs};

// With `features = ["crossterm"]`: the same components plus adapters and a
// managed session that removes repetitive terminal setup/cleanup.
use termrock::crossterm::{CrosstermSession, SessionOptions, adapt_event};
use termrock::{style::Theme, widgets::Tabs};
```

The names are illustrative until the API review, but the layering is fixed:

* enabling `crossterm` must not change a component type, default style,
  rendering result, action, outcome, or module path;
* no widget, theme, layout primitive, or logical input type may be guarded by
  `#[cfg(feature = "crossterm")]`;
* the feature may add `From`/`TryFrom` implementations, adapter functions,
  backend/session types, and convenience constructors only;
* a consumer may enable the feature solely for event conversion while managing
  its terminal independently;
* the managed session is optional even when the feature is enabled;
* no convenience API installs a panic hook, signal handler, logger, Tokio
  runtime, or application event loop;
* `default-features = false` remains a fully documented and tested way to use
  every TermRock component with Ratatui.

This follows the separation already present in Ratatui: the
[`ratatui-crossterm` integration](https://docs.rs/ratatui-crossterm/latest/ratatui_crossterm/)
implements Ratatui's backend contract, while widgets render independently of
that backend.

## Proposed public modules [#proposed-public-modules]

| Module        | Owns                                                                                                                                                                 | Must not own                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `text`        | display width, clipping, terminal-control sanitization, bounded lines                                                                                                | paths, URLs, command output policy                                                          |
| `input`       | logical keys, modifiers, chords, bindings, action dispatch                                                                                                           | product commands or hardcoded action wording                                                |
| `interaction` | focus, hover, selection, modal outcomes                                                                                                                              | application navigation or root model                                                        |
| `layout`      | responsive dialog specifications, hit-test geometry, bottom regions                                                                                                  | role/auth/mount/database-specific rectangles                                                |
| `scroll`      | offsets, axes, viewport math, cursor following, scrollbar models                                                                                                     | product content construction                                                                |
| `style`       | semantic roles, `Theme`, builders, Tailrocks phosphor preset                                                                                                         | global product constants or capsule/status meanings                                         |
| `widgets`     | borrowed render models and explicit stateful widgets                                                                                                                 | I/O, effects, domain records, global process state                                          |
| `osc`         | typed hyperlink/pointer/clipboard request values and pure OSC 8/22/52 encoders                                                                                       | emission timing, stdout writes, clipboard policy, URL-opening decisions                     |
| `runtime`     | executor-neutral `Dirty`/`UpdateResult`/`Subscription`/`Component`/`View` contracts plus the `drive_frame`/`drive_render` frame drivers; base module optional by use | Tokio, spawning, effect execution, panic/signal policy, any requirement that widgets use it |
| `crossterm`   | optional event adapters, backend convenience, and scoped session guard                                                                                               | widgets, Tokio, signal policy, panic-hook installation, logging initialization              |

Do not add a broad `prelude` or glob re-export in the first release. Callers should import from the owning module so the library can distinguish foundational API from component internals. Private render helpers remain private. A root re-export is allowed only for the few entry types consumers use on nearly every screen, such as `Theme`.

## Component contract [#component-contract]

Ratatui recommends implementing widgets by reference for reuse. New components should implement `Widget for &Component` for stateless rendering and `StatefulWidget for &Component` when Ratatui-managed interaction state is genuinely required. Do not expose the current mixture of free `render_*` functions, `Frame`-specific wrappers, and consuming widgets as three equivalent public paths. See Ratatui's [custom widget guidance](https://docs.rs/ratatui/0.30.2/ratatui/widgets/index.html#authoring-custom-widgets).

Use this shape:

```rust
pub struct Tabs<'a, Id> {
    items: &'a [Tab<'a, Id>],
    theme: &'a Theme,
}

pub struct TabsState<Id> {
    selected: Option<Id>,
    hovered: Option<Id>,
    focused: bool,
}

impl<Id> StatefulWidget for &Tabs<'_, Id> {
    type State = TabsState<Id>;

    fn render(self, area: Rect, buffer: &mut Buffer, state: &mut Self::State) {
        // Paint from borrowed items and explicit interaction state.
    }
}
```

The code is illustrative, not a frozen signature. The required properties are:

* render data is borrowed where practical rather than cloned into a component every frame;
* stable consumer IDs cross the interaction boundary instead of array indices;
* interaction state is separate from domain data;
* rendering has no side effects and never changes domain state;
* builders keep fields private so a field can be added without breaking every struct literal;
* public enums that may grow are `#[non_exhaustive]`, with consumers required to handle unknown variants;
* hit-test geometry is derived by the same layout function used to paint the component;
* a component does not retain callbacks, views, executors, or product objects.

`WidgetRef` and `StatefulWidgetRef` remain unstable Ratatui features in the researched release. `termrock` should use the stable `Widget for &T` / `StatefulWidget for &T` pattern and avoid making an unstable Ratatui feature part of its contract.

## State, input, and effects [#state-input-and-effects]

The donor's `Component<Ev, Msg>`, `View<Model>`, `Dirty`, `ModalOutcome<T>`, and typed `UpdateResult<E>` concepts are useful, but they should not become mandatory widget machinery.

Ratatui's [Elm Architecture guidance](https://ratatui.rs/concepts/application-patterns/the-elm-architecture/) separates model, message-driven updates, and a side-effect-free view. Its [Component Architecture guidance](https://ratatui.rs/concepts/application-patterns/component-architecture/) describes a valid application-level alternative that co-locates event handling, update, and rendering. TermRock must remain compatible with both without choosing either for the consumer: it provides component interaction state, logical actions, pure updates, typed outcomes, and standard widgets, while the consumer owns the root model/message loop and effect execution. The complete per-component application of this decision is in [09 - Component redesign catalog](/reference/research/shared-tui-extraction/09-component-redesign-catalog/).

The initial contract has three independent levels:

1. **Rendering:** standard Ratatui `Widget`/`StatefulWidget` implementations.
2. **Interaction:** component-specific `handle_action` methods over logical actions, returning a small outcome such as `Ignored`, `Changed`, `Submitted(Id)`, or `Cancelled`.
3. **Application effects:** consumer-owned update code converts outcomes into product messages and executes I/O.

Raw terminal events are adapted before component dispatch:

```text
Crossterm KeyEvent ----+
                       +--> termrock::input::KeyChord --> consumer keymap --> Action
capsule byte decoder --+
```

This keeps the same widgets usable in `jackin❯` host surfaces and the in-container multiplexer without forcing every surface to receive Crossterm events. Key bindings own chords and actions; visible hint labels are consumer-provided data derived from that binding table. The library must not hardcode English labels such as `select`, `save`, or `cancel` inside state objects.

The executor-neutral runtime contracts — `Dirty`, `UpdateResult<E>`, `SubscriptionPoll`, the `Subscription` trait with std-channel and closure adapters, `Component<Ev, Msg>`, and `View<Model>` — plus the persistent `drive_frame` and one-shot closure-based `drive_render` drivers extract into the ordinary base `runtime` module. It is optional by use, not a Cargo feature: the initial manifest still has only the `crossterm` feature, no widget references `runtime`, and no architecture example requires it. The contracts and both draw drivers are pure Ratatui; `drive_render` adapts a short-lived renderer onto `drive_frame` rather than introducing a competing loop. `jackin❯`'s canonical TUI rules already mandate the one shared frame driver across its console, Capsule, and launch surfaces, and deleting the donor would otherwise strand these drivers in a new product-local crate. The module stays convenience, not framework, and future consumers are not required to adopt it. The Tokio receiver implementations, spawn helpers, and fallback runtime in <RepoFile path="crates/jackin-tui/src/runtime.rs">crates/jackin-tui/src/runtime.rs</RepoFile> stay in `jackin❯` with `jackin-console` as the target home; Rust coherence prevents a consumer crate from implementing the extracted `Subscription` trait directly for Tokio receiver types, so the shared module ships a subscription closure adapter and consumers may wrap receivers in local newtypes. No fallback Tokio runtime is created inside the component library.

## Theme API [#theme-api]

The current palette exposes product constants for rain animation, Capsule menu states, debug chips, status tabs, and links from <RepoFile path="crates/jackin-tui/src/lib.rs">crates/jackin-tui/src/lib.rs</RepoFile>. That is a product stylesheet, not a reusable theme API.

`Theme` should have private fields and a builder/default path. For the initial revision line, both `Theme::default()` and a new builder start from `Theme::tailrocks_phosphor()`; consumers opt into another palette or override tokens explicitly. Components request semantic roles rather than palette names:

```text
canvas / surface / elevated / backdrop
text / text_muted / text_disabled
border / border_focused / selection / focus
accent / success / warning / danger / info
link / link_hover
input / input_invalid
scroll_track / scroll_thumb
```

Do not expose a trait with one required method per token: adding a token would break implementors. A private representation with `Theme::style(Role)` and a `ThemeBuilder` can add a role with a documented fallback. `Theme::tailrocks_phosphor()` preserves the established look without making `PHOSPHOR_GREEN` the contract of every widget. Changing a default token is a behavioral API change that requires regenerated renders and a migration note.

Component-specific style overrides are data passed to that component, not new global constants. All states also require a non-color signal: glyph, border, label, modifier, or position. Color cannot be the only indication of focus, selection, error, or progress. The donor violates this today (panel focus is a color-only border change); the fix is a recorded quality-tier item that lands after `jackin❯` parity, not during extraction.

Parity constrains the refactor in three specific ways: `Theme::tailrocks_phosphor()` must reproduce the donor's exact RGB values so migrated fixtures stay byte-identical; the `faded` alpha helper keeps its exact scaling math; and the lookbook SVG writer derives its color table from the same theme values instead of the duplicated hex table it hardcodes today, with a test asserting the committed SVGs are unchanged by that consolidation. Product tokens — `CAPSULE_MENU_*`, `BRAND_BLOCK`, `DEBUG_AMBER`, `TAB_BG_*`, and the rain colors — never enter `Theme`; `jackin❯` keeps them as local constants layered on top of the semantic tokens.

## Terminal ownership [#terminal-ownership]

Crossterm's raw mode, alternate screen, and mouse capture are separate operations, and its APIs require explicit disable/leave operations. See the Crossterm [terminal module](https://docs.rs/crossterm/0.29.0/crossterm/terminal/index.html) and [event module](https://docs.rs/crossterm/0.29.0/crossterm/event/index.html).

The optional `crossterm` integration should provide a scoped session with these rules:

* record each mode only after it was successfully enabled;
* if setup fails halfway, restore the already-enabled modes before returning the error;
* expose an explicit fallible `restore()` used on the normal path;
* use `Drop` only as an idempotent best-effort fallback because `Drop` cannot report restoration failure;
* restore modes in reverse ownership order and always attempt every cleanup step;
* support alternate-screen and inline modes through explicit options;
* never install a global panic hook, signal handler, logger, or diagnostics route from the library;
* never use product-global atomics such as the current rich-surface/host-screen flags in <RepoFile path="crates/jackin-tui/src/ownership.rs">crates/jackin-tui/src/ownership.rs</RepoFile>.

The application root owns panic/signal integration and calls the scoped restore operation. `termrock-lookbook` provides a complete reference integration and PTY tests. Crossterm event conversion is pure; event polling strategy remains consumer-owned.

## Donor refactoring ledger [#donor-refactoring-ledger]

| Donor area                             | Required action                                                                                                                                                      | Target                                         |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `theme.rs` and root RGB constants      | replace product constants with private semantic theme + phosphor preset                                                                                              | `style`                                        |
| `geometry.rs`                          | split display/text/tab math from `agent_display_name`                                                                                                                | `text`, `layout`; agent mapping stays local    |
| `jackin_core::shorten_home` re-export  | remove from the donor facade; retain the environment-aware helper in `jackin-core` for TUI and non-TUI callers                                                       | `jackin-core`                                  |
| `keymap.rs`                            | retain logical chord/action mapping; move action labels to consumer data                                                                                             | `input`                                        |
| `scroll.rs`, scrollable-panel math     | consolidate one scroll model and one display-width source, retaining the `tui-scrollbar` metrics dependency as the single thumb-math source                          | `scroll`, `widgets`                            |
| `runtime.rs`                           | extract executor-neutral contracts and the pure Ratatui `drive_frame`/`drive_render` drivers into the base module, optional by use; Tokio adapters stay in `jackin❯` | `runtime`; Tokio helpers stay local            |
| root `PointerShape`, OSC 22/52 helpers | extract as typed requests plus pure encoders; emission stays consumer-owned                                                                                          | `osc`                                          |
| lookbook SVG color table               | derive from the shared theme values; assert committed SVGs unchanged                                                                                                 | `termrock-lookbook`                            |
| `terminal_modes.rs`                    | refactor into partial-init-safe optional Crossterm session                                                                                                           | `crossterm`                                    |
| `ownership.rs`                         | do not extract product-global terminal/diagnostics policy                                                                                                            | `jackin❯`                                      |
| `host_colors.rs`                       | retain locally until a second consumer needs OSC 10/11 discovery                                                                                                     | consumer adapter                               |
| `ansi.rs`                              | split pure parsing/sanitization from banners, version/help, and direct output                                                                                        | `text`; product output stays local             |
| `animation.rs`, `output.rs`            | do not extract launch/container behavior or direct output                                                                                                            | `jackin❯`                                      |
| `BrandHeader`                          | keep `jackin❯` wordmark composition local; extract a neutral header bar only through separately scoped future demand                                                 | consumer first                                 |
| `ContainerInfo` / `DebugInfo`          | replace generic row rendering with `DetailTable`; keep container/version builder local                                                                               | `widgets` + consumer facade                    |
| confirm dialog                         | extract generic state/widget; move exit wording and data-loss constructors local                                                                                     | `widgets`                                      |
| modal rectangles                       | replace role/auth/source/mount modes with min/preferred/max dialog specification                                                                                     | `layout`                                       |
| status footer                          | redesign as data-driven left/right slots with stable IDs; keep usage/container/run policy local                                                                      | `widgets` + consumer facade                    |
| select list                            | separate rows from state, use stable IDs, pluggable filtering, display-width measurement, configurable hints                                                         | `widgets`                                      |
| text input                             | use grapheme/display-column cursor rules, external validation, configurable actions                                                                                  | `widgets`                                      |
| diff view                              | make render/update boundary explicit; no hidden content mutation in a free render function                                                                           | `widgets`                                      |
| URL and OSC overlays                   | return typed regions/requests through `osc`; consumer decides whether policy permits emission                                                                        | `osc` types; emission stays a consumer adapter |

The existing `SelectListState` in <RepoFile path="crates/jackin-tui/src/components/select_list.rs">crates/jackin-tui/src/components/select\_list.rs</RepoFile> demonstrates why refactoring precedes publication: it owns `Vec<String>`, returns indices, performs fixed ASCII substring filtering, measures labels with `chars().count()`, and embeds action labels. The existing `TextField` in <RepoFile path="crates/jackin-tui/src/components/text_input.rs">crates/jackin-tui/src/components/text\_input.rs</RepoFile> combines editing with product validation and action wording. Those behaviors are valuable donor implementations, but their current signatures are not the reusable API.

Chapter 09 expands this ledger across every current component implementation file and the supporting geometry, input, theme, runtime, terminal, ANSI, URL, output, and animation modules. Its classification is the required source for the generated extraction ledger; the shorter table above is only the dependency-level summary.

## Public API review gate [#public-api-review-gate]

Before the first immutable consumer revision:

* generate and review a public-item inventory for `termrock`;
* deny missing documentation and broken intra-doc links;
* run `cargo-semver-checks` against the previous tag after the first tagged baseline;
* require a migration note for public renames, moves, changed defaults, feature changes, and MSRV changes;
* prohibit public consumer dependencies, glob exports, mutable globals, hidden I/O, and executor-specific types;
* verify no widget returns raw escape bytes; the typed `osc` requests and pure encoders are the only escape-producing surface;
* verify the default-feature crate builds without Crossterm or Tokio in `cargo tree`;
* compare base and `crossterm` render fixtures and component API inventories so enabling the feature cannot change or hide a widget;
* use `cargo package --list` to review exactly what a registry package would contain, even while Git revisions remain the distribution path.

`cargo-semver-checks` detects many Rust API changes, but it cannot prove behavioral, keyboard, rendering, or feature-default compatibility. The deterministic lookbook and conformance suites remain independent required gates. Cargo's own [SemVer reference](https://doc.rust-lang.org/cargo/reference/semver.html) is the policy baseline.
