GenServer that manages the frontend renderer Port.
Operates in two modes depending on the MINGA_PORT_MODE env var:
Spawn mode (default): BEAM is the parent process. Port.Manager spawns the GUI/TUI binary as a child via
Port.open({:spawn_executable, ...}). Used in development, TUI mode, and Burrito releases.Connected mode (
MINGA_PORT_MODE=connected): BEAM is a child of the GUI process. The GUI set up stdin/stdout pipes before launching us. Port.Manager opens{:fd, 0, 1}as a Port instead of spawning a child. Used when launching fromMinga.app(Finder, Spotlight, Dock).
Both modes use identical {:packet, 4} framing. The protocol layer (event decoding, render commands, subscriber broadcasting) is the same.
Output admission always uses Port.command/3 with :nosuspend. If the transport is unwritable, the manager retains one current frame and one latest coalesced replacement and retries within a short budget. An expired frame-only interval requests correlated keyframe recovery. An expired interval with retained controls terminates the failed transport instead of discarding one-shot effects. Synchronous admission calls keep frame binaries out of the manager mailbox while leaving the process free to receive frontend input between attempts.
Subscribers register via subscribe/1 and receive messages as:
{:minga_input, event}where event is a MingaEditor.Frontend.Protocol.input_event().
Summary
Types
Non-suspending frontend transport admission result.
Renderer backend.
Options for starting the port manager.
Internal state.
Functions
Returns the frontend's reported capabilities.
Returns a specification to start this module under a supervisor.
Returns bounded output-pressure diagnostics.
Returns whether the renderer has sent its ready signal.
Atomically reserves the next recovery generation for the live frontend connection.
Attempts non-suspending admission of encoded commands to the renderer.
Attempts one lifecycle command without retaining it after rejection.
Like send_commands/2 but stamps the synchronous admission request with a monotonic send time. The receiver emits a [:minga, :render, :hop_latency] (hop: :send_commands) sample measuring the Renderer.Server to Port.Manager scheduling delay. Used only for the per-frame render batch on the keystroke path.
Starts the port manager.
Subscribes the calling process to receive input events.
Returns the terminal size as {width, height}, or nil if not yet ready.
Builds a /dev/ path from the tty name returned by ps -o tty=.
Types
@type admission() :: :accepted | :unwritable
Non-suspending frontend transport admission result.
@type backend() :: :tui | :gui
Renderer backend.
@type lifecycle_admission() :: :accepted | :unwritable | :disconnected
@type start_opt() :: {:name, GenServer.name()} | {:renderer_path, String.t()} | {:backend, backend()} | {:port_mode, MingaEditor.Frontend.Manager.State.port_mode()} | {:port_opener, (term(), [term()] -> port())} | {:port_commander, MingaEditor.Frontend.Manager.State.port_commander()} | {:output_retry_ms, pos_integer()} | {:output_failure_ms, non_neg_integer()} | {:tty_path, String.t() | nil}
Options for starting the port manager.
@type state() :: MingaEditor.Frontend.Manager.State.t()
Internal state.
Functions
@spec capabilities(GenServer.server()) :: MingaEditor.Frontend.Capabilities.t()
Returns the frontend's reported capabilities.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec output_pressure(GenServer.server()) :: MingaEditor.Frontend.Manager.OutputPressure.stats()
Returns bounded output-pressure diagnostics.
@spec ready?(GenServer.server()) :: boolean()
Returns whether the renderer has sent its ready signal.
@spec reserve_recovery_generation(GenServer.server()) :: pos_integer()
Atomically reserves the next recovery generation for the live frontend connection.
@spec send_commands(GenServer.server() | nil, [binary()]) :: admission()
Attempts non-suspending admission of encoded commands to the renderer.
@spec send_lifecycle_command(GenServer.server() | nil, binary()) :: lifecycle_admission()
Attempts one lifecycle command without retaining it after rejection.
@spec send_render_commands(GenServer.server() | nil, [binary()]) :: admission()
Like send_commands/2 but stamps the synchronous admission request with a monotonic send time. The receiver emits a [:minga, :render, :hop_latency] (hop: :send_commands) sample measuring the Renderer.Server to Port.Manager scheduling delay. Used only for the per-frame render batch on the keystroke path.
@spec start_link([start_opt()]) :: GenServer.on_start()
Starts the port manager.
@spec subscribe(GenServer.server()) :: :ok
Subscribes the calling process to receive input events.
@spec terminal_size(GenServer.server()) :: {pos_integer(), pos_integer()} | nil
Returns the terminal size as {width, height}, or nil if not yet ready.
Builds a /dev/ path from the tty name returned by ps -o tty=.
The format varies by OS and version:
- macOS long form:
"ttys008"→"/dev/ttys008" - macOS short form:
"s003"→"/dev/ttys003" - Linux:
"pts/3"→"/dev/pts/3"
Checks if /dev/{name} exists first (handles long form and Linux).
Falls back to /dev/tty{name} for short forms.
Returns nil when the process has no controlling terminal. ps -o tty=
reports this as all question marks: "??" on macOS, "?" on Linux. Without
this guard we would build a bogus path like /dev/tty?, which the Go renderer
cannot open and crashes on. Returning nil lets the renderer fall back to
/dev/tty.