MingaEditor.Frontend.Manager (Minga v0.1.0)

Copy Markdown View Source

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 from Minga.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

admission()

@type admission() :: :accepted | :unwritable

Non-suspending frontend transport admission result.

backend()

@type backend() :: :tui | :gui

Renderer backend.

lifecycle_admission()

@type lifecycle_admission() :: :accepted | :unwritable | :disconnected

start_opt()

@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.

state()

Internal state.

Functions

capabilities(server \\ __MODULE__)

Returns the frontend's reported capabilities.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

output_pressure(server \\ __MODULE__)

Returns bounded output-pressure diagnostics.

ready?(server \\ __MODULE__)

@spec ready?(GenServer.server()) :: boolean()

Returns whether the renderer has sent its ready signal.

reserve_recovery_generation(server \\ __MODULE__)

@spec reserve_recovery_generation(GenServer.server()) :: pos_integer()

Atomically reserves the next recovery generation for the live frontend connection.

send_commands(server \\ __MODULE__, commands)

@spec send_commands(GenServer.server() | nil, [binary()]) :: admission()

Attempts non-suspending admission of encoded commands to the renderer.

send_lifecycle_command(server \\ __MODULE__, command)

@spec send_lifecycle_command(GenServer.server() | nil, binary()) ::
  lifecycle_admission()

Attempts one lifecycle command without retaining it after rejection.

send_render_commands(server \\ __MODULE__, commands)

@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.

start_link(opts \\ [])

@spec start_link([start_opt()]) :: GenServer.on_start()

Starts the port manager.

subscribe(server \\ __MODULE__)

@spec subscribe(GenServer.server()) :: :ok

Subscribes the calling process to receive input events.

terminal_size(server \\ __MODULE__)

@spec terminal_size(GenServer.server()) :: {pos_integer(), pos_integer()} | nil

Returns the terminal size as {width, height}, or nil if not yet ready.

tty_path_for(tty_name)

@spec tty_path_for(String.t()) :: String.t() | nil

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.