Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

138. A window’s actions are a model of their own

Date: 2026-08-19

Status

Accepted. Removes a leak ADR-0132 introduced.

Context

ADR-0132 made the window’s own actions ordinary by annotating the application:

@Model
public final class Showcase implements Application {

    @Action("app.open-menu") private void toggleMenu() { … }
}

It solved the real problem — “open the menu” needs a Host and has no business on a view model — and it solved it by putting two unrelated roles on one class. An Application is the thing that owns the window, the lifecycle and the native resources. A @Model is a thing markup resolves names against. Stacking the annotation on the implements made that visible in the worst way: the first two lines of the application’s class declaration are now about two different abstractions.

It was also the only place in the guide where the four kinds of class did not hold — the application was quietly a fifth thing.

Decision

A small @Model record for the window’s actions, holding what they do rather than what does them.

@Model
public record WindowActions(Runnable openMenu, Runnable toggleHud) {

    @Action("app.open-menu")  public void open() { openMenu.run(); }
    @Action("app.toggle-hud") public void hud()  { toggleHud.run(); }
}

built in the application as new WindowActions(this::toggleMenu, this::toggleHud) and added to models(). Showcase carries no annotation and keeps its methods private.

Two Runnables rather than a reference to the window, so this type knows what the actions are called and nothing about who performs them. It is testable without a Host and reads as what it is: the window’s half of the name table.

Consequences

The application is an Application again, and the guide’s four kinds of class hold everywhere including the showcase.

It is one more object for two names, which is the cost. A window with a dozen actions would find this obviously worthwhile; a window with two finds it a wash, and the reason to do it anyway is that the alternative was a leak that got worse with every action added.

models() is now three entries — values, actions, window actions — which is a better demonstration of multi-model wiring than the two it replaced, since the third one genuinely comes from somewhere else.