Skip to content

feat(tui): restyle the ADR split and import overlays (#144) - #150

Merged
aksOps merged 1 commit into
mainfrom
restyle/adr-import
Aug 19, 2026
Merged

feat(tui): restyle the ADR split and import overlays (#144)#150
aksOps merged 1 commit into
mainfrom
restyle/adr-import

Conversation

@aksOps

@aksOps aksOps commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Slice 5 of the map #136 cut (spec section 9.3): the ADR split review flow and
the forge import flow move onto internal/tui/theme and internal/tui/widget.
The binding spec is docs/design/tui-design-spec.md on design/tui-spec.

This slice also takes the two overlay-adjacent leftovers the merged slices
recorded and handed forward: the section 4 step 1 backdrop dim (PR #147
deviation 1, PR #149 deviation 6) and pointer.go's raw reverse-video escape
(PR #149 deviation 8).

Closes #144.

What the spec dictated

Section 4 - elevation. Both NewStyle().Border(RoundedBorder()) frames
(spec section 9.1 names them) are gone. Each overlay is a widget.Overlay
panel: a solid Brand header band carrying the title, an OverlaySurf body,
OverlayBand section breaks (SOURCE, STORIES, ISSUES), an OverlayBand
footer band carrying the hints with the ADR split's scroll indicator
right-aligned, and the two Shadow bands cast one cell down and right through
widget.OverlayLayers. Geometry and the per-overlay width caps come from
theme.Metrics.Overlay (ADRSplit 100, IssueImport 88); the two dead
literals are gone.

Section 4 step 1 - the dim, finally wired. Model.backdrop() returns the
model the board renders through: with any overlay open it is the same model
carrying styles.Dimmed, so renderBoard re-renders the whole board through
the second built palette. It is not a post-pass over a rendered string, which
is what the spec forbids, and it is one call at the compose site because #148
made the board renderer take styles.

Section 9.1 - the pressed token. pointer.State.Render takes a
*theme.Styles and delegates to the new Styles.PressedRun. The theme owns
the attribute and the re-arming a composed run needs; the pointer package only
decides which control wears it. The run still closes by clearing the attribute
alone rather than resetting the style, because callers substitute it into the
middle of a styled line (help.go, settings_view.go).

Sections 6.2 and 9.2 - the seam. adrsplit/view.go and
issueimport/view.go leave the seam allowlist; the list is down to one
entry (help.go) and its size cap with it. Both overlays take a
*theme.Styles through SetStyles, threaded from internal/tui/model.go on
construction and on tea.BackgroundColorMsg, and neither constructs a style.

Section 5.1 - kb widgets. Actions are widget.Button, the include and
proposal marks are widget.Check, the ADR split's scroll position is
widget.ScrollHint, and every body row goes through widget.OverlayRow or
widget.Section.

Section 5.2 - charm, where the spec assigns it. huh's inline Select
renders the Source, Max stories, Priority, Effort and Destination choices;
huh's Note renders the two AI disclaimer blocks in adrsplit. The bubbles
spinner replaces the static ... on every busy state in both overlays
(reading the file, splitting, creating card i of N, fetching and drafting), and
the bubbles progress bar renders issueimport's writing i/N. The huh
adapters moved from internal/tui to internal/tui/formview because three
packages now render them; formview.HuhInlineSelect is the new one.

Section 6.4 - goldens. TestADRSplitInputGolden is regenerated and now
pins theme.StructureProfile explicitly. Three goldens are new:
TestADRSplitReviewColorGolden, and the pair spec section 6.4 asks for on the
issue-import overlay, which had none - TestIssueImportGolden (structure) and
TestIssueImportColorGolden (truecolor over a board background, with the write
progress bar in frame).

Interaction is frozen

Every keymap, flow, session-scoping rule and mouse target of v1.0.1 survives.
Three mechanical changes were needed to keep them surviving a themed render:

  • Pointer targets are structural. Both overlays used to recover a control
    by line arithmetic over the body (adrsplit.controlRows) or by matching the
    rendered line text (issueimport). Rows now carry their target, and the
    buttons that share a row carry their column offset. Same control ids, same
    rects, same messages - and an ADR or a forge draft title can no longer
    impersonate a control, which is what the two impersonation tests assert.
  • A created story keeps its rows and loses its targets. The old line
    arithmetic skipped created stories entirely; the row model blanks their
    targets instead, so neither the tab cycle nor a click can reopen a story that
    is already written.
  • Pressed feedback is the token, not a text substitution. [>Discard<] and
    [>Cancel<] are gone; the labels stay [ Discard ] and [ Cancel ] and
    wear reverse video. Two assertions moved from the substituted text to the
    attribute in the composed frame.

Deviations, and why

  1. issueimport's max stays a plain field. Spec section 5.2 assigns
    Select to "Max stories"; that is the ADR split's field and it is a choice.
    The import's max accepts digit keys 0-9 as direct entry, which an inline
    Select cannot express, so it keeps its value rendering.
  2. huh's Select is used inline, and it still does not receive. The frozen
    layout gives each choice one row and one pointer target; a stacked Select is
    a title plus one row per option. The inline form is the only one that fits.
    As in feat(tui): restyle the editor, settings, and dialogs (#143) #149 the field is built from kb's state, rendered and dropped - huh
    owns focus and its own keymap, and exposes no hit regions.
  3. widget.Check replaces [x] include and [ ]. Section 5.1 assigns the
    checklist row to the widget, and these two rows are checklist rows. A
    created or already-written item takes the third mark (CheckDropped), which
    is what "out of the batch" looks like. Three test needles moved with it.
  4. The status line moved into the footer band. It was a body row in both
    overlays; the panel has a footer band for exactly that, and it costs each
    overlay one row less. While a status shows it replaces the hint ladder,
    which is the pattern feat(tui): restyle the card detail overlay (#142) #147 and feat(tui): restyle the editor, settings, and dialogs (#143) #149 already ship.
  5. The import panel is as tall as its content. Section 4's fixed geometry
    is the card detail pane's. This overlay windows its own proposals
    (reviewWindow, frozen at min(h-10, 12) rows) and has never scrolled a
    panel, so pinning the panel to the frame would have double-limited it. The
    ADR split, which does scroll, takes the fixed min(max(h-2,7), h) geometry
    and gains one body row from losing the border.
  6. issueimport's ref field goes through formview.Input. It rendered
    textinput.Model.View() straight into a row whose own truncation maps
    control runes to spaces, so its escapes showed up as visible [37m text.
    Incidental, pre-existing, and fixed here because this is the row's restyle.
  7. The spinner tick is batched at the operation start. Two test helpers
    walk the batch and skip the tick. The alternative - ticking for as long as
    the overlay is open - costs a timer on an idle overlay.
  8. cardeditor's drafting/saving states keep their static text. Spec
    section 5.2 names them for the spinner too, but they are Restyle the editor, settings, and dialogs #143's surface and
    it has shipped. Left for the map to place.

Dependency

charm.land/bubbles/v2/progress pulls github.com/charmbracelet/harmonica v0.2.0 (MIT, charmbracelet) as an indirect requirement for its spring
animation. The bar itself is rendered with ViewAs, so nothing animates.

Verification

Check Result
go build ./... pass
go vet ./... pass
sh scripts/check-go-coverage.sh pass, total 96.9% (gate 96.4%)
internal/tui coverage 96.5% (floor 90%)
internal/tui/adrsplit coverage 97.7%
internal/tui/issueimport coverage 99.4%
internal/tui/formview coverage 98.2%
internal/tui/theme, widget, pointer coverage 100.0%
go test -race ./internal/tui/... pass
sh scripts/check-go-format.sh clean

Slice 5 of the map #136 cut: the ADR split review flow and the forge
import flow move onto internal/tui/theme and internal/tui/widget, and the
two overlay-adjacent leftovers the earlier slices recorded are taken with
them - the section 4 step 1 backdrop dim and the pointer package's raw
reverse-video escape.

Both overlays are widget.Overlay panels: the rounded borders are deleted,
rows carry a semantic kind that picks the token, pointer targets are
structural, and the busy states adopt the bubbles spinner and progress
bar. adrsplit/view.go and issueimport/view.go leave the seam allowlist,
which is down to one entry.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@aksOps
aksOps merged commit 53b624c into main Aug 19, 2026
8 checks passed
@aksOps
aksOps deleted the restyle/adr-import branch August 19, 2026 12:40
aksOps added a commit that referenced this pull request Aug 20, 2026
* feat(tui): spin the card editor's drafting and saving states

Spec section 5.2 assigns the bubbles spinner to every busy state that was
static text; the card editor's drafting and saving footers were the last
two left behind (PR #150 deviation 8).

The pattern is PR #150's: the editor holds a spinner.Model, SetStyles
adopts the theme's spinner, the tick is batched at the operation start so
an idle editor costs no timers, and the tick loop stops as soon as
neither drafting nor saving is in flight.

Refs #145.

* docs: add v1.1.0 release notes

The redesign release: the Strata design language, the full-frame board
and proportional overlays, visible semantic-colored buttons, the adopted
charm components, and the text-field key fixes. No database migration.

Refs #145.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restyle the ADR split and import overlays

1 participant