Browse Source

refactor(skills/fleet-ops): no glyphs at tree junctions

A glyph at the junction of a tree connector reads as part of the
connector and breaks the eye-line that gives the tree its meaning.
Drop the state icon from group headers — the label text plus color
(yellow=RUNNING/CONFLICT, green=READY/LANDED, red=FAILED) carries the
state without sitting on the junction.

Before:                          After:
  ├─ ⏳ RUNNING (1)                ├─  RUNNING (1)
  │  └─ alpha     12m              │  └─ alpha     12m
  └─ 🚀 LANDED  (1)                └─  LANDED  (1)
     └─ delta    1h                   └─ delta    1h

The │ down column 0 is now unbroken from first group to last leaf
above the terminating └─. Trace upward from any connector: there's a
clean │ or whitespace, never a glyph. Documented as a hard rule in
DESIGN.md alongside both 2-level (groups → leaves) and 3-level
(groups → branches → leaves) examples.

Leaves may still carry icons in views where it helps — leaves are
terminal, so a glyph there has no junction to break. fleet-ops 2-level
view doesn't need them; the 3-level repo example in DESIGN.md shows
where they fit.

Tests: e2e ASCII assertion now checks for ASCII tree connectors (+- /
backtick-) instead of legacy state-icon glyphs, plus a new no-Unicode-
bleed check. 21/21 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0xDarkMatter 1 month ago
parent
commit
18d7cbddc7
3 changed files with 62 additions and 44 deletions
  1. 44 34
      docs/DESIGN.md
  2. 13 8
      skills/fleet-ops/scripts/fleet.sh
  3. 5 2
      tests/skills/functional/fleet-ops/e2e.sh

+ 44 - 34
docs/DESIGN.md

@@ -74,32 +74,38 @@ terminal width so the header reads as the section's banner.
 
 ### Grouped tree (default body)
 
-**Tree-control rule:** the connectors `├─ │ └─` are the scaffold. They
-run in their own column from the top of the body to the last leaf, and
-**nothing breaks the vertical**. Icons and labels live to the right of
-the connector, not between it and its parent's `│`. If you find yourself
-wanting to put an icon where the `│` should continue, you don't have a
-tree — you have a list with decorations.
+**Tree-control rule:** the connectors `├─ │ └─` are the scaffold.
+**Nothing sits at a junction.** A junction is the point where a node's
+connector meets its parent's vertical — putting a glyph there breaks
+the eye-line that gives the tree its meaning.
+
+- Group headers (interior nodes — they have children below) get **no
+  icon**. State is carried by the label text plus color.
+- Leaves (terminal nodes — nothing continues below them) **may** carry
+  an icon, since there's no vertical line to interrupt.
+- If you find yourself wanting an icon on an interior node, ask whether
+  it's really a group or just a decorated leaf — the answer is usually
+  the latter.
 
 #### 2-level: groups → leaves
 
-The default for state-bucketed views (lanes, PR checks, jobs).
+The default for state-bucketed views (lanes, PR checks, jobs). Group
+labels read as plain text, the `│` runs unbroken down column 0.
 
 ```
 ── fleet ─────────────────────────────────────────────────────  4 lanes · 3 active
-├─  RUNNING   (2)
+├─  RUNNING     (2)
 │  ├─ feat/auth-rewrite             12m
 │  └─ spike/wasm-eval               34m
-├─  READY     (1)
+├─  READY       (1)
 │  └─ fix/cache-bust                2m
-└─ 🚀 LANDED    (1)
+└─  LANDED      (1)
    └─ chore/bump-deps               1h
 ```
 
-Notice: the `│` running down column 0 is unbroken from the first group
-to the last child of the second-last group. The `└─` on `LANDED`
-terminates the vertical cleanly. Empty groups are omitted — never
-render `(0)`.
+The double space after each connector (`├─ ` + leading space on the
+label) gives the eye a small breath before the label, reinforcing that
+the connector is structural and the label is content.
 
 Why grouped instead of flat: when ten lanes are in flight, scanning a
 flat table for "what's actually ready to land?" forces your eyes to do
@@ -110,24 +116,28 @@ glance whether the answer is none, one, or twelve.
 
 For hierarchies with intermediate structure — repos with branches with
 files, projects with packages with tests, lanes with commits with
-patches. Same rule: connectors don't break.
+patches. Interior nodes (`main`, `src/`, `utils/`) stay icon-free; only
+the leaves carry glyphs (state of the file).
 
 ```
 ── repo ──────────────────────────────────────────────────────  X:/Forge/claude-mods · 2 worktrees
-├─ 📦 main
-│  ├─ src/
-│  │  ├─ index.ts                   modified
-│  │  └─ utils/
-│  │     ├─ format.ts               modified
-│  │     └─ parse.ts                added
+├─  main
+│  ├─  src/
+│  │  ├─ index.ts                   ⚠️  modified
+│  │  └─  utils/
+│  │     ├─ format.ts               ⚠️  modified
+│  │     └─ parse.ts                added
 │  └─ README.md                     clean
-└─ 🌿 feat/auth-rewrite
-   └─ src/
-      ├─ auth.ts                    new
-      └─ middleware/
-         └─ session.ts              modified
+└─  feat/auth-rewrite
+   └─  src/
+      ├─ auth.ts                    new
+      └─  middleware/
+         └─ session.ts              ⚠️  modified
 ```
 
+Look at any `├─` or `└─` and trace upward: there's always a clean `│`
+or empty space directly above it, never a glyph. That's the rule.
+
 Each level adds a 3-column indent: `│  ` while the ancestor still has
 siblings to render, `   ` once the ancestor is on its last sibling. The
 helpers in `term.sh` (`term_tree_node`, `term_tree_indent`,
@@ -198,19 +208,19 @@ Disabled when stdout isn't a TTY, or `NO_COLOR` is set. Forced on with
 
 ```
 ── fleet ─────────────────────────────────────────────────────  3 lanes · 2 active
-├─  RUNNING   (1)
+├─  RUNNING     (1)
 │  └─ feat/auth-rewrite             12m
-├─  READY     (1)
+├─  READY       (1)
 │  └─ fix/cache-bust                2m
-└─ 🚀 LANDED    (1)
+└─  LANDED      (1)
    └─ chore/bump-deps               1h
 ```
 
-The header rule survives — it's the strongest visual cue that you're
-inside a skill's output. The flat table gives way to a tree where
-groups are first-class branches: the `│` runs uninterrupted from the
-first group to the last leaf above the terminating `└─`, and icons
-sit *after* the connector instead of breaking it.
+The header rule stays — strongest cue you're inside a skill's output.
+Group labels are icon-free so the `│` running down column 0 is unbroken
+from the first group to the last leaf. State is carried by the label
+text and color (yellow for RUNNING, green for READY/LANDED). The tree
+reads as a tree, not a list with decorations.
 
 ### `git-ops/status` reformatted in the same language
 

+ 13 - 8
skills/fleet-ops/scripts/fleet.sh

@@ -187,16 +187,21 @@ cmd_fleet() {
   for i in "${active_groups[@]}"; do
     local n=${state_counts[$i]}
     local state=${order[$i]}
-    local icon
-    icon=$(term_state_icon "$state")
-    [[ -z "$icon" || "$icon" == "?" ]] && icon="$ICON_UNKNOWN"
 
-    # Group line — connector at column 0, then icon + label live to its right.
-    local g_conn
+    # Group line — connector + plain label. NO icon at the junction:
+    # a glyph here breaks the eye-line of the tree's vertical. State is
+    # carried by label + color (and the per-leaf glyph if needed).
+    local g_conn group_label
     g_conn=$(term_tree_connector "$g_idx" "$g_last")
-    local group_label
-    group_label="$icon $state"
-    term_tree_node "" "$g_conn" "$group_label" "($n)"
+    case "$state" in
+      RUNNING|PENDING)  group_label=$(term_color yellow "$state") ;;
+      READY)            group_label=$(term_color green  "$state") ;;
+      LANDED|DONE|OK)   group_label=$(term_color green  "$state") ;;
+      FAILED|ERROR)     group_label=$(term_color red    "$state") ;;
+      CONFLICT|WARN)    group_label=$(term_color yellow "$state") ;;
+      *)                group_label="$state" ;;
+    esac
+    term_tree_node "" "$g_conn " "$group_label" "($n)"
 
     # Children indent = continuation of this group's connector.
     local child_prefix

+ 5 - 2
tests/skills/functional/fleet-ops/e2e.sh

@@ -161,9 +161,12 @@ echo "$scrub_out" | grep -q "FORBIDDEN" && ok "scrub-check flagged TODO_SCRUB" |
 git -C "$SCRATCH" checkout main >/dev/null 2>&1
 
 # ── ASCII fallback ──
-step "FLEET_ASCII=1 swaps icons"
+step "FLEET_ASCII=1 swaps glyphs to ASCII"
 ascii_out=$(FLEET_ASCII=1 bash "$FLEET" fleet 2>&1 || true)
-echo "$ascii_out" | grep -qE '\[\*\]|\[\+\]|\[\.\]' && ok "ASCII icons rendered" || fail "ASCII icons not used"
+# Tree connectors carry the ASCII signal now (+- / `-); group headers
+# no longer carry icons (they sat at the junction and broke the tree).
+echo "$ascii_out" | grep -qE '\+-|`-' && ok "ASCII tree connectors rendered" || fail "ASCII connectors not used"
+echo "$ascii_out" | grep -qE '├─|└─|│' && fail "Unicode connectors leaked in ASCII mode" || ok "no Unicode in ASCII mode"
 
 # ── summary ──
 echo ""