Skip to content
2 changes: 2 additions & 0 deletions skills/terminal-ui-polisher/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ menu family.

Reference files:

- `skills/terminal-ui-polisher/assets/box-templates.md` contains reusable box templates; use it before writing manual box math.
- `terminal/menus/mq-hal-menu.sh` is the clearest submenu pattern.
- `terminal/menus/mq-performance-menu.sh` shows status-driven panels.
- `terminal/menus/mq-main-menu.sh` shows the main command surface.
Expand Down Expand Up @@ -189,6 +190,7 @@ Layout rules:
- Text must fit at 60 columns because `surface_terminal_width` clamps there.
- If a dependency is missing, render a panel explaining the missing binary and the exact check command.
- Menus should work sourced from mqlaunch and directly as scripts.
- For legacy launchers with local frame helpers, copy the width math from `assets/box-templates.md`; do not draw nested two-column boxes.

When reviewing a proposed menu, reject it if it:

Expand Down
96 changes: 96 additions & 0 deletions skills/terminal-ui-polisher/assets/box-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Terminal Box Templates

Use these templates when creating or repairing mqlaunch terminal boxes.

The goal is to avoid one-off width math. Prefer shared `surface_*` helpers for
new menus. Use the `frame_*` template only when maintaining legacy launchers
that already own their own frame helpers, such as `terminal/launchers/gitlaunch.sh`.

## mqlaunch Surface Menu

Use this for new menu modules under `terminal/menus/`.

```bash
render_example_panel() {
local width panel_color
width="$(surface_terminal_width)"
panel_color="$(surface_panel_color)"

surface_panel_header "Example" "Mode" "$width" "$panel_color"
surface_row "SECTION" "$width" "$panel_color"
surface_split_row "1. First action" "2. Second action" "$width" "$panel_color"
surface_split_row "3. Third action" "4. Fourth action" "$width" "$panel_color"
surface_row "" "$width" "$panel_color"
surface_split_row "b. Back" "x. Exit launcher" "$width" "$panel_color"
surface_row "" "$width" "$panel_color"
surface_row "Status: ready" "$width" "$panel_color"
surface_bottom "$width" "$panel_color"
}
```

Rules:

- Use `surface_terminal_width`; never hardcode box width.
- Use `surface_split_row`; do not draw a manual center divider.
- Keep section labels short uppercase nouns.
- Keep every option label short enough to fit at the 60-column clamp.
- Keep border color from `surface_panel_color`.

## Legacy Frame Helpers

Use this only for standalone launchers that cannot easily source `mq-ui.sh`.
This layout keeps every row exactly the same visible width as the top and bottom
borders.

```bash
UI_WIDTH=88
UI_INNER=$((UI_WIDTH - 4))

frame_two_col() {
local left right left_width right_width
left_width=$((UI_INNER / 2))
right_width=$((UI_INNER - left_width - 1))
left=$(truncate_text "$1" "$left_width")
right=$(truncate_text "$2" "$right_width")
printf "%b│%b %-${left_width}s %-${right_width}s %b│%b\n" \
"$C_BORDER" "$C_RESET" "$left" "$right" "$C_BORDER" "$C_RESET"
}

fallback_status_row() {
local label="$1"
local value="$2"
local color="${3:-}"
local label_width=8
local value_width
update_ui_width
value_width=$((UI_INNER - 10))
(( value_width < 1 )) && value_width=1
value=$(truncate_text "$value" "$value_width")
printf "%b│%b %b%-${label_width}s%b: %b%-${value_width}s%b %b│%b\n" \
"$C_BORDER" "$C_RESET" "$C_TITLE" "$label" "$C_RESET" "$color" "$value" "$C_RESET" "$C_BORDER" "$C_RESET"
}
```

Rules:

- `UI_INNER` is `UI_WIDTH - 4` because each row has border, space, text, space, border.
- Two-column rows should contain one outer box, not two nested boxes.
- `left_width + 1 + right_width` must equal `UI_INNER`.
- For status rows, `label_width + colon + space + value_width` must equal `UI_INNER`.
- If the prompt accepts `b`, the helper text must say so.
- In non-interactive tests, EOF should exit/back instead of looping on `Invalid`.

## Visual Target

```text
┌─ Gitlaunch ──────────────────────────────────────────────────────────────────┐
│ Host: Zephyr User: mansys Repo: macos-scripts Branch: main │
│ Git: Clean Staged: 0 Unstaged: 0 Untracked: 0 │
├──────────────────────────────────────────────────────────────────────────────┤
│ 1. Git status 2. Pull │
│ 3. Suggest commit 4. Safe push │
│ 5. Open repo 6. Dev mode │
│ 7. Switch repo 8. Auto commit + push │
│ 9. Recent log b. Back │
└──────────────────────────────────────────────────────────────────────────────┘
```
96 changes: 42 additions & 54 deletions terminal/launchers/gitlaunch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,24 @@ UNTRACKED_COUNT=0
# ASCII ART
# ------------------------
function render_ascii() {
local pulse line
local -a pulses dark_lines amber_lines

dark_lines=(
" ▄████ ██▓▄▄▄█████"
" ██▒ ▀█▒▓██▒▓ ██▒ ▓▒"
" ▒██░▄▄▄░▒██▒▒ ▓██░ ▒░"
" ░▓█ ██▓░██░░ ▓██▓ ░ "
" ░▒▓███▀▒░██░ ▒██▒ ░ "
" ░▒ ▒ ░▓ ▒ ░░ "
local i
local -a pulses figure labels

figure=(
" ███████"
" ██ ██ "
" ████████ "
" ████████████ "
" ██ ██ "
" ████ ████ "
)
amber_lines=(
" ██▓ ▄▄▄ █ ██ ███▄ █ ▄████▄ ██░ ██ "
"▓██▒ ▒████▄ ██ ▓██▒ ██ ▀█ █ ▒██▀ ▀█ ▓██░ ██▒"
"▒██░ ▒██ ▀█▄ ▓██ ▒██░▓██ ▀█ ██▒▒▓█ ▄ ▒██▀▀██░"
"▒██░ ░██▄▄▄▄██ ▓▓█ ░██░▓██▒ ▐▌██▒▒▓▓▄ ▄██▒░▓█ ░██ "
"░██████▒▓█ ▓██▒▒▒█████▓ ▒██░ ▓██░▒ ▓███▀ ░░▓█▒░██▓"
"░ ▒░▓ ░▒▒ ▓▒█░░▒▓▒ ▒ ▒ ░ ▒░ ▒ ▒ ░ ░▒ ▒ ░ ▒ ░░▒░▒"
labels=(
""
" Gitlaunch"
""
""
""
""
)

if [[ "$_BANNER_SHOWN" -eq 0 ]]; then
Expand All @@ -95,21 +95,14 @@ function render_ascii() {
sleep 0.05
done
printf "\033[2K"
printf "%b" "$C_TITLE"
for line in "${dark_lines[@]}"; do
printf '%s\n' "$line"
sleep 0.03
done
printf "%b" "$C_AMBER"
for line in "${amber_lines[@]}"; do
printf '%s\n' "$line"
sleep 0.03
for (( i=1; i<=${#figure[@]}; i++ )); do
printf "%b %s%b%s%b\n" "$C_AMBER" "${figure[$i]}" "$C_TITLE" "${labels[$i]}" "$C_RESET"
sleep 0.06
done
else
printf "%b" "$C_TITLE"
for line in "${dark_lines[@]}"; do printf '%s\n' "$line"; done
printf "%b" "$C_AMBER"
for line in "${amber_lines[@]}"; do printf '%s\n' "$line"; done
for (( i=1; i<=${#figure[@]}; i++ )); do
printf "%b %s%b%s%b\n" "$C_AMBER" "${figure[$i]}" "$C_TITLE" "${labels[$i]}" "$C_RESET"
done
fi

printf "%b" "$C_RESET"
Expand Down Expand Up @@ -523,7 +516,7 @@ function render_menu() {
frame_row "Git: $git_state Staged: $STAGED_COUNT Unstaged: $UNSTAGED_COUNT Untracked: $UNTRACKED_COUNT"
frame_mid
frame_two_col "1. Git status" "2. Pull"
frame_two_col "3. Suggest commit" "4. Safe push"
frame_two_col "3. Commit with suggested message" "4. Safe push"
frame_two_col "5. Open repo" "6. Dev mode"
frame_two_col "7. Switch repo" "8. Auto commit + push"
frame_two_col "9. Recent log" "b. Back"
Expand All @@ -548,40 +541,38 @@ function render_next_action() {

# Prompts for choice with script-level validation.
function prompt_choice() {
local prompt_sep input
local sep input
update_ui_width
prompt_sep="$(repeat_char "─" "$UI_WIDTH")"
sep="$(repeat_char "─" "$UI_WIDTH")"

printf "%b%s%b\n" "$C_BORDER" "$prompt_sep" "$C_RESET"
printf "\n%b%s%b\n" "$C_BORDER" "$sep" "$C_RESET"
printf "%bgitlaunch > %b\n" "$C_TITLE" "$C_RESET"
printf "%b%s%b\n" "$C_BORDER" "$prompt_sep" "$C_RESET"
printf "%b%s%b\n" "$C_BORDER" "$sep" "$C_RESET"
printf "%b>> press 1-9 or b%b\n" "$C_DIM" "$C_RESET"
if [[ -t 0 && -t 1 ]]; then
printf "\033[3A"
printf "\033[3A\r"
printf "%bgitlaunch > %b" "$C_TITLE" "$C_RESET"
fi

input=""
if [[ -t 0 ]]; then
read -rsk 1 input || input="b"
else
IFS= read -r input || input="b"
fi
IFS= read -r input </dev/tty || true

printf "%s\n" "$input"
if [[ -t 0 && -t 1 ]]; then
printf "\033[2B"
printf "\033[2B\r\n"
fi

input="${input[1,1]}"
choice="$input"
}

# Pauses inside gitlaunch without changing menu level.
function pause_git_menu() {
local pause_reply

printf "%bPress Enter to return to Git menu...%b" "$C_DIM" "$C_RESET"
read pause_reply
local pause_reply=""
stty sane 2>/dev/null || true
echo ""
printf "%bPress Enter to return to Gitlaunch menu...%b" "$C_DIM" "$C_RESET"
IFS= read -r pause_reply </dev/tty || true
choice=""
}

# ------------------------
Expand Down Expand Up @@ -738,7 +729,7 @@ function create_pr_branch_for_push() {
echo "GitHub requires these changes to go through a pull request."
echo "Suggested PR branch: $pr_branch"
printf "%bCreate and push this PR branch? [Y/n]: %b" "$C_LABEL" "$C_RESET"
read confirm
read confirm </dev/tty

if [[ "$confirm" =~ ^[Nn]$ ]]; then
echo "Push cancelled. Commit remains local on $base_branch."
Expand Down Expand Up @@ -862,21 +853,17 @@ function run_ai_commit() {
analyze_diff

printf "%bProceed with commit? (y/n): %b" "$C_LABEL" "$C_RESET"
read proceed
read proceed </dev/tty

if [[ "$proceed" != "y" ]]; then
echo "❌ Commit cancelled"
pause_git_menu
return 0
fi

git add .
if git commit -m "$SUGGESTED"; then
pr_aware_push "$SUGGESTED"
fi

pause_git_menu
return 0
}

# ------------------------
Expand Down Expand Up @@ -907,6 +894,7 @@ trap 'printf "%b" "$C_RESET"' EXIT
while true; do
detect_repo
clear_screen

status_check
next_action
render_next_action
Expand All @@ -931,7 +919,7 @@ while true; do
;;
3)
run_ai_commit
continue
pause_git_menu
;;
4)
safe_push
Expand Down
Loading