Pull Request #282: [WIP] Analyze current VT100 and toolbar system architecture

New Pull Request: [WIP] Analyze current VT100 and toolbar system architecture
DangerousPrototypes/BusPirate5-firmwareDangerousPrototypes/BusPirate5-firmware

Thanks for asking me to work on this. I will get started on it and keep this PR’s description up to date as I form a plan and make progress.

Give me the low hanging fruit from here so I can look at it closer with my local agent.

VT100 & Toolbar System — Architecture Analysis

Executive Summary

The current VT100/terminal system grew organically. It has a solid foundational concept (color API, buffered statusbar TX, scroll regions) but suffers from code duplication, scattered raw escape sequences, no centralized toolbar management, and a race condition window during status bar updates. This document maps the current state and proposes a migration path.


1. Current Architecture Map

Layer Diagram

┌──────────────────────────────────────────────────────────┐
│  Application Code                                        │
│  freq.c  psu.c  ui_info.c  logic_bar.c  ui_cmdln.c      │
│       ↓ (raw \033[, \e[ scattered everywhere)            │
├──────────────────────────────────────────────────────────┤
│  ui_term.c — Color API                                   │
│  ui_term_color_*() → returns static string               │
│  ui_term_color_text/background() → printf directly       │
│  ui_term_color_text_background_buf() → snprintf to buf   │
│  ui_term_cursor_hide/show()                              │
│  (NO cursor positioning, NO erase, NO scroll regions)    │
├──────────────────────────────────────────────────────────┤
│  ui_statusbar.c — 4-line fixed bar at terminal bottom    │
│  Builds VT100 into tx_sb_buf[1024] via snprintf          │
│  Core1 only, uses intercore messaging                    │
├──────────────────────────────────────────────────────────┤
│  logic_bar.c — 10-line LA waveform display               │
│  Direct printf of raw \e[ sequences                      │
│  Own scroll region, own cursor save/restore               │
├──────────────────────────────────────────────────────────┤
│  usb_tx.c — TX State Machine                             │
│  tx_fifo (regular output) — Core0 produces, Core1 drains │
│  tx_sb_buf (statusbar) — Core1 builds & drains           │
│  State: IDLE → STATUSBAR_DELAY → STATUSBAR_TX            │
└──────────────────────────────────────────────────────────┘

Data Flow for Status Bar

Core0: ui_statusbar_update_blocking()
  → sends BP_ICM_UPDATE_STATUS_BAR to Core1 (synchronous)

Core1: ui_statusbar_update_from_core1(flags)
  → snprintf VT100 escape sequences into tx_sb_buf[1024]
  → tx_sb_start(len)

Core1: tx_fifo_service() state machine
  → IDLE: drain tx_fifo (regular printf output)
  → STATUSBAR_DELAY: wait one cycle for tx_fifo to empty
  → STATUSBAR_TX: send tx_sb_buf in 64-byte chunks to USB/UART

Data Flow for Logic Bar

Core0: logic_bar_draw_frame() / logic_bar_redraw()
  → draw_prepare(): pause statusbar, hide cursor
  → printf raw \e[ sequences directly into tx_fifo
  → draw_release(): unpause statusbar, show cursor

No buffering, no state machine, no interleaving protection.

2. Problem Inventory

2.1 Scattered Raw Escape Sequences

17 files contain raw VT100 escape codes. Three notations are mixed:

Style Files using it
\033[ (octal) ui_term.c, ui_statusbar.c, ui_info.c, psu.c
\e[ (GCC extension) logic_bar.c, freq.c
\x1b[ (hex) linenoise (3rd party only)

ui_term.c provides cursor hide/show and colors, but has zero helpers for:

  • Cursor positioning (\033[row;colH)
  • Erase line (\033[K) / erase N chars (\033[nX)
  • Scroll region setting (\033[top;bottomr)
  • Save/restore cursor (\0337 / \0338)
  • Screen flash (\033[?5h / \033[?5l)
  • Move cursor N positions (\033[nC, \033[nB, \033[nD)
  • Disable line wrap (\033[7l)

Every file that needs these operations rolls its own.

2.2 Duplicated Pin Display Code

Two complete implementations of the same pin info display exist:

Function File Output method Used when
ui_info_print_pin_names() ui_info.c Direct printf v/V command, no statusbar
ui_statusbar_names() ui_statusbar.c snprintf to buf Statusbar active
ui_info_print_pin_labels() ui_info.c Direct printf v/V command
ui_statusbar_labels() ui_statusbar.c snprintf to buf Statusbar active
ui_info_print_pin_voltage() ui_info.c Direct printf v/V command
ui_statusbar_value() ui_statusbar.c snprintf to buf Statusbar active

The data, formatting, and even escape sequences (\033[8X%d.%s\t) are identical — only the output method differs.

2.3 No Centralized Toolbar Management

Each toolbar independently:

  • Calculates its own position: rows - (height + statusbar*4)
  • Sets its own scroll region: \033[1;Nr
  • Pauses/unpauses the statusbar: terminal_ansi_statusbar_pause = true/false
  • Hides/shows cursor: terminal_hide_cursor = true/false
  • Saves/restores cursor position

There’s no registry, no arbitration. If two toolbars were active simultaneously, they’d fight over scroll regions.

The logic bar hardcodes the statusbar height as * 4:

return system_config.terminal_ansi_rows - (height + (system_config.terminal_ansi_statusbar * 4));

2.4 Status Bar “Blowout” Race

The STATUSBAR_DELAY state waits one cycle for tx_fifo to empty before sending the statusbar buffer. But:

  1. Core0 can printf between the DELAY check and the actual TX
  2. The 64-byte chunked transmission means multi-chunk statusbar updates can be interleaved with Core0 printf output arriving between chunks
  3. draw_prepare() sets statusbar_pause, then does a busy_wait_ms(1) — a 1ms window is not a guarantee

The fundamental issue: there is no mutex or lock between Core0 printf and Core1 statusbar TX. The SPSC queue is lock-free, but the VT100 cursor-position sequences in tx_sb_buf can get interleaved with regular output, causing screen corruption.

2.5 Color Function Boilerplate

Every ui_term_color_*() function is an identical switch statement:

char* ui_term_color_THING(void) {
    switch (system_config.terminal_ansi_color) {
        case UI_TERM_256:    return UI_TERM_256_COLOR_CONCAT_TEXT(BP_COLOR_256_THING);
        case UI_TERM_FULL_COLOR: return UI_TERM_FULL_COLOR_CONCAT_TEXT(BP_COLOR_THING);
        case UI_TERM_NO_COLOR:
        default: return "";
    }
}

This pattern is repeated 9 times. It’s a lookup table trying to be functions.

2.6 Terminal Sizing is Static

terminal_ansi_rows and terminal_ansi_columns are set once at detection. If the user resizes their terminal, nothing updates. The statusbar and logic bar positions become wrong.


3. Proposed Architecture

3.1 Central VT100 Escape Code API

Add cursor/erase/scroll primitives to ui_term.c. All return char* or write to buffer, respecting the color mode. These replace every raw escape sequence in the codebase:

// Proposed new API surface in ui_term.h

// Cursor movement
uint32_t ui_term_cursor_position(char* buf, size_t len, uint16_t row, uint16_t col);
uint32_t ui_term_cursor_move(char* buf, size_t len, int16_t rows, int16_t cols);
uint32_t ui_term_cursor_save(char* buf, size_t len);
uint32_t ui_term_cursor_restore(char* buf, size_t len);
uint32_t ui_term_cursor_column(char* buf, size_t len, uint16_t col);

// Erase
uint32_t ui_term_erase_line(char* buf, size_t len);           // \033[K
uint32_t ui_term_erase_chars(char* buf, size_t len, uint16_t n); // \033[nX
uint32_t ui_term_clear_screen(char* buf, size_t len);         // \033[2J

// Scroll region
uint32_t ui_term_scroll_region_set(char* buf, size_t len, uint16_t top, uint16_t bottom);
uint32_t ui_term_scroll_region_reset(char* buf, size_t len);

// Line wrap
uint32_t ui_term_line_wrap(char* buf, size_t len, bool enable);

// Screen flash (reverse video)
uint32_t ui_term_screen_flash(char* buf, size_t len, bool enable);

// Window title
uint32_t ui_term_set_title(char* buf, size_t len, const char* title);

// Printf variants for convenience (direct output)
void ui_term_cursor_position_printf(uint16_t row, uint16_t col);
void ui_term_erase_line_printf(void);
// ... etc

Every function returns 0 (writes nothing) when terminal_ansi_color == UI_TERM_NO_COLOR. This automatically disables VT100 for dumb terminals.

Two variants per function:

  • _buf() — writes to buffer (for statusbar/toolbar buffered rendering)
  • _printf() — direct output (for immediate commands)

3.2 Table-Driven Color System

Replace the 9 identical switch functions with a single lookup:

typedef struct {
    const char* full_color;    // "\033[38;2;R;G;Bm"
    const char* color_256;     // "\033[38;5;Nm"
} ui_term_color_entry_t;

typedef enum {
    UI_COLOR_RESET = 0,
    UI_COLOR_PROMPT,
    UI_COLOR_INFO,
    UI_COLOR_NOTICE,
    UI_COLOR_WARNING,
    UI_COLOR_ERROR,
    UI_COLOR_NUM_FLOAT,
    UI_COLOR_GREY,
    UI_COLOR_PACMAN,
    UI_COLOR_COUNT
} ui_color_id_t;

// Single function replaces 9:
char* ui_term_color(ui_color_id_t id);

// Keep the old names as inline wrappers during migration:
static inline char* ui_term_color_prompt(void) { return ui_term_color(UI_COLOR_PROMPT); }

3.3 Toolbar Registry

Central toolbar manager that owns scroll region calculation and lifecycle:

#define TOOLBAR_MAX_COUNT 4

typedef struct {
    const char* name;           // "statusbar", "logic_analyzer", "i2c_monitor"...
    uint16_t height;            // lines this toolbar occupies
    bool enabled;               // currently active
    bool visible;               // currently drawn
    
    // Callbacks
    void (*draw)(char* buf, size_t len, uint16_t start_row, uint16_t width);
    void (*update)(char* buf, size_t len, uint16_t start_row, uint16_t width, uint32_t flags);
    void (*destroy)(void);
} toolbar_t;

// Registry API
bool     toolbar_register(toolbar_t* tb);
void     toolbar_unregister(toolbar_t* tb);
void     toolbar_enable(const char* name);
void     toolbar_disable(const char* name);
uint16_t toolbar_total_height(void);          // sum of all enabled toolbar heights
uint16_t toolbar_get_scroll_top(void);        // always 1
uint16_t toolbar_get_scroll_bottom(void);     // rows - total_height
void     toolbar_recalculate_layout(void);    // recalc positions, set scroll region
void     toolbar_redraw_all(void);

Layout rule: Toolbars stack from the bottom. The statusbar is always last (bottommost). New toolbars go above it.

┌─────────────────────────────┐ ← row 1
│ Scrollable command area     │
│                             │
│                             │
├─────────────────────────────┤ ← scroll_bottom = rows - total_toolbar_height
│ [toolbar: logic_analyzer]   │ 10 lines
│ [toolbar: i2c_monitor]      │  3 lines  (future)
│ [toolbar: statusbar]        │  4 lines
└─────────────────────────────┘ ← row = terminal_ansi_rows

The scroll region is set once by the registry: \033[1;(scroll_bottom)r

3.4 Unified Pin Display

Merge ui_info.c and ui_statusbar.c pin rendering into one set of functions that can output to either a buffer or printf:

typedef struct {
    char* buf;        // NULL = use printf
    size_t buf_len;
    uint32_t offset;  // current write position in buf
} ui_output_t;

// Unified functions
uint32_t ui_pin_render_names(ui_output_t* out);
uint32_t ui_pin_render_labels(ui_output_t* out);
uint32_t ui_pin_render_values(ui_output_t* out);

When out->buf == NULL, they printf. When a buffer is provided, they snprintf into it. One implementation, two output modes.

3.5 Safer Status Bar TX

The existing STATUSBAR_DELAY state is a start, but needs strengthening:

Option A (minimal change): Add a “quiet window” counter. Instead of 1 cycle, require N consecutive cycles (e.g., 3) with an empty tx_fifo before transmitting the statusbar. This reduces the race window significantly.

Option B (better): Add a tx_fifo_statusbar_lock flag. When Core1 is about to send the statusbar, set the flag. Core0’s tx_fifo_put() spins/drops while the flag is set. The flag is cleared after statusbar TX completes. This is effectively a lightweight spinlock scoped only to the statusbar update window.

Option C (best, more work): Build the statusbar content into a separate SPSC queue (or double-buffer). The TX state machine sends from whichever buffer has complete content. Core1 builds the next frame while the previous is being transmitted. No interleaving possible because each buffer is complete before being selected for TX.

3.6 Potential Dummy Toolbars (for testing the registry)

While building the enable/disable system, these are useful development/testing toolbars:

  1. Pin Watcher Toolbar (2-3 lines) — Shows real-time GPIO states as HIGH/LOW with direction arrows. Simpler than the logic analyzer; just reads current pin state.

  2. Command History Toolbar (2-3 lines) — Shows last 3 commands executed. Useful for Bus Pirate users running macro sequences.

  3. Protocol Decoder Toolbar (3-4 lines) — When in I2C/SPI/UART mode, shows decoded last-N transactions (address, data, ACK/NAK). Stub version just shows “No transactions”.

  4. System Stats Toolbar (1-2 lines) — Core temperatures, free memory, uptime, USB connection state.


4. File Impact Matrix

File Current Role Change Needed
ui/ui_term.c Color API only Add cursor/erase/scroll/wrap primitives; table-driven colors
ui/ui_term.h Color API header New enums, new function declarations
ui/ui_statusbar.c 4-line fixed bar Refactor to toolbar_t interface; use ui_term primitives
ui/ui_statusbar.h Statusbar header Minimal change; add toolbar_t registration
toolbars/logic_bar.c 10-line LA bar Refactor to toolbar_t interface; replace all raw \e[
toolbars/logic_bar.h LA header Update to toolbar_t API
ui/ui_info.c Pin display (v cmd) Merge with ui_pin_render.c shared implementation
ui/ui_flags.h Update flag enum Possibly extend for toolbar-generic flags
usb_tx.c TX state machine Strengthen STATUSBAR_DELAY; consider double-buffer
commands/global/freq.c Freq display Replace \e[0K with ui_term_erase_line_printf()
pirate/psu.c PSU error flash Replace raw screen flash with ui_term_screen_flash()
system_config.h Terminal state fields Add toolbar registry state (or separate struct)
NEW: ui/ui_toolbar.c Central toolbar registry + layout manager
NEW: ui/ui_toolbar.h Toolbar types and API
NEW: ui/ui_pin_render.c Unified pin name/label/voltage rendering

5. Migration Order

Phase 1: VT100 Primitive API (no behavioral change)

  1. Add cursor/erase/scroll functions to ui_term.c / ui_term.h
  2. Replace raw escape codes file-by-file, testing each
  3. Standardize on \033[ notation everywhere
  4. Table-driven color refactor

Phase 2: Toolbar Registry (infrastructure)

  1. Create ui_toolbar.c / ui_toolbar.h with registry, layout calc
  2. Refactor ui_statusbar.c to register as a toolbar_t
  3. Implement dummy toolbars for testing enable/disable
  4. Update ui_statusbar_init/deinit to use toolbar_recalculate_layout()

Phase 3: Logic Bar Migration

  1. Refactor logic_bar.c to register as a toolbar_t
  2. Use toolbar registry for position calculation instead of draw_get_position_index()
  3. Verify both statusbar + logic bar work simultaneously

Phase 4: Unified Pin Rendering

  1. Create ui_pin_render.c with output-target abstraction
  2. Migrate ui_info.c to use it (printf mode)
  3. Migrate ui_statusbar.c to use it (buffer mode)
  4. Delete duplicate code

Phase 5: TX Hardening

  1. Implement stronger STATUSBAR_DELAY (multi-cycle or lock)
  2. Test under heavy output load (e.g., protocol decoder flood)

6. Key Constraints

  • No ncurses — bare VT100 only; this runs on a Pico RP2040 with no OS
  • Dual-core: Core0 = command processing + printf; Core1 = USB/UART TX + statusbar rendering
  • Lock-free: SPSC queues are the inter-core primitive; avoid mutexes where possible
  • Memory: tx_sb_buf is 1024 bytes; must accommodate all toolbar content combined
  • Flash: Color string literals are stored in flash; table-driven approach should use const to stay in flash
  • Backwards compat: v/V command must continue to work with statusbar disabled
  • 256-color support: LGPL3 library is optional (#ifdef ANSI_COLOR_256); all new code must respect this

:speech_balloon: We’d love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Created by: Copilot

@copilot That was really low hanging fruit. Make some proposed architectural changes.

Comment by: Iangitpers

@copilot Do some of the major architectural changes so we can just have a look tomorrow.

Comment by: DangerousPrototypes

@copilot Do some of the major architectural changes so we can just have a look tomorrow.

Done in ecfdde7. Here’s what was implemented:

Phase 2 — Toolbar Registry (ui_toolbar.c/ui_toolbar.h): Central registry that owns scroll-region calculation. Toolbars stack from the bottom. Key API: toolbar_register/unregister, toolbar_get_start_row, toolbar_scroll_bottom, toolbar_apply_scroll_region, toolbar_redraw_all.

Phase 2a — Statusbar migration (ui_statusbar.c): Registers a static toolbar_t statusbar_toolbar (height=4) on init. Row positioning now comes from toolbar_get_start_row() instead of hardcoded rows-3/rows-2 etc. Init/deinit use toolbar_apply_scroll_region().

Phase 3 — Logic bar migration (logic_bar.c): Registers logic_bar_toolbar (height=10) on logic_bar_start(), unregisters on stop. draw_get_position_index() now delegates to toolbar_get_start_row(). Frame draw uses toolbar_scroll_bottom() for the scroll region so both bars co-exist correctly.

Phase 4 — Unified pin rendering (ui_pin_render.c/ui_pin_render.h): Single ui_pin_render_names/labels/values() implementation with dual output modes — buf==NULLprintf (for the v/V command), buf!=NULLsnprintf (for the statusbar). ui_info.c is now 3 thin wrappers; ~100 lines of duplicate code removed from ui_statusbar.c.

Comment by: Copilot

VT100 & Toolbar System — Post-Refactor Status

Updated: 2026-02-24 — After Unified Buffer Builder refactor


1. Architecture Overview

Dual-Core TX Model

The RP2040 has two cores with separate TX mechanisms:

Core0 (commands) Core1 (USB service loop)
TX path tx_fifo_write(buf, len) → SPSC ring buffer (1024 bytes) → Core1 drains to USB snprintftx_sb_buf[1024]tx_sb_start(len) → state machine drains to USB
Constraint tx_fifo_write() / tx_fifo_put() assert BP_ASSERT_CORE0() tx_sb_start() asserts BP_ASSERT_CORE1()
When it runs On-demand when user types v, V, i, etc. Periodically in core1_infinite_loop() after monitor() detects changes

Unified Buffer Builder (ui_pin_render.c)

All pin rendering (names, labels, voltages) always writes into a caller-provided buffer via snprintf — there is no printf path and no if (buf) branching. Behaviour is controlled through pin_render_flags_t flags:

typedef enum {
    PIN_RENDER_CHANGE_TRACK  = (1u << 0),  // skip unchanged cells (emit bare \t)
    PIN_RENDER_NEWLINE       = (1u << 1),  // append \r\n at end of row
    PIN_RENDER_CLEAR_CELLS   = (1u << 2),  // prepend \033[8X before each cell
} pin_render_flags_t;
Flag Statusbar (Core1) v/V command (Core0)
CHANGE_TRACK :white_check_mark: Yes — skip unchanged cells :cross_mark: No — always repaint
NEWLINE :cross_mark: No — cursor positioned externally :white_check_mark: Yes — needs \r\n
CLEAR_CELLS :white_check_mark: Yes :white_check_mark: Yes

The builder reads raw data directly — no dependency on system_monitor.h:

  • Voltages: *hw_pin_voltage_ordered[i] (millivolts)
  • Current: hw_adc_raw[HW_ADC_CURRENT_SENSE] (raw ADC → computed milliamps)
  • Freq/PWM: freq_display_hz()

Change tracking uses a static shadow buffer (shadow_voltage_mv[], shadow_current_raw) — only written when CHANGE_TRACK is set, which is Core1-only. No lock needed.


2. What Was Done

Phase 1: VT100 Primitive API :white_check_mark: Partial

New _printf helpers in ui_term.c:

Function Status
ui_term_erase_line_printf() :white_check_mark:
ui_term_screen_flash_printf(bool) :white_check_mark:
ui_term_cursor_position_printf(row,col) :white_check_mark:
ui_term_cursor_save_printf() :white_check_mark:
ui_term_cursor_restore_printf() :white_check_mark:
ui_term_scroll_region_printf(top,bot) :white_check_mark:
ui_term_line_wrap_disable_printf() :white_check_mark:
ui_term_cursor_move_down_printf(n) :white_check_mark:
ui_term_cursor_move_right_printf(n) :white_check_mark:
ui_term_cursor_move_left_printf(n) :white_check_mark:

Not yet implemented: _buf() variants for all of the above, ui_term_erase_chars(), ui_term_clear_screen(), ui_term_set_title().

Phase 1: Table-Driven Colors :white_check_mark: Complete

Nine identical ui_term_color_*() switch functions replaced by a single ui_term_color(ui_color_id_t id) lookup with old-name wrappers preserved.

Phase 2: Toolbar Registry :white_check_mark: Complete

Files: src/ui/ui_toolbar.c, src/ui/ui_toolbar.h. Provides toolbar_register/unregister, toolbar_total_height, toolbar_scroll_bottom, toolbar_get_start_row, toolbar_apply_scroll_region, toolbar_redraw_all.

Phase 3: Statusbar + Logic Bar Migration :white_check_mark: Complete

Both register as toolbar_t and use the toolbar registry for positioning. Logic bar still has raw escape sequences (see §3).

Phase 4: Unified Pin Rendering → Unified Buffer Builder :white_check_mark: Complete

ui_pin_render.c and ui_pin_render.h provide three buffer-only functions:

  • ui_pin_render_names(buf, len, flags)
  • ui_pin_render_labels(buf, len, flags)
  • ui_pin_render_values(buf, len, flags)

Key properties:

  • Zero printf/vprintf calls — everything is snprintf into caller’s buffer
  • Zero if (buf) branches — buffer is always non-NULL
  • Zero monitor_get_voltage_ptr / monitor_get_current_ptr calls — reads raw ADC data directly
  • No dependency on system_monitor.h

ui_info.c is a 3-function wrapper: builds into char tmp[512], sends via tx_fifo_write().

ui_statusbar.c passes PIN_RENDER_CHANGE_TRACK | PIN_RENDER_CLEAR_CELLS and builds directly into tx_sb_buf.

New: tx_fifo_write() helper

Added to usb_tx.c/.h — pushes a pre-built buffer through the SPSC queue byte-by-byte. Asserts BP_ASSERT_CORE0(). Used by ui_info.c wrappers so Core0 never calls printf for pin rendering.


3. Remaining Raw Escape Sequences

File Count Types Notes
src/toolbars/logic_bar.c ~21 Cursor position, erase line, scroll region, wrap disable, cursor movement, cursor show Worst offender — needs _printf wrapper migration
src/ui/ui_statusbar.c ~10 Cursor position, erase chars, cursor hide/show, save/restore Needs _buf variants to migrate
src/ui/ui_pin_render.c 8 Erase chars (\033[8X) Controlled via PIN_RENDER_CLEAR_CELLS flag; needs ui_term_erase_chars_buf()

Why they can’t be migrated yet

  • ui_statusbar.c / ui_pin_render.c — Build into buffers via snprintf. No _buf variants of VT100 primitives exist yet.
  • logic_bar.c — Could use existing _printf wrappers for most sequences but hasn’t been migrated yet.

4. Issues & Concerns

4.1 No _buf Variants for VT100 Primitives

The statusbar and pin render paths build into buffers. They need snprintf-compatible versions of VT100 primitives:

uint32_t ui_term_cursor_position_buf(char* buf, size_t len, uint16_t row, uint16_t col);
uint32_t ui_term_cursor_save_buf(char* buf, size_t len);
uint32_t ui_term_erase_chars_buf(char* buf, size_t len, uint16_t n);
// etc.

4.2 Toolbar .draw Callbacks are NULL

Both statusbar_toolbar.draw and logic_bar_toolbar.draw are NULL. toolbar_redraw_all() is a no-op. Fine for now but prevents full-screen redraw on terminal resize.

4.3 Logic Bar Detach Scroll Region Fragility

logic_bar_detach() computes the post-unregister scroll bottom before calling toolbar_unregister(). Should unregister first, then recalculate.

4.4 ui_info_print_toolbar() — Dead Declaration

ui_info.h declares ui_info_print_toolbar() but no definition exists.

4.5 voltages_value Bounds Safety

voltages_value has HW_PINS - 1 entries (9). ui_pin_render_values() iterates all HW_PINS (10). The 10th (GND) is caught before the default case, but any future pin type at that index could OOB.

4.6 Status Bar TX Race (Unchanged)

The STATUSBAR_DELAY state still waits only one cycle. No new protections added.


5. File Change Summary

File Change
src/ui/ui_term.c 11 _printf VT100 helpers; table-driven color lookup
src/ui/ui_term.h ui_color_id_t enum; new function declarations
src/ui/ui_toolbar.c NEW — Toolbar registry + layout manager
src/ui/ui_toolbar.h NEWtoolbar_t struct and registry API
src/ui/ui_pin_render.c NEW — Buffer-only unified pin rendering with pin_render_flags_t flags, shadow-based change tracking, reads raw ADC directly
src/ui/ui_pin_render.h NEWpin_render_flags_t enum + 3-arg function signatures
src/ui/ui_info.c 3 thin wrappers: build into char tmp[512], send via tx_fifo_write()
src/ui/ui_statusbar.c Registers as toolbar_t; delegates pin rows to ui_pin_render_*(buf, len, flags) with CHANGE_TRACK | CLEAR_CELLS; no direct monitor_get_* calls
src/usb_tx.c / src/usb_tx.h Added tx_fifo_write(const char* buf, uint32_t len) — Core0 buffer-send helper
src/toolbars/logic_bar.c Registers as toolbar_t; uses toolbar_scroll_bottom(); still has raw escape sequences

6. Remaining Migration Work

VT100 _buf Variants

  1. Add _buf variants of all VT100 primitives to ui_term.c
  2. Migrate ui_statusbar.c raw sequences to _buf variants
  3. Migrate ui_pin_render.c erase-chars to ui_term_erase_chars_buf()

Logic Bar Full Migration

  1. Replace ~21 raw escape sequences in logic_bar.c with _printf wrappers
  2. Remove draw_get_position_index() — use toolbar_get_start_row() exclusively
  3. Fix detach scroll-region fragility

TX Hardening

  1. Strengthen STATUSBAR_DELAY (multi-cycle quiet window or lock)
  2. Test under heavy output load

Cleanup

  1. Remove dead ui_info_print_toolbar() declaration from ui_info.h
  2. Wire up toolbar_t.draw callbacks so toolbar_redraw_all() works
  3. Add bounds check for voltages_value access in system_monitor.c