# KB2040 Hidden Signer — Display Screens

Reference for the on-device UI so we can iterate on screen designs.

- Panel: **SSD1327 128x128 grayscale OLED** (I2C, STEMMA QT, addr `0x3D`).
- Rendering source: [`display.cpp`](./display.cpp), driven by call sites in [`kb2040_hidden_signer.ino`](./kb2040_hidden_signer.ino).
- Grayscale levels in use:
  - dim (headers / outlines): `0x04`
  - bar fill: `0x08`
  - bright text: `0x0F`
- Each screen historically ended with a bottom **status line** at `y=118`, drawn by `drawStatusLine()`. The status-line writes are currently commented out in the sketch (they were being overwritten by debug/diagnostic prints), so that line now stays effectively empty unless re-enabled.

The boxes below are schematic (the panel is 128x128 px, not character-aligned). They show layout intent, relative position, and the text/labels actually printed.

---

## 1. Volume View
Source: `drawVolumeView()` in [`display.cpp`](./display.cpp:60).
Top-level "media mode" screen. Header switches between `MODE: MEDIA` / `MODE: SIGNER`.

```
+--------------------------------+
| MODE: MEDIA                    |  <- tiny, dim (size 1)
|                                |
|             VOL                |  <- size 2, dim
|                                |
|                                |
|           ####  ####           |
|          ##  ## #  ##          |  <- "50" centered, size 6
|          ##  ## #  ##          |     (huge digits)
|          ##  ## #  ##          |
|           ####  ####           |
|                                |
|   +------------------------+   |  <- volume bar outline
|   |##########              |   |     fill = volume %
|   +------------------------+   |
|                                |
| [status line]                  |  <- currently disabled
+--------------------------------+
```

---

## 2. Signer Menu
Source: `drawSignerMenu()` in [`display.cpp`](./display.cpp:102).
Two items; `>` marks the current selection (shown here on index 0).

```
+--------------------------------+
| SIGNER MENU                    |  <- size 1, dim
|                                |
|                                |
|  >  Generate new               |  <- size 2, selected
|                                |
|                                |
|     Enter mnemonic             |  <- size 2
|                                |
|                                |
|                                |
|                                |
| [status line]                  |  <- currently disabled
+--------------------------------+
```

---

## 3. Enter Mnemonic (in-place 2-column grid)
Source (planned): `drawEnterMnemonic()` in [`display.cpp`](./display.cpp).
Replaces the old separate `PICK FIRST LETTER` modal + `WORD PICK` modal with a
single grid that looks like the Generated Phrase screen but is filled in by the
user. Header reads `ENTER MNEMONIC`.

Layout: 2 columns x 6 rows (slots 1-6 left, 7-12 right), same geometry as the
Generated Phrase screen. All 12 numbers are shown; words are blank until entered.
A cursor marks the active slot (starts at slot 1).

```
+--------------------------------+
| ENTER MNEMONIC                 |  <- size 1, dim
|> 1 m         7                 |  <- cursor '>' on active slot
|  2           8                 |     slot 1 building: "m"
|  3           9                 |
|  4          10                 |
|  5          11                 |
|  6          12                 |
|                                |
| back     letter                |  <- footer legend (stage-dependent)
+--------------------------------+
```

### Per-word 3-stage entry (in the active slot)

Only the letters chosen so far are shown; a full word appears only at stage 3.

| Stage | Scroll wheel cycles                | Active slot shows | PLAY does                  |
| ----- | ---------------------------------- | ----------------- | -------------------------- |
| 1     | first letter `a`..`z`              | `m`               | lock letter 1 -> stage 2   |
| 2     | valid 2nd letters for letter 1     | `mo`              | lock letter 2 -> stage 3   |
| 3     | words matching first two letters   | `mosaic`          | select word -> next slot   |

- After slot 12's word is selected: build phrase, run
  `mnemonic_validate_basic()`, then go to Signer Ready (or INVALID modal).

### BACK behavior (context-sensitive erase)

`BACK` = the NEXT button (PREV stays unused here), consistent with other entry
screens.

| Position                         | BACK action                                   |
| -------------------------------- | --------------------------------------------- |
| Stage 3 (choosing word)          | erase 2nd letter -> return to stage 2         |
| Stage 2 (choosing 2nd letter)    | erase 1st letter -> return to stage 1         |
| Stage 1, slot N > 1              | clear previous slot's word, move cursor back to slot N-1 at stage 1 |
| Stage 1, slot 1                  | exit to Signer Menu                           |

### Footer legend (stage-dependent)

| Stage   | PREV (left) | PLAY (center) | NEXT (right) |
| ------- | ----------- | ------------- | ------------ |
| Stage 1 | `-`         | `letter`      | `back`       |
| Stage 2 | `-`         | `letter`      | `back`       |
| Stage 3 | `-`         | `select`      | `back`       |

### New wordlist helpers required (in `mnemonic_kb.cpp` / `.h`)

- `mnemonic_second_letters(char first, char *out, uint8_t *count)`
  -> set of valid 2nd letters given the 1st letter.
- `mnemonic_prefix_range(char first, char second, uint16_t *start, uint16_t *count)`
  -> wordlist index range for all words starting with those two letters.

(`mnemonic_letter_range()` already provides the first-letter range and can back
the stage-1 -> stage-2 transition.)

---

## 4. Mnemonic Page (Generated Phrase)
Source: `drawMnemonicPage()` in [`display.cpp`](./display.cpp:152).
Shows generated phrase in one screen: 2 columns × 6 rows (all 12 words, no spacer rows).

```
+--------------------------------+
| GENERATED PHRASE               |  <- size 1, dim
|  1 abandon   7 issue           |  <- size 1, tight rows
|  2 ability   8 jungle          |
|  3 able      9 kitchen         |
|  4 about    10 lemon           |
|  5 above    11 mosquito        |
|  6 absent   12 napkin          |
|                                |
| PLAY confirm  NEXT cancel      |  <- size 1
|                                |
| [status line]                  |  <- currently disabled
+--------------------------------+
```

---

## 5. Message (generic 2-line modal)
Source: `drawMessage()` in [`display.cpp`](./display.cpp:193).
Generic title + 2 lines. Reused for several prompts.

Example — **Approval prompt** (title `APPROVAL`):

```
+--------------------------------+
| APPROVAL                       |  <- size 1, dim
|                                |
|                                |
|  PLAY=Allow                    |  <- size 2
|                                |
|  PREV=Deny                     |  <- size 2
|                                |
|                                |
|                                |
|                                |
|                                |
| [status line]                  |  <- currently disabled
+--------------------------------+
```

Other current uses of this same screen:
- `PICK FIRST LETTER` with lines `First letter` / `Letter: a`
- `INVALID` with `Bad mnemonic` / `NEXT to return`
- `ERROR` with `Generate failed` / `Try again`

---

## 6. Signer Ready
Source: `drawSignerReady()` in [`display.cpp`](./display.cpp:210).
Shown while waiting for the host to request signing.

```
+--------------------------------+
| SIGNER READY                   |  <- size 1, dim
|                                |
|   +------------------------+   |
|   |    Awaiting            |   |  <- size 2
|   |                        |   |
|   |       host             |   |  <- size 2
|   +------------------------+   |
|                                |
|  NEXT/PLAY: menu               |  <- size 1
|                                |
| [status line]                  |  <- currently disabled
+--------------------------------+
```

---

## Notes / observations for redesign

- The bottom status line frequently **duplicated** the in-body hint text (e.g. Word Pick showed `ENC scroll PLAY select` both in the body and in the status line). With status writes disabled, that redundancy is gone.
- `PICK FIRST LETTER` reuses the generic Message screen rather than having a dedicated layout — a candidate for a purpose-built screen.
- All hint text (`ENC` / `PLAY` / `NEXT` / `PREV`) refers to the physical controls: rotary encoder + the PLAY/NEXT/PREV buttons.
```

---

## Screen ↔ render-function map

| Screen          | Render function       | display.cpp |
| --------------- | --------------------- | ----------- |
| Volume View     | `drawVolumeView()`    | [60](./display.cpp:60) |
| Signer Menu     | `drawSignerMenu()`    | [102](./display.cpp:102) |
| Word Pick       | `drawWordPick()`      | [123](./display.cpp:123) |
| Mnemonic Page   | `drawMnemonicPage()`  | [152](./display.cpp:152) |
| Message (modal) | `drawMessage()`       | [193](./display.cpp:193) |
| Signer Ready    | `drawSignerReady()`   | [210](./display.cpp:210) |
| Status line     | `drawStatusLine()`    | [53](./display.cpp:53) |

---

# PROPOSAL: three-zone button legend footer

The three physical buttons sit directly below the screen in this order:

```
  PREV    PLAY/PAUSE    NEXT
   ^           ^          ^
 (left)    (center)    (right)
```

Idea: replace the free-text status line with a fixed **3-zone legend** along the
bottom of every screen. Each zone shows a single short verb for what that button
does on the current screen — left-aligned, center-aligned, right-aligned so each
label sits over its physical button. No need to print the button name; position
implies it. An empty zone means that button does nothing on that screen.

### Layout math (size 1 font, 6 px/char on a 128 px panel)

- Left zone: left-aligned at `x = 2`.
- Center zone: centered around `x = 64` (use `getTextBounds` to center).
- Right zone: right-aligned to `x = 126` (compute `x = 126 - width`).
- Budget ~7 chars per zone worst case (7 x 6 = 42 px x 3 = 126 px). Keep verbs
  <= 7 chars (`select`, `confirm`, `cancel`, `back`, `deny`, `allow`, `next`,
  `prev`) to guarantee no overlap.
- Optional: a thin divider line above the legend at `y ~= 112`, and 1 px vertical
  ticks between zones to reinforce the 3-button mapping.

```
+--------------------------------+
| ...screen body...              |
|                                |
| -------------------------------|  <- optional divider (y~112)
| prev      confirm        next  |  <- 3 zones (left/center/right)
+--------------------------------+
   ^PREV       ^PLAY        ^NEXT     (physical buttons below glass)
```

### Per-screen legend mapping (derived from `signer_mode_tick()`)

Encoder still scrolls/selects on every screen; the legend only describes the 3
buttons. `-` = button does nothing on that screen.

| Screen / state                         | PREV (left) | PLAY (center) | NEXT (right) |
| -------------------------------------- | ----------- | ------------- | ------------ |
| Volume / Media view                    | `prev`      | `play`        | `next`       |
| Signer Menu (`SIGNER_UI_MENU`)         | `-`         | `select`      | `-`          |
| Generated Phrase (`GENERATE_REVIEW`)   | `cancel`    | `confirm`     | `new`        |
| Signer Ready (`READY`)                 | `-`         | `-`           | `menu`       |
| Letter Pick (`ENTER_LETTER`)           | `-`         | `confirm`     | `back`       |
| Word Pick (`ENTER_PICK`)               | `-`         | `select`      | `back`       |
| Approval prompt (Message modal)        | `deny`      | `allow`       | `-`          |

Notes:
- `PREV` is currently unused in most signer entry screens; it is meaningful in
  Media view (`prev` track), the Approval prompt (`deny`), and now the Generated
  Phrase screen (`cancel`).

### Behavior change required: Generated Phrase (`SIGNER_UI_GENERATE_REVIEW`)

The legend wording above implies a **functional change** in
[`signer_mode_tick()`](./kb2040_hidden_signer.ino:657), not just labels:

| Button | Current behavior            | Proposed behavior                          |
| ------ | --------------------------- | ------------------------------------------ |
| PREV   | (unused)                    | `cancel` -> return to Signer Menu          |
| PLAY   | confirm -> save seed        | `confirm` -> save seed (unchanged)         |
| NEXT   | cancel -> Signer Menu       | `new` -> regenerate a fresh 12-word phrase and refresh this screen |

Implementation detail for `new`:
- On `s_evt_next_pressed`, call `generate_mnemonic_12(s_generated_mnemonic, ...)`
  again, then `signer_enter_review_screen()` to redraw with the new words.
- On `s_evt_prev_pressed`, call `signer_enter_menu_screen()` (the old NEXT path).

> Dependency check (resolved): `s_evt_prev_pressed` already exists and is wired
> the same way as PLAY/NEXT — declared at [line 112](./kb2040_hidden_signer.ino:112),
> reset in [`update_button_events()`](./kb2040_hidden_signer.ino:560), set on PREV
> press at [line 581](./kb2040_hidden_signer.ino:581). No new plumbing needed.

### What this replaces on each screen

- Word Pick body lines `ENC scroll  PLAY select` / `NEXT back` -> removed; the
  encoder action stays implicit, and PLAY/NEXT move to the footer legend.
- Mnemonic Page body line `PLAY confirm  NEXT cancel` -> moves to the footer
  legend (frees a body row).
- Signer Ready body line `NEXT/PLAY: menu` -> becomes a single `menu` in the
  NEXT zone.
- Approval modal `PLAY=Allow` / `PREV=Deny` body lines -> footer legend
  (`deny` left / `allow` center).

### Implementation sketch

1. Add a helper `drawButtonLegend(const char *left, const char *center, const char *right)`
   in [`display.cpp`](./display.cpp) that renders the 3 aligned zones (plus the
   optional divider/ticks) at the bottom.
2. Give each screen a small static legend (or pass it in) and call
   `drawButtonLegend(...)` instead of `drawStatusLine()`.
3. Remove the now-redundant in-body hint rows listed above.
4. Keep `drawStatusLine()` available (commented/disabled) for transient debug.

### Open design decisions (need your call)

1. **NEXT "back" wording**: use the word `back` vs a glyph like `<` / `<<`.
2. **Divider/ticks**: include the thin divider line + inter-zone ticks, or keep
   it text-only for a cleaner look.
3. **Empty zones**: render nothing, or a subtle `·` placeholder so the user can
   tell the button is intentionally inactive.
4. **Media view**: apply the same legend (`prev` / `play` / `next`), or leave the
   Volume view visually as-is since its meaning is already obvious.
