Binary protocol encoder/decoder for GUI chrome commands (BEAM → Swift/GTK).
This module handles the structured data protocol for native GUI elements:
tab bars, file trees, which-key popups, completion menus, breadcrumbs,
status bars, pickers, agent chat, and theme colors. These are separate
from the TUI cell-grid rendering commands in MingaEditor.Frontend.Protocol.
GUI Chrome Commands (BEAM → Frontend)
Contiguous range 0x70-0x7F for easy classification by frontends.
| Opcode | Name | Description |
|---|---|---|
| 0x70 | gui_file_tree | File tree entries |
| 0x71 | gui_tab_bar | Tab bar with tab entries |
| 0x72 | gui_which_key | Which-key popup bindings |
| 0x73 | gui_completion | Completion popup items |
| 0x74 | gui_theme | Theme color slots |
| 0x75 | gui_breadcrumb | Path breadcrumb segments |
| 0x76 | gui_status_bar | Status bar data |
| 0x77 | gui_picker | Fuzzy picker items |
| 0x78 | gui_agent_chat | Agent conversation view |
| 0x79 | gui_gutter_sep | Gutter separator col + color |
| 0x7A | gui_cursorline | Cursorline row + bg color |
| 0x7B | gui_gutter | Structured gutter data |
| 0x7C | gui_bottom_panel | Bottom panel container state |
| 0x7D | gui_picker_preview | Picker preview content |
| 0x7E | gui_tool_manager | Tool manager panel |
| 0x7F | gui_minibuffer | Native minibuffer + candidates |
| 0x81 | gui_hover_popup | Native hover tooltip popup |
| 0x82 | gui_signature_help | Signature help popup |
| 0x83 | gui_float_popup | Float popup window |
| 0x84 | gui_split_separators | Split pane separator lines |
| 0x85 | gui_git_status | Git status panel data |
| 0x86 | gui_agent_groups | Workspace indicator + list |
| 0x87 | gui_board | Board card grid state |
GUI Actions (Frontend → BEAM)
| Sub-opcode | Name |
|---|---|
| 0x01 | select_tab |
| 0x02 | close_tab |
| 0x03 | file_tree_click |
| 0x04 | file_tree_toggle |
| 0x05 | completion_select |
| 0x06 | breadcrumb_click |
| 0x07 | toggle_panel |
| 0x08 | new_tab |
| 0x09 | panel_switch_tab |
| 0x0A | panel_dismiss |
| 0x0B | panel_resize |
| 0x0C | open_file |
| 0x0D | file_tree_new_file |
| 0x0E | file_tree_new_folder |
| 0x2D | file_tree_edit_confirm |
| 0x2E | file_tree_edit_cancel |
| 0x2F | scroll_to_line |
| 0x30 | file_tree_delete |
| 0x0F | file_tree_collapse_all |
| 0x10 | file_tree_refresh |
| 0x11 | tool_install |
| 0x12 | tool_uninstall |
| 0x13 | tool_update |
| 0x14 | tool_dismiss |
| 0x15 | agent_tool_toggle |
| 0x16 | execute_command |
| 0x17 | minibuffer_select |
| 0x18 | git_stage_file |
| 0x19 | git_unstage_file |
| 0x1A | git_discard_file |
| 0x1B | git_stage_all |
| 0x1C | git_unstage_all |
| 0x1D | git_commit |
| 0x1E | git_open_file |
| 0x1F | agent_group_rename |
| 0x20 | agent_group_set_icon |
| 0x21 | agent_group_close |
Summary
Types
Action menu state: {actions, selected_index} or nil.
Clipboard target for the write opcode.
Display type for a gutter row.
Data for a float popup.
Git status panel data for encoding.
A semantic GUI action from the Swift/GTK frontend.
A chat message that may carry pre-computed styled runs.
Gutter data for a single window.
A single gutter entry for one visible line.
A horizontal split separator with filename.
Indent guide data for one window.
Line number display style for the GUI gutter.
A styled text segment for preview content: {text, fg_color, bold?}.
Sign type for the gutter sign column.
A line of styled runs.
A styled text run for GUI rendering: {text, fg_rgb, bg_rgb, flags}.
A vertical split separator.
Functions
Decodes a GUI action sub-opcode and its payload into a gui_action() tuple.
Encodes a clipboard_write command.
Encodes a gui_agent_chat command with conversation messages.
Encodes the agent context bar state when zoomed into an agent card.
Encodes a gui_agent_groups command with the current workspace state.
Encodes a gui_board command with the card grid state.
Encodes a gui_bottom_panel command from a BottomPanel.t().
Encodes a gui_breadcrumb command.
Encodes a gui_change_summary command (0x89).
Encodes a gui_completion command.
Encodes a gui_cursorline command.
Encodes a gui_file_tree command with the visible file tree entries.
Encodes a gui_float_popup command (0x83).
Encodes a gui_git_status command (0x85) for the native GUI frontend.
Encodes a gui_gutter command for one window.
Encodes a gui_gutter_separator command.
Encodes a gui_hover_popup command (0x81).
Encodes a gui_indent_guides command for one window.
Encodes a gui_indent_guides command with no guides (empty).
Encodes a gui_line_spacing command.
Encodes a gui_minibuffer command (0x7F).
Encodes a gui_picker command.
Encodes a gui_picker_preview command.
Encodes a gui_signature_help command (0x82).
Encodes a gui_split_separators command (0x84).
Encodes a gui_status_bar command from a StatusBar.Data.t() tagged union.
Encodes a gui_tab_bar command with the current tab bar state.
Encodes a gui_theme command from a Theme.t().
Encodes the tool manager panel state.
Encodes a gui_which_key command.
Types
@type clipboard_target() :: :general | :find
Clipboard target for the write opcode.
@type display_type() ::
:normal | :fold_start | :fold_continuation | :wrap_continuation
Display type for a gutter row.
@type float_popup_data() :: %{ visible: boolean(), title: String.t(), lines: [String.t()], width: non_neg_integer(), height: non_neg_integer() }
Data for a float popup.
@type git_status_data() :: %{ repo_state: :normal | :not_a_repo | :loading, branch: String.t(), ahead: non_neg_integer(), behind: non_neg_integer(), entries: [Minga.Git.StatusEntry.t()] }
Git status panel data for encoding.
@type gui_action() :: {:select_tab, id :: pos_integer()} | {:close_tab, id :: pos_integer()} | {:file_tree_click, index :: non_neg_integer()} | {:file_tree_toggle, index :: non_neg_integer()} | {:completion_select, index :: non_neg_integer()} | {:breadcrumb_click, segment_index :: non_neg_integer()} | {:toggle_panel, panel :: non_neg_integer()} | :new_tab | {:panel_switch_tab, tab_index :: non_neg_integer()} | :panel_dismiss | {:panel_resize, height_percent :: non_neg_integer()} | {:open_file, path :: String.t()} | {:file_tree_new_file, index :: non_neg_integer()} | {:file_tree_new_folder, index :: non_neg_integer()} | {:file_tree_edit_confirm, text :: String.t()} | :file_tree_edit_cancel | :file_tree_collapse_all | :file_tree_refresh | {:tool_install, name :: String.t()} | {:tool_uninstall, name :: String.t()} | {:tool_update, name :: String.t()} | :tool_dismiss | {:agent_tool_toggle, message_index :: non_neg_integer()} | {:execute_command, name :: String.t()} | {:minibuffer_select, candidate_index :: non_neg_integer()} | {:git_stage_file, path :: String.t()} | {:git_unstage_file, path :: String.t()} | {:git_discard_file, path :: String.t()} | :git_stage_all | :git_unstage_all | {:git_commit, message :: String.t()} | {:git_open_file, path :: String.t()} | {:agent_group_rename, id :: non_neg_integer(), name :: String.t()} | {:agent_group_set_icon, id :: non_neg_integer(), icon :: String.t()} | {:agent_group_close, id :: non_neg_integer()} | {:space_leader_chord, codepoint :: non_neg_integer(), modifiers :: non_neg_integer()} | {:space_leader_retract, codepoint :: non_neg_integer(), modifiers :: non_neg_integer()} | {:find_pasteboard_search, text :: String.t(), direction :: non_neg_integer()} | {:board_select_card, card_id :: pos_integer()} | {:board_close_card, card_id :: pos_integer()} | {:board_reorder, card_id :: pos_integer(), new_index :: non_neg_integer()} | {:board_dispatch_agent, task :: String.t(), model :: String.t()} | :agent_approve | :agent_request_changes | :agent_dismiss | {:change_summary_click, index :: non_neg_integer()} | {:file_tree_delete, index :: non_neg_integer()} | {:file_tree_rename, index :: non_neg_integer()} | {:file_tree_duplicate, index :: non_neg_integer()} | {:file_tree_move, source_index :: non_neg_integer(), target_dir_index :: non_neg_integer()}
A semantic GUI action from the Swift/GTK frontend.
@type gui_chat_message() :: MingaAgent.Message.t() | {:styled_assistant, [[styled_run()]]} | {:styled_tool_call, %{ name: String.t(), status: :running | :complete | :error, is_error: boolean(), collapsed: boolean(), duration_ms: non_neg_integer() | nil, result: String.t() | nil }, [[styled_run()]]}
A chat message that may carry pre-computed styled runs.
@type gutter_data() :: %{ window_id: non_neg_integer(), content_row: non_neg_integer(), content_col: non_neg_integer(), content_height: non_neg_integer(), is_active: boolean(), cursor_line: non_neg_integer(), line_number_style: line_number_style(), line_number_width: non_neg_integer(), sign_col_width: non_neg_integer(), entries: [gutter_entry()] }
Gutter data for a single window.
@type gutter_entry() :: %{ :buf_line => non_neg_integer(), :display_type => display_type(), :sign_type => sign_type(), optional(:sign_fg) => non_neg_integer(), optional(:sign_text) => String.t() }
A single gutter entry for one visible line.
When sign_type is :annotation, sign_fg and sign_text carry the
annotation icon's color and text. For all other sign types these fields
are absent or ignored.
@type horizontal_separator() :: {row :: non_neg_integer(), col :: non_neg_integer(), width :: non_neg_integer(), filename :: String.t()}
A horizontal split separator with filename.
@type indent_guide_data() :: %{ window_id: non_neg_integer(), tab_width: pos_integer(), active_guide_col: non_neg_integer(), guide_cols: [non_neg_integer()] }
Indent guide data for one window.
@type line_number_style() :: :hybrid | :absolute | :relative | :none
Line number display style for the GUI gutter.
@type preview_segment() :: {String.t(), non_neg_integer(), boolean()}
A styled text segment for preview content: {text, fg_color, bold?}.
@type sign_type() ::
:none
| :git_added
| :git_modified
| :git_deleted
| :diag_error
| :diag_warning
| :diag_info
| :diag_hint
| :annotation
Sign type for the gutter sign column.
@type styled_line() :: [styled_run()]
A line of styled runs.
@type styled_run() :: {String.t(), non_neg_integer(), non_neg_integer(), non_neg_integer()}
A styled text run for GUI rendering: {text, fg_rgb, bg_rgb, flags}.
@type vertical_separator() :: {col :: non_neg_integer(), start_row :: non_neg_integer(), end_row :: non_neg_integer()}
A vertical split separator.
Functions
@spec decode_gui_action(non_neg_integer(), binary()) :: {:ok, gui_action()} | :error
Decodes a GUI action sub-opcode and its payload into a gui_action() tuple.
Called from Protocol.decode_event/1 when the outer opcode is 0x07 (gui_action).
@spec encode_clipboard_write(String.t(), clipboard_target()) :: binary()
Encodes a clipboard_write command.
Uses the forward-compatible 0x90+ format: opcode(1) + payload_length(2) + payload. Payload: target(1) + text_length(2) + text(text_length).
Target: 0 = general pasteboard (Cmd+C), 1 = find pasteboard (Cmd+E).
Encodes a gui_agent_chat command with conversation messages.
Messages are {id, message} tuples where id is a stable BEAM-assigned
uint32. Each encoded message is prefixed with its ID so the GUI frontend
can use it as a persistent SwiftUI identity across updates.
Encodes the agent context bar state when zoomed into an agent card.
Layout:
- visible(1): 1 if zoomed into a non-You agent card, 0 otherwise
- task_len(2): length of task string
- task(N): task description
- dispatch_timestamp(8): Unix timestamp when the task was dispatched
- status(1): current card status (0-5)
- can_approve(1): 1 if the user can approve the agent's work, 0 otherwise
@spec encode_gui_agent_groups(MingaEditor.State.TabBar.t()) :: binary()
Encodes a gui_agent_groups command with the current workspace state.
Wire format: opcode(1) + active_group_id(2) + workspace_count(1) + workspaces...
Per workspace: id(2) + kind(1) + agent_status(1) + color_r(1) + color_g(1) + color_b(1)
- tab_count(2) + label_len(1) + label(label_len) + icon_len(1) + icon(icon_len)
Kind: 0 = manual, 1 = agent. Agent status: 0 = idle, 1 = thinking, 2 = tool_executing, 3 = error.
@spec encode_gui_board(MingaEditor.Shell.Board.State.t()) :: binary()
Encodes a gui_board command with the card grid state.
Wire format: opcode(0x87) + visible(1) + focused_card_id(4) + card_count(2) + cards...
Per card: card_id(4) + status(1) + flags(1)
- task_len(2) + task(task_len)
- model_len(1) + model(model_len)
- elapsed_seconds(4)
- recent_file_count(1) + recent_files...
Per recent_file: path_len(2) + path(path_len)
Status bytes: 0=idle, 1=working, 2=iterating, 3=needs_you, 4=done, 5=errored Flags: bit 0 = is_you_card, bit 1 = is_focused
@spec encode_gui_bottom_panel( MingaEditor.BottomPanel.t(), MingaEditor.UI.Panel.MessageStore.t() ) :: {binary(), MingaEditor.UI.Panel.MessageStore.t()}
Encodes a gui_bottom_panel command from a BottomPanel.t().
Wire format: When visible:
opcode(1) + visible=1(1) + active_tab_index(1) + height_percent(1)
+ filter_preset(1) + tab_count(1) + tab_defs... + content_payloadPer tab_def:
tab_type(1) + name_len(1) + name(name_len)Messages content_payload (when active tab is :messages):
entry_count(2) + entries...Per entry:
id(4) + level(1) + subsystem(1) + timestamp_secs(4)
+ path_len(2) + path(path_len) + text_len(2) + text(text_len)When hidden:
opcode(1) + visible=0(1)
Encodes a gui_breadcrumb command.
@spec encode_gui_change_summary([map()], non_neg_integer()) :: binary()
Encodes a gui_change_summary command (0x89).
Sends the list of changed files with diff stats when zoomed into an agent card. The Swift frontend renders this as a resizable sidebar on the left.
Wire format: opcode(1) + visible(1) + selected_index(2) + entry_count(2) + entries...
Each entry: path_len(2) + path + action(1) + lines_added(4) + lines_removed(4)
Action: 0=modified, 1=added, 2=deleted, 3=renamed
@spec encode_gui_completion( Minga.Editing.Completion.t() | nil, non_neg_integer(), non_neg_integer() ) :: binary()
Encodes a gui_completion command.
@spec encode_gui_cursorline(non_neg_integer(), non_neg_integer()) :: binary()
Encodes a gui_cursorline command.
Sends the cursor screen row and cursorline background color to the GUI frontend so it can draw the highlight as a native Metal quad instead of a full-width space fill draw.
row is the screen row (0-indexed). bg_rgb is a 24-bit RGB color value.
Pass row = 0xFFFF and bg_rgb = 0 to indicate no cursorline (inactive
window or cursorline disabled).
@spec encode_gui_file_tree( Minga.Project.FileTree.t() | nil, MingaEditor.State.FileTree.editing() | nil ) :: binary()
Encodes a gui_file_tree command with the visible file tree entries.
Sends: selected_index, tree_width, entry_count, root_len, root, then per entry: path_hash, flags (is_dir, is_expanded), depth, git_status, icon, name, rel_path.
@spec encode_gui_float_popup(float_popup_data()) :: binary()
Encodes a gui_float_popup command (0x83).
Wire format: opcode(1) + visible(1) + width(2) + height(2) + title_len(2) + title(title_len) + line_count(2) + lines...
Each line: text_len(2) + text(text_len)
When visible=0, no further fields are sent.
@spec encode_gui_git_status(git_status_data()) :: binary()
Encodes a gui_git_status command (0x85) for the native GUI frontend.
Wire format: opcode:1, repo_state:1, ahead:2, behind:2, branch_len:2, branch, entry_count:2, then per entry:
path_hash:4, section:1, status:1, path_len:2, path
@spec encode_gui_gutter(gutter_data()) :: binary()
Encodes a gui_gutter command for one window.
One message is sent per window (not batched). Each message includes the window's screen position so the GUI knows where to render.
Wire format: opcode(1) + window_id(2) + content_row(2) + content_col(2) + content_height(2)
- is_active(1) + cursor_line(4) + line_number_style(1)
- line_number_width(1) + sign_col_width(1) + line_count(2) + entries...
Per entry: buf_line(4) + display_type(1) + sign_type(1)
@spec encode_gui_gutter_separator(non_neg_integer(), non_neg_integer()) :: binary()
Encodes a gui_gutter_separator command.
Sends the gutter column position and separator color to the GUI frontend.
col is the cell column at the right edge of the gutter (0 = no separator).
color_rgb is a 24-bit RGB color value.
@spec encode_gui_hover_popup(MingaEditor.HoverPopup.t() | nil) :: binary()
Encodes a gui_hover_popup command (0x81).
Wire format: opcode(1) + visible(1) + anchor_row(2) + anchor_col(2) + focused(1) + scroll_offset(2) + line_count(2) + lines...
Each line: line_type(1) + segment_count(2) + segments...
Each segment: style(1) + text_len(2) + text(text_len)
Line types: 0=text, 1=code, 2=code_header, 3=header, 4=blockquote, 5=list_item, 6=rule, 7=empty
Segment styles: 0=plain, 1=bold, 2=italic, 3=bold_italic, 4=code, 5=code_block, 6=code_content, 7=header1, 8=header2, 9=header3, 10=blockquote, 11=list_bullet, 12=rule
@spec encode_gui_indent_guides(indent_guide_data()) :: binary()
Encodes a gui_indent_guides command for one window.
Uses the forward-compatible 0x90+ format: opcode(1) + payload_length(2) + payload. Payload: window_id(2) + tab_width(1) + active_guide_col(2) + guide_count(1) + guide_cols(2 each).
active_guide_col of 0xFFFF means no active guide. Guide columns are
character-unit offsets from the content start (not screen left).
@spec encode_gui_indent_guides_empty(non_neg_integer()) :: binary()
Encodes a gui_indent_guides command with no guides (empty).
Encodes a gui_line_spacing command.
Uses the forward-compatible 0x90+ format: opcode(1) + payload_length(2) + payload. Payload: spacing_x100(2) — the spacing multiplier times 100 as a 16-bit unsigned integer. For example, 1.2 is encoded as 120, 1.0 as 100.
@spec encode_gui_minibuffer(MingaEditor.MinibufferData.t()) :: binary()
Encodes a gui_minibuffer command (0x7F).
Sends structured minibuffer state to the GUI frontend for native rendering. Includes mode, prompt, input text, cursor position, context string, and completion candidates.
When visible is false, sends a single hide byte. When visible, encodes
the full payload including any completion candidates.
@spec encode_gui_picker( MingaEditor.UI.Picker.t() | nil, boolean(), action_menu_state(), non_neg_integer() ) :: binary()
Encodes a gui_picker command.
Wire format (v2, extended):
opcode(1) + visible(1) + selected_index(2) + filtered_count(2) + total_count(2)
+ title_len(2) + title + query_len(2) + query + has_preview(1) + item_count(2) + items...
Per item:
icon_color(3) + flags(1) + label_len(2) + label + desc_len(2) + desc
+ annotation_len(2) + annotation + match_pos_count(1) + match_positions(each 2 bytes)
Flags bits:
bit 0: two_line (file-style two-line layout)
bit 1: marked (multi-select checkmark)
@spec encode_gui_picker_preview([[preview_segment()]] | nil) :: binary()
Encodes a gui_picker_preview command.
Wire format:
opcode(1) + visible(1)
When visible:
opcode(1) + 1(1) + line_count(2) + lines...
Per line:
segment_count(1) + segments...
Per segment:
fg_color(3) + flags(1) + text_len(2) + text
Flags bits:
bit 0: bold
@spec encode_gui_signature_help(MingaEditor.SignatureHelp.t() | nil) :: binary()
Encodes a gui_signature_help command (0x82).
Wire format: opcode(1) + visible(1) + anchor_row(2) + anchor_col(2) + active_signature(1) + active_parameter(1) + signature_count(1) + signatures...
Each signature: label_len(2) + label + doc_len(2) + doc + param_count(1) + params...
Each parameter: label_len(2) + label + doc_len(2) + doc
@spec encode_gui_split_separators(non_neg_integer(), [vertical_separator()], [ horizontal_separator() ]) :: binary()
Encodes a gui_split_separators command (0x84).
Wire format: opcode(1) + border_color_rgb(3) + vertical_count(1) + verticals... + horizontal_count(1) + horizontals...
Each vertical: col(2) + start_row(2) + end_row(2) Each horizontal: row(2) + col(2) + width(2) + filename_len(2) + filename
@spec encode_gui_status_bar(MingaEditor.StatusBar.Data.t()) :: binary()
Encodes a gui_status_bar command from a StatusBar.Data.t() tagged union.
Wire format (opcode 0x76, sectioned):
[opcode:1][section_count:1][section_id:1][section_len:2][payload:N]...
Sections are self-describing: each starts with a 1-byte ID and 2-byte length. Unknown sections are skipped by the frontend. New fields can be added without changing the decoder for existing sections.
Section IDs: 0x01 - Identity: content_kind, mode, flags 0x02 - Cursor: cursor_line, cursor_col, line_count 0x03 - Diagnostics: error/warning/info/hint counts, diagnostic_hint 0x04 - Language: lsp_status, parser_status 0x05 - Git: branch, added, modified, deleted 0x06 - File: icon, icon_color, filename, filetype 0x07 - Message: status message 0x08 - Recording: macro_recording 0x09 - Agent: model_name, message_count, session_status, agent_status
@spec encode_gui_tab_bar(MingaEditor.State.TabBar.t(), pid() | nil) :: binary()
Encodes a gui_tab_bar command with the current tab bar state.
Each tab entry includes: flags byte (is_active, is_dirty, is_agent, has_attention, agent_status in upper bits), tab id, group_id for workspace grouping, Nerd Font icon, and display label.
@spec encode_gui_theme(MingaEditor.UI.Theme.t()) :: binary()
Encodes a gui_theme command from a Theme.t().
Takes a Theme.t() and produces a binary with {slot_id:u8, r:u8, g:u8, b:u8}
entries for every color slot the GUI needs. Colors that are nil are skipped.
Encodes the tool manager panel state.
Sends a rich structured view of all available tools with their install status, versions, categories, and progress info. The GUI frontend renders this as a native management panel.
Wire format
When visible:
opcode(1) + 1(1) + filter(1) + selected_index(2) + tool_count(2) + tools...
Per tool:
name_len(1) + name(name_len) + label_len(1) + label(label_len)
+ desc_len(2) + desc(desc_len) + category(1) + status(1)
+ method(1) + language_count(1) + languages...
+ version_len(1) + version(version_len)
+ homepage_len(2) + homepage(homepage_len)
+ provides_count(1) + provides...
+ error_reason_len(2) + error_reason(error_reason_len)
Per language:
lang_len(1) + lang(lang_len)
Per provides:
cmd_len(1) + cmd(cmd_len)
When hidden:
opcode(1) + 0(1)Status values
| Value | Status |
|---|---|
| 0 | not_installed |
| 1 | installed |
| 2 | installing |
| 3 | update_available |
| 4 | failed |
Category values
| Value | Category |
|---|---|
| 0 | lsp_server |
| 1 | formatter |
| 2 | linter |
| 3 | debugger |
Method values
| Value | Method |
|---|---|
| 0 | npm |
| 1 | pip |
| 2 | cargo |
| 3 | go_install |
| 4 | github_release |
Filter values
| Value | Filter |
|---|---|
| 0 | all |
| 1 | installed |
| 2 | not_installed |
| 3 | lsp_servers |
| 4 | formatters |
@spec encode_gui_which_key(MingaEditor.State.WhichKey.t()) :: binary()
Encodes a gui_which_key command.