Make the screen explain itself: labels, tools, help pages, hints

For developers new to submodules (notes/usability-plan.md, D32):
- x and X read "restore to pin" and "restore committed pins"
- problems only, filter and fetch are shown in the footer
- ? shows every key in titled columns, then a legend of the status
  words, symbols and spine roles; esc closes it
- u reads "pull (fast-forward)" on the superproject
- the detail pane suggests f when the last fetch is old
- a hint above the footer until the first key
- the header says "staged", like the tree

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 23:26:32 +02:00
co-authored by Claude Opus 5.5
parent b205576759
commit c9932932bb
19 changed files with 816 additions and 78 deletions
+4
View File
@@ -42,3 +42,7 @@ Next steps, roughly in order:
1. (done) Write actions: restore, switch branch, update pin, see
[actions.md](actions.md). Stage pin was dropped (D29).
2. (done) "Problems only" (`!`) view, reusing the tree's filter.
3. Usability pass for users new to submodules, see
[notes/usability-plan.md](../notes/usability-plan.md). The low-effort
items are done (D31, D32); next are the issue hints and the palette
that fits the selection.
+4 -3
View File
@@ -390,13 +390,14 @@ runs the switch flow with it (`SwitchLoss`, `Switch`).
All three are submodule keys (`keyMap.Submodule`), shown in the detail
pane's palette, or in the footer when the pane can't show it. They are
always offered, the palette is static: restore is idempotent, and `u` says
why it can't update.
why it can't update. Only `u`'s label follows the selection: on the header
it reads `pull (fast-forward)`.
| Key | Action | Pressed when not possible (flash) |
|---|---|---|
| `x` restore | Restore the selection | `this data source can't restore` (mock); `wait for the restore of … to finish` |
| `x` restore to pin, `X` restore committed pins | Restore the selection; `X` see [Hard restore](#hard-restore) | `this data source can't restore` (mock); `wait for the restore of … to finish` |
| `b` switch branch | [Switch branch](#switch-branch) | `this data source can't switch branches`; `wait for the restore of … to finish`; `src/app is not initialized; restore it first` |
| `u` update pin | [Update pin](#update-pin) | `this data source can't update pins`; `wait for the restore of … to finish`; `the superproject has no upstream branch`; `src/app is still loading`; `src/app is not initialized; restore it first`; `the pin of src/app is not on a branch`; `the pin of src/app has diverged from origin/main; b switches the branch`; `the pin of src/app is ahead of origin/main`; `src/app is up to date with origin/main` (the superproject without `the pin of`); after the check `can't update src/app: main is a local branch; b switches to it` |
| `u` update pin (`pull (fast-forward)` on the header) | [Update pin](#update-pin) | `this data source can't update pins`; `wait for the restore of … to finish`; `the superproject has no upstream branch`; `src/app is still loading`; `src/app is not initialized; restore it first`; `the pin of src/app is not on a branch`; `the pin of src/app has diverged from origin/main; b switches the branch`; `the pin of src/app is ahead of origin/main`; `src/app is up to date with origin/main` (the superproject without `the pin of`); after the check `can't update src/app: main is a local branch; b switches to it` |
`R` would sit next to `r` (reload), a dangerous mix-up for a destructive
action, hence `x`.
+1 -1
View File
@@ -37,7 +37,7 @@ internal/tui/ Bubble Tea application
detail/ detail pane; changes.go: change preview
header/ header line
branches/ branch picker popup (search line + list)
footer/ key bar (navigation left, global right) and flash messages
footer/ key bar (navigation and tools left, global right), full help pages (keys, legend) and flash messages
```
### Layering rules
+26
View File
@@ -608,3 +608,29 @@ submodule's own staged pin (in its parent's index), which `lossOf` doesn't
see. The `c` answer of `x` stays silent: the user picked it in a question.
**Consequences.** `X` asks more often, but only where it changes what is
recorded. Without staged pins or other losses it still runs at once.
## D32 — The screen explains itself
**Context.** The target user is a developer who doesn't know submodules
well and doesn't read manuals. The walkthrough found labels that don't say
what a key does (`x restore`, `X hard restore`), important keys found only
in `?` (`!`, `/`, `f`), and status words explained nowhere.
**Decision.**
- Labels name the target: `x restore to pin`, `X restore committed pins`.
"Hard" suggested "discards changes", which both do.
- A fourth key group, Tools (`!`, `/`, `f`), in the footer after the
navigation keys, dropped before them.
- On the header, `u` reads `pull (fast-forward)`: the label follows the
selection, the key doesn't.
- The full help gets titled columns, and a second page, a legend of the
status words, symbols and spine roles in their colors. Issue levels move
to `IssueKind.Level()` so the legend can't drift from the tree. `esc`
closes either page: cycling back with `?` alone was not obvious.
- A start hint above the footer until the first key.
- `fetched <age> · f fetch` in the detail pane when the last fetch is older
than a day.
**Alternatives.** A first-run screen or tutorial: needs state, and is
skipped. Contextual palettes that grey out impossible keys: larger, kept for
later (item 10 of the usability plan).
**Consequences.** `footer.Keys` drops `help.KeyMap` for `Tools`,
`HelpGroups` and `LegendKeys`; the footer renders the full help itself.
+51 -18
View File
@@ -26,7 +26,7 @@
my_submodule_3 unpushed origin/main ↑1 │ ┊ 1 commit
libs/ │ tip ● 6f5e4d3 Release 2.1.0
logging origin/main ↓3 │
↑↓/jk move • ←→/hl fold • ⇧←→/HL fold all • space toggle • ? help • q quit
↑↓/jk move • ←→/hl collapse/expand • ⇧←→/HL all • space toggle • ! problems only • / filter • f fetch ? help • q quit
```
| Area | Height / width |
@@ -34,14 +34,18 @@
| Header | 1 line |
| Tree | Remaining width |
| Detail pane | 40% of the width, at least 36 columns; hidden below 90 columns |
| Filter line | 1 line above the footer, only while filtering |
| Line above the footer | 1 line, only while filtering, showing problems only, during a restore, after a failure, or until the first key (hint) |
| Footer | 1 line (more with full help) |
### Key bar and palette
Keys come in three groups (`keyMap.Navigation`, `.Submodule`, `.Global`):
Keys come in four groups (`keyMap.Navigation`, `.Tools`, `.Submodule`,
`.Global`):
- **Navigation** (move, fold, toggle): left in the footer.
- **Navigation** (move, collapse/expand, toggle): left in the footer.
- **Tools** (`!` problems only, `/` filter, `f` fetch): in the footer after
the navigation keys, most useful first. They act on the whole list and
would otherwise be found only in the full help (D32).
- **Global** (`?`, `q`): right-aligned in the footer, always kept. If a newer
release exists, `q quit and update to vX` follows them as the right-most
item, on a highlighted background (`Theme.Highlight`: black on Primary), and
@@ -50,11 +54,36 @@ Keys come in three groups (`keyMap.Navigation`, `.Submodule`, `.Global`):
bottom of the detail pane, wrapped onto a second line if the pane is too
narrow for all keys (none is cut off). It is left out if the pane is
shorter than 12 lines. Whenever the palette isn't shown (pane hidden or too short), these
keys move into the footer after the navigation keys.
keys move into the footer after the tools. With the header selected, `u`
reads `u pull (fast-forward)`, which is what it does there (D29).
If the footer is too narrow, keys are dropped from the left group's end
(submodule keys first); the global keys stay. Full help (`?`) lists all keys
in columns, whatever the size. Keys work whether or not they are shown.
(submodule keys first, then the tools); the global keys stay. Keys work
whether or not they are shown.
### Full help
`?` steps through two pages, then back to the key bar; `esc` closes either
page at once (before it would close the filter):
1. **Keys**: every key in titled columns: `Move`, `Jump`, `Selected
submodule`, `All submodules`, `App`. The `App` column says `? what words
mean` and `esc close`.
2. **Legend**: what the words and symbols mean, in their colors: `Status`
(each issue word, `staged`, `in sync`), `Symbols` (`●`, `↑n ↓n`, `+n`,
`·k`, `[n]`), `Commits` (the spine's roles). Then `App` with `esc close`
(`?` closes it too).
The words come from `Theme.Legend()`; issue colors from
`IssueKind.Level()`, so they are the tree's.
Titles are muted and bold. Columns that don't fit are left out from the
right, marked with `…`; the `App` column is always kept.
### Start hint
Until the first key, the line above the footer reads muted `new here? ?
shows every key and what the words mean`. Every start, no state is kept.
Any other line above the footer (filter, restore, …) takes precedence.
### Tree row
@@ -85,7 +114,9 @@ For the superproject (header selected), the `pin` and `you` roles become
1. Directory (muted) and name (bold); `fetched <age>` of the submodule,
muted, right-aligned in the name's line (left out if the pane is too
narrow, or the submodule is missing)
narrow, or the submodule is missing). If the last fetch is older than a
day, or there was none, it continues `· f fetch` (key bold), if there is
room
2. One line per issue: word (colored, 11 columns) + explanation;
`in sync nothing to do` if there are none and no pin is staged;
`staged` and `detached HEAD on pin` as information
@@ -213,7 +244,7 @@ shows the superproject's own state word like a tree row (`in sync`,
`changed`, `unpushed +1`, spinner, `error`). The counts that follow are
about submodules only.
`path branch ↑↓ state n error · <count> <issue>… · n pin staged · n loading · n submodules · <fetch>`
`path branch ↑↓ state n error · <count> <issue>… · n staged · n loading · n submodules · <fetch>`
The summary and `<fetch>` form one group at the right edge; the path,
branch and state stay at the left. If the line is too narrow it is cut with
@@ -287,7 +318,8 @@ colors in components.
| `H` / `shift+←` / `ctrl+←` | Collapse all (resets remembered expansion); select the top-level ancestor |
| `home` / `g`, `end` / `G` | Header (superproject) / last submodule |
| `pgup`, `pgdn` | Move by one screen |
| `?` | Toggle full help |
| `?` | Full help: keys, then the legend, then closed, see [Full help](#full-help) |
| `esc` | Close the full help; else close the filter |
| `q`, `ctrl+c` | Quit. With a newer release: `q` quits and updates, `Q` and `ctrl+c` quit |
| `f` | Fetch all repositories (does nothing while a fetch runs) |
| `r` | Reload everything (e.g. after editing files) |
@@ -295,7 +327,7 @@ colors in components.
| `/` | Open the filter, see [Filter](#filter) |
| `!` | Toggle problems only, see [Problems only](#problems-only) |
| `x` | Restore the selection, see [Restore](#restore) |
| `X` | Hard restore the selection: committed pins, staged pins dropped at every depth, see [Restore](#restore) |
| `X` | Restore the selection to the committed pins, staged pins dropped at every depth, see [Restore](#restore) |
"First child" always means the first child **as displayed** (children are
grouped by directory).
@@ -425,15 +457,16 @@ where the palette can't be shown, see [Key bar and palette](#key-bar-and-palette
| `home` / `g`, `end` / `G` | Header / last submodule | Navigation | Help |
| `pgup`, `pgdn` | Move by one screen | Navigation | Help |
| `c` | Copy path | Submodule | Palette, Help |
| `u` | Update pin to the tip of its ref (superproject: fast-forward to upstream) | Submodule | Palette, Help |
| `u` | Update pin to the tip of its ref (superproject: fast-forward to upstream, label `pull (fast-forward)`) | Submodule | Palette, Help |
| `b` | Switch branch (popup) | Submodule | Palette, Help |
| `x` | Restore | Submodule | Palette, Help |
| `X` | Hard restore | Submodule | Palette, Help |
| `f` | Fetch all repositories | Global | Help |
| `x` | Restore to pin | Submodule | Palette, Help |
| `X` | Restore committed pins | Submodule | Palette, Help |
| `!` | Problems only (toggle) | Tools | Bar, Help |
| `/` | Filter | Tools | Bar, Help |
| `f` | Fetch all repositories | Tools | Bar, Help |
| `r` | Reload everything | Global | Help |
| `/` | Filter | Global | Help |
| `!` | Problems only (toggle) | Global | Help |
| `?` | Toggle full help | Global | Bar, Help |
| `?` | Full help: keys, legend, close | Global | Bar, Help |
| `esc` | Close the full help (and the filter, see [Filter](#filter)) | Global | Help |
| `q`, `ctrl+c` | Quit (`Q`, `ctrl+c` while an update is offered) | Global | Bar, Help |
| `q` | Quit and update to the newer release (only if there is one) | Global | Bar (highlighted), Help |