Designing Reusable Config Screens: From Vertical Labels to Table Layout
August 12, 2026
Every config screen in Phosphor BBS used to be a snowflake. SystemConfigScreen had its own layout. AiConfigScreen had its own layout. They looked different, behaved differently, and shared no code. Adding a new settings screen meant copying one of them, stripping out what you didn’t need, and writing the rest from scratch.
This is the story of how we fixed that — extracting a reusable TableConfigScreen parent class and converting every config screen to the same table-based layout, killing about 150 lines of boilerplate in the process.
The Problem: Vertical Labels
The old config screens used vertical labels. Each setting was a Label widget stacked in a LinearLayout:
BBS Name: Phosphor BBS
Welcome Message: Welcome to the system
Auto-Approve: true
Max Users: 50
[Enter] Edit [Q] Quit
It worked, but it had problems:
- No alignment — long setting names pushed values to different columns. The screen looked messy.
- No navigation — there was no cursor. You tabbed through fields blindly.
- No type awareness — editing a boolean was the same as editing a string. You typed “true” or “false” as text.
- Copy-paste everywhere — every screen reimplemented the same edit dialogs, the same save logic, the same refresh pattern.
The AiConfigScreen was the worst offender: nine vertical labels for settings like AI enabled, model name, temperature, system prompt. It looked like a settings dump, not a config screen.
The Solution: Table Layout
The new design uses a Table with two columns: Setting and Value. Arrow keys navigate rows. Enter edits the selected row based on its type. The table handles alignment automatically — both columns get consistent widths.
┌─ AI Configuration ──────────────────────┐
│ Setting │ Value │
│─────────────────────────────────────────│
│ AI Enabled │ true │
│ Model │ llama3.2 │
│ Temperature │ 0.7 │
│ System Prompt │ You are Hal, the..│
│ Ollama URL │ localhost:11434 │
│─────────────────────────────────────────│
│ [Enter] Edit [Space] Toggle [S] Save │
│ [T] Test Connection [Q] Back │
└──────────────────────────────────────────┘
The status bar at the bottom shows hotkeys. Some are universal (Enter to edit, Q to go back), and some are screen-specific (S to save and reload, T to test the Ollama connection, Space to toggle a boolean).
The TableConfigScreen Parent Class
The key insight was that every config screen does the same things:
- Creates a table with a model
- Puts it in a bordered panel
- Focuses the table for keyboard input
- Intercepts
Enterto edit the selected row - Checks the row’s type (STRING, INT, BOOLEAN, TEXT) and opens the right editor
- Refreshes the model after editing
- Shows a status bar with hotkeys
So we extracted all of that into TableConfigScreen:
public abstract class TableConfigScreen extends ContentScreen {
protected final SystemSettingsTableModel model;
protected final Table table;
// Parent provides:
// - createContent() → table in bordered panel
// - getInputComponent() → returns table for keyboard focus
// - handleKeyStroke() → intercepts ENTER for row editing
// - editSelectedRow() → checks row type, calls edit method
// - refresh() → rebuilds model, fires structure change
// - editString/editInt() → opens TextInputDialog pre-filled with current value
// - getStatusText() → default: [Enter] Edit [?] Help [Q] Back
// Child provides:
// - rebuildModel() → populate model with setting entries
// - editStringByRow() → edit a STRING/TEXT setting
// - editIntByRow() → edit an INT setting
// - toggleBooleanByRow() → toggle a BOOLEAN setting
// - onCustomKey() → quick-edit hotkey shortcuts
// - showHelp() → screen-specific help dialog
}
The parent handles the mechanics. The child handles the semantics. SystemConfigScreen just populates the model with BBS name, welcome message, auto-approve, etc. AiConfigScreen populates it with AI enabled, model, temperature, system prompt. Both get the same navigation, the same editing, the same look — because they’re both using the same parent.
The Model
SystemSettingsTableModel was made generic with clear() and addEntry() methods. Each entry has a key, a display value, and a SettingType (STRING, INT, BOOLEAN, TEXT):
public class SystemSettingsTableModel extends AbstractTableModel {
public enum SettingType { STRING, INT, BOOLEAN, TEXT }
public static class SettingEntry {
public final String key;
public final String value;
public final SettingType type;
}
public void clear() { entries.clear(); }
public void addEntry(String key, String value, SettingType type) {
entries.add(new SettingEntry(key, value, type));
}
}
The type determines what happens when you press Enter: STRING and TEXT open a TextInputDialog pre-filled with the current value. INT opens the same dialog with integer validation. BOOLEAN doesn’t open a dialog at all — it toggles the value in place, which is faster and less annoying.
The Results
SystemConfigScreen was refactored first. The old version had nine vertical labels and custom edit logic for each. The new version extends TableConfigScreen, populates the model in rebuildModel(), and overrides onCustomKey() for the quick-edit hotkeys (N for BBS name, T for theme, W for welcome message, etc.). The status bar was shortened to just the essentials: [Enter] Edit [A] Auto-Approve [R] Menu Transitions [?] Help [Q] Back.
AiConfigScreen was the bigger transformation. It went from nine vertical labels to the same table layout, with [Space] to toggle, [S] to save and reload, and [T] to test the Ollama connection. The save-and-reload hotkey calls SystemSettingsService to persist changes and refresh the running AI configuration without restarting the BBS.
Combined, the extraction eliminated about 150 lines of duplicated boilerplate. More importantly, every config screen now looks and behaves the same way. A user who knows how to edit the system config already knows how to edit the AI config. The muscle memory transfers.
The Lesson
The pattern here is general: when you have N screens doing the same thing with different data, extract the common behavior into a parent class. The child classes become small — just the data mapping and the screen-specific hotkeys. The parent class handles the mechanics once.
This isn’t a new idea. It’s Template Method from the Gang of Four. But it’s easy to skip when you’re building screens one at a time and each one seems different enough. The trick is recognizing when “different enough” is actually “the same thing with different labels.” Config screens are almost always that.
The table layout also solved the alignment problem for free. Tables handle column widths automatically. No more manual padding, no more misaligned values. The terminal does the work.
#BBS #terminal #jterm #refactoring #opensource