stacklok / stacklok/mecatl

refactor(mecatui): surface abstraction for modals/overlays (keys + wheel + render + lifecycle in one place)

Open
#555 2 comments 0 reactions 1 assignee View on GitHub

@jbeda is already working on this.

Since Aug 15, 2026.

enhancement tui
Dominant language
Go
Stars
152
Forks
16
Avg merge
14h 48m
Merged PRs (30d)
536

Description

Problem

Adding a new modal/overlay to mecatui today touches seven places, all held in lockstep by convention:

  1. a phase or Model flag (e.g. argsViewOpen),
  2. a renderBody arm,
  3. an onKey/dispatchPhaseKey branch (e.g. onApprovalKey's cascade),
  4. an onMouseWheel branch,
  5. an askButtonAt/hit-test arm in clickgeom.go,
  6. lifecycle clears (advanceAsk, endRun, resetSession, retract),
  7. the help overlay's key listing.

PR #553 (issue #488) drifted on two of these within one feature (the render path didn't thread the scroll offset; the idle-resume phase was wrong), which shows the convention is a weak gate. This came out of the code review discussion on #553.

Proposal

A surface interface so each modal/overlay owns its behavior in one file:

type surface interface {
    Render() string                                    // body content
    Regions() []ClickableRegion                        // emitted by the layout pass, not re-derived
    HandleKey(tea.KeyPressMsg) (Action, bool)          // keys the surface consumes
    HandleWheel(tea.MouseWheelMsg) (Action, bool)
    Close()                                            // lifecycle teardown
}

The Model holds a stack of active surfaces and routes render/keys/wheel/clicks to the top one — replacing the per-surface branches in renderBody, onKey, onMouseWheel, and the hit-test arms. Clickable regions carry a semantic Action payload (a sum type, not a func — value-safe across Model copies, testable), so the hit-test returns the action and ONE dispatcher executes it; no second mapping table.

Migration plan

  • The click half (region-emitting builders + ClickableRegion) lands in PR #553 as the vocabulary/pattern proof.
  • Migrate the approval surface first (it has the most arms: plan / args-view / generic modal), then the other overlays (MCP, team/agents, skills, soul, sessions, models, worktrees, schedule) one at a time.
  • The seven-touchpoint list above becomes the acceptance checklist: a new surface should touch ONE file + ONE registration point.

Non-goals

  • No widget hierarchy / retained-mode GUI framework — this stays Elm-architecture; surfaces are rebuilt from state every render (regions are emitted per layout pass, so they are always as fresh as the frame).
  • No change to the wire/proto surface or the keymap registry semantics.

See also: PR #553 discussion (click geometry origin), ADR 0108.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.