Rebind char cycling to ,/. instead of [/]
All checks were successful
Docker / build-and-push (push) Successful in 50s

[ and ] needed a reach/shift on most layouts and felt awkward next to
the otherwise all-unshifted control scheme (hjkl, space, digits). ,
and . are adjacent, unshifted, easy to hit without looking away from
the canvas -- a natural prev/next pair. Old bindings kept as a fallback
rather than removed.

Verified both in isolation (Update() with a KeyMsg) and live over a
real SSH session -- the status-line ink swatch visibly cycles from █
to ▓ on a fresh connection. An initial live test after the rebind
appeared to fail, but that traced to a stale session left over from
the server restart, not an actual bug -- confirmed working correctly
on a fresh connection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-07-15 19:55:59 +02:00
parent 2b8c3ae6ee
commit 039e131304
4 changed files with 29 additions and 5 deletions

View File

@@ -18,7 +18,7 @@ fresh enough to reuse without re-learning it.
- Arrow keys or `hjkl` — move your cursor
- `space` / `enter` — paint
- `backspace` / `delete` — erase
- `[` / `]` — cycle the ink character
- `,` / `.` — cycle the ink character (`[` / `]` also work)
- `1`-`8` — pick a color
- `c` — clear the whole canvas (yes, for everyone — it's a shared wall)
- `q` / `ctrl+c` — quit

View File

@@ -80,9 +80,9 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.canvas.Paint(cur.X, cur.Y, ' ', "")
}
case "[":
case ",", "[":
m.charIdx = (m.charIdx - 1 + len(charPalette)) % len(charPalette)
case "]":
case ".", "]":
m.charIdx = (m.charIdx + 1) % len(charPalette)
case "1", "2", "3", "4", "5", "6", "7", "8":

View File

@@ -118,3 +118,27 @@ func newTestRenderer() *lipgloss.Renderer {
r.SetColorProfile(termenv.TrueColor)
return r
}
func TestCommaPeriodCharCycling(t *testing.T) {
c := NewCanvas()
m := NewModel(c, "test-id", "tester", newTestRenderer())
c.Join("test-id", "tester", nil)
mi, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("x")}) // exit intro
m = mi.(Model)
t.Logf("charIdx before: %d", m.charIdx)
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(".")})
m = mi.(Model)
t.Logf("charIdx after '.': %d", m.charIdx)
if m.charIdx != 1 {
t.Fatalf("expected charIdx=1 after '.', got %d", m.charIdx)
}
mi, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(",")})
m = mi.(Model)
t.Logf("charIdx after ',': %d", m.charIdx)
if m.charIdx != 0 {
t.Fatalf("expected charIdx=0 after ',', got %d", m.charIdx)
}
}

View File

@@ -42,7 +42,7 @@ func (m Model) renderIntro() string {
dim.Render("a shared doodle wall, one keystroke at a time") + "\n\n" +
"Everyone connected right now is drawing on the same canvas.\n" +
"Move with arrow keys or hjkl, space/enter to paint, backspace to\n" +
"erase. [ and ] cycle the ink character, 1-8 pick a color, c clears\n" +
"erase. , and . cycle the ink character, 1-8 pick a color, c clears\n" +
"everything for everyone (yes, really — it's a shared wall).\n\n" +
dim.Render("press any key to start")
return m.boxStyle().Render(body)
@@ -112,7 +112,7 @@ func (m Model) renderCanvas() string {
inkSwatch := m.style().Foreground(lipgloss.Color(inkPalette[m.inkColorIdx])).Render(string(charPalette[m.charIdx]))
dim := m.style().Foreground(lipgloss.Color("#777777"))
status := fmt.Sprintf("ink %s (1-8 color, [ ] char) peers: %d %s",
status := fmt.Sprintf("ink %s (1-8 color, , . char) peers: %d %s",
inkSwatch, m.canvas.PeerCount(), dim.Render("space paint · c clear · q quit"))
return m.boxStyle().Render(b.String() + "\n" + status)