Generics, Traits, and Runtime Polymorphism
Generic buses preserve instruction types
Bus<T> can receive one or more channels carrying exactly T, while sharing
the same selection, timeout, OS-access, and sender plumbing. A Screen bus
cannot accidentally receive a PTY instruction. The generic disappears after
monomorphization; no dynamic dispatch is needed for the protocol loop.
Trait objects unify unlike panes
A tab stores Box<dyn Pane> because terminal panes and plugin panes coexist
in the same tiled and floating collections. The common trait exposes geometry,
rendering, input, focus, scrolling, and lifecycle behavior. Which concrete pane
occupies a layout slot is runtime state.
This is a natural trait-object boundary: one heterogeneous collection needs
uniform behavior. Making Tab<T: Pane> generic would allow only one pane type
per tab or require a second enum mirroring all pane variants.
OS behavior is injected behind traits
ServerOsApi, ClientOsApi, and AsyncReader isolate Unix, Windows, tests,
and held-pane behavior. Owners hold boxed trait objects because the selected
platform implementation is runtime infrastructure, not a parameter readers
should carry through every terminal method.
Closures carry one-off lifecycle policy
PTY spawning accepts callbacks for child exit. The closure owns the originating plugin information, hold-on-close policy, and typed senders needed later. This is generics and capture-based composition where defining a permanent trait implementation would add ceremony without creating a reusable domain type.
Typed messages versus erased services
Zellij keeps message payloads concrete in enums, uses generics for reusable plumbing, and uses trait objects at heterogeneous model/platform boundaries. That division keeps the concurrency graph readable while still supporting terminal panes, plugins, multiple operating systems, and test doubles.