You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Keymap registry + navigation stack + overlay compositor (foundations) #59
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.
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 bothorigin 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.
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.
Activity
marcoarnulfo commentedon Jul 27, 2026
🇬🇧 English
Two of the three boxes landed in v1.9 tranche B1 (#132, merged).
Keymap registry.
internal/tui/keys.goholds a screen-independentkeyDefaultstable and a purekeysFor(m Model) keyMapwith one constructor per screen. All 97 stringcaseclauses are gone, and so is theq-quits-except-in-six-screens hack — quit is now a declared binding whose per-screen enablement is pinned byTestQuitBindingPerScreen.keyMapimplementsbubbles/help.KeyMapalready, so #69 has an interface to render against.Navigation stack.
internal/tui/nav.goholds anav []screenparent chain reached only throughgoTo/replace/pop/resetTo, with a truncating push. All 66m.screen = …assignments are routed through it, and bothoriginfields are deleted —logModel.originas well aslistBrowserModel.origin. Note for whoever picks this up:listBrowserModel.originwas not purely navigation state, sinceselectBrowsedListbranched 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(theFullHelp()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.gocontiene una tabellakeyDefaultsindipendente dalla schermata e una funzione purakeysFor(m Model) keyMapcon un costruttore per schermata. Tutte e 97 le clausolecasesu stringa sono sparite, e con loro l'hack delqche usciva tranne che in sei schermate: l'uscita è ora un binding dichiarato, la cui abilitazione per schermata è fissata daTestQuitBindingPerScreen.keyMapimplementa giàbubbles/help.KeyMap, quindi la #69 ha un'interfaccia su cui renderizzare.Stack di navigazione.
internal/tui/nav.gocontiene una catena di parentnav []screenraggiungibile solo tramitegoTo/replace/pop/resetTo, con push troncante. Tutte e 66 le assegnazionim.screen = …passano da lì, ed entrambi i campioriginsono eliminati:logModel.originoltre alistBrowserModel.origin. Nota per chi riprenderà il lavoro:listBrowserModel.originnon era solo stato di navigazione, perchéselectBrowsedListci 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 diFullHelp()) è popolato solo per Home e Report. Le altre dieci schermate renderizzerebbero un overlay?vuoto. Vanno riempite, oppure il campo va smesso di leggere.app.goafferma 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.marcoarnulfo commentedon Jul 30, 2026
🇬🇧 English
Last box closed in #142 (v1.9, tranche B3).
composite(body, box, x, y)ininternal/tui/overlay.gosplices 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.overlayis orthogonal tom.screen: raising an overlay does not touchm.nav, and closing one is not apop().Two notes for whoever writes the next overlay:
compositerequires a rectangular box — every line the same display width. A ragged box silently loses body cells, because the right-hand segment always resumes atx + boxW. The precondition is now in the doc comment; the palette satisfies it by construction andTestPaletteBoxIsExactlyItsWidthpins it.ansi.Cutis 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)ininternal/tui/overlay.goinnesta 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 am.screen: alzare un overlay non toccam.nav, e chiuderlo non è unpop().Due note per chi scriverà il prossimo overlay:
compositerichiede 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 dax + boxW. La precondizione è ora nel doc comment; la palette la soddisfa per costruzione eTestPaletteBoxIsExactlyItsWidthla fissa.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.Merge pull request #142 from marcoarnulfo/feat/command-palette