Skip to content

Keymap registry + navigation stack + overlay compositor (foundations) #59

Closed

Description

@marcoarnulfo

🇬🇧 English

Goal: Centralize keybindings and enable overlays and clean back-navigation.
Scope:

  • bubbles/key KeyMap per screen (replace the hardcoded "q"-quits-except-in-4-screens hack)
  • A real navigation stack replacing the ad-hoc listBrowserModel.origin
  • An overlay state orthogonal to m.screen + a line compositor (View() returns one screen today; overlays need layering)

Notes / API: Prerequisite for the command palette, the ? overlay and the task-detail screen.

Part of #33.

🇮🇹 Italiano

Obiettivo: Centralizzare le keybinding e abilitare gli overlay e una navigazione all'indietro pulita.
Scope:

  • KeyMap bubbles/key per schermata (sostituire l'hack hardcoded "q"-esce-tranne-in-4-schermate)
  • Uno stack di navigazione reale che sostituisce l'ad-hoc listBrowserModel.origin
  • Uno stato di overlay ortogonale a m.screen + un compositor di righe (View() oggi ritorna una sola schermata; gli overlay richiedono la stratificazione)

Note / API: Prerequisito per la command palette, l'overlay ? e la schermata task-detail.

Parte di #33.

Activity

  1. marcoarnulfo commented on Jul 27, 2026

    @marcoarnulfo
    OwnerAuthor

    🇬🇧 English

    Two of the three boxes landed in v1.9 tranche B1 (#132, merged).

    Keymap registry. internal/tui/keys.go holds a screen-independent keyDefaults table and a pure keysFor(m Model) keyMap with one constructor per screen. All 97 string case clauses are gone, and so is the q-quits-except-in-six-screens hack — quit is now a declared binding whose per-screen enablement is pinned by TestQuitBindingPerScreen. keyMap implements bubbles/help.KeyMap already, so #69 has an interface to render against.

    Navigation stack. internal/tui/nav.go holds a nav []screen parent chain reached only through goTo / replace / pop / resetTo, with a truncating push. All 66 m.screen = … assignments are routed through it, and both origin fields are deleted — logModel.origin as well as listBrowserModel.origin. Note for whoever picks this up: listBrowserModel.origin was not purely navigation state, since selectBrowsedList branched on it to choose which sub-model receives the picked list; that now reads the top of the parent chain.

    Still open: the overlay compositor. It is deliberately deferred so it arrives with its first consumer — the ? help overlay in #69 — rather than as dead code.

    Two things B1 leaves for that work:

    • keyMap.full (the FullHelp() source) is populated for Home and Report only. The other ten screens would render an empty ? overlay. Either fill them in or stop reading the field.
    • app.go's staleness-guard comment claims nothing can navigate away while a fetch is in flight. The periodic timer repoll is an exception worth noting when that code is next touched.

    🇮🇹 Italiano

    Due delle tre voci sono arrivate con la tranche B1 della v1.9 (#132, mergiata).

    Registry dei tasti. internal/tui/keys.go contiene una tabella keyDefaults indipendente dalla schermata e una funzione pura keysFor(m Model) keyMap con un costruttore per schermata. Tutte e 97 le clausole case su stringa sono sparite, e con loro l'hack del q che usciva tranne che in sei schermate: l'uscita è ora un binding dichiarato, la cui abilitazione per schermata è fissata da TestQuitBindingPerScreen. keyMap implementa già bubbles/help.KeyMap, quindi la #69 ha un'interfaccia su cui renderizzare.

    Stack di navigazione. internal/tui/nav.go contiene una catena di parent nav []screen raggiungibile solo tramite goTo / replace / pop / resetTo, con push troncante. Tutte e 66 le assegnazioni m.screen = … passano da lì, ed entrambi i campi origin sono eliminati: logModel.origin oltre a listBrowserModel.origin. Nota per chi riprenderà il lavoro: listBrowserModel.origin non era solo stato di navigazione, perché selectBrowsedList ci si basava per scegliere quale sotto-modello riceve la lista selezionata; ora quella scelta legge la cima della catena.

    Resta aperto: il compositor di overlay. È rimandato di proposito, così da arrivare insieme al suo primo consumatore — l'overlay di help ? della #69 — invece che come codice morto.

    Due cose che B1 lascia a quel lavoro:

    • keyMap.full (la sorgente di FullHelp()) è popolato solo per Home e Report. Le altre dieci schermate renderizzerebbero un overlay ? vuoto. Vanno riempite, oppure il campo va smesso di leggere.
    • Il commento sulla guardia anti-stale in app.go afferma che nulla può navigare via mentre un fetch è in volo. Il repoll periodico del timer è un'eccezione da annotare quando si tocca di nuovo quel codice.
  2. marcoarnulfo commented on Jul 30, 2026

    @marcoarnulfo
    OwnerAuthor

    🇬🇧 English

    Last box closed in #142 (v1.9, tranche B3). composite(body, box, x, y) in internal/tui/overlay.go splices a rendered box into the body's lines, cell by cell and ANSI-aware, so the screen underneath stays visible around it — layering, not replacement. m.overlay is orthogonal to m.screen: raising an overlay does not touch m.nav, and closing one is not a pop().

    Two notes for whoever writes the next overlay:

    • composite requires a rectangular box — every line the same display width. A ragged box silently loses body cells, because the right-hand segment always resumes at x + boxW. The precondition is now in the doc comment; the palette satisfies it by construction and TestPaletteBoxIsExactlyItsWidth pins it.
    • A wide glyph straddling either edge is dropped, not kept. ansi.Cut is asymmetric — it drops a straddling cluster on the left and keeps it whole on the right — so without a correction the composited line came out a cell too wide. An ASCII-only width test passes straight over that; the fixture is CJK for a reason.

    Of the three clients this issue predicted, the command palette is built (#71) and the task-detail screen is still ahead. The ? overlay is not coming: a help panel that covers the very screen you are asking about is worse than a footer that covers nothing, so #69's expanded footer stands. That decision is recorded as final in the tranche's spec rather than deferred, so it does not need an issue.

    🇮🇹 Italiano

    Ultima casella chiusa in #142 (v1.9, tranche B3). composite(body, box, x, y) in internal/tui/overlay.go innesta un box reso dentro le righe del corpo, cella per cella e ANSI-aware, così la schermata sottostante resta visibile tutto intorno — stratificazione, non sostituzione. m.overlay è ortogonale a m.screen: alzare un overlay non tocca m.nav, e chiuderlo non è un pop().

    Due note per chi scriverà il prossimo overlay:

    • composite richiede un box rettangolare — ogni riga della stessa larghezza di visualizzazione. Un box sfrangiato perde celle del corpo in silenzio, perché il segmento destro riprende sempre da x + boxW. La precondizione è ora nel doc comment; la palette la soddisfa per costruzione e TestPaletteBoxIsExactlyItsWidth la fissa.
    • Un glifo wide a cavallo di un bordo viene scartato, non tenuto. ansi.Cut è asimmetrico — scarta il cluster a cavallo a sinistra e lo tiene intero a destra — quindi senza una correzione la riga composta usciva larga una colonna in più. Un test di larghezza solo-ASCII ci passa sopra senza vederlo; la fixture è CJK per un motivo.

    Dei tre clienti che questa issue prevedeva, la command palette è fatta (#71) e la schermata task-detail è ancora davanti. L'overlay ? non arriverà: un pannello d'aiuto che copre proprio la schermata su cui stai chiedendo aiuto è peggio di un footer che non copre niente, quindi il footer espanso di #69 resta. Quella decisione è messa agli atti come definitiva nella spec della tranche, non rimandata, quindi non ha bisogno di una issue.

  3. added a commit that references this issue on Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:tuiTUI/UX, styling, design system

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions