Skip to content

Auto-Routing (Stack ↔ Focus Sync)

Previously, every Stack.Push() and Stack.Pop() required a matching Router.Focus() call to keep keyboard routing in sync. Starting with this release, focus syncs automatically.

The Problem It Solves

go
// ❌ Before — easy to forget the Focus() call
ctx.Stack().Push("search")
ctx.Router().Focus(1, "search")  // ← must remember this every time

If you forgot the Focus() call, key bindings for the new layer silently stopped working — the router was still routing to the old zone.

How It Works

When NewRouter is created, it wires two observer hooks on the Stack:

Stack.AfterPush → Router.Focus(newLevel, layerID)
Stack.AfterPop  → Router.Focus(newLevel, newTop)

These run after the stack mutation completes and outside the stack's lock, preventing any deadlock between the stack mutex and the router's focus mutex.

The convention is: zone name == layer ID. Pushing "search" automatically focuses zone "search" at the new stack level.

Usage — Nothing Changes

Auto-routing is transparent. You write the same Stack.Push() call — focus now follows for free:

go
// ✅ After — focus shifts automatically
ctx.Stack().Push("search")
// focus is now: level 1 → zone "search"

ctx.Stack().Pop()
// focus is now: level 0 → zone "base" (whatever the new top is)

Override Auto-Focus for a Custom Zone Name

If your zone name differs from your layer ID (e.g., you always use "main" as the zone), call Router.Focus() manually after the push — it overrides the auto-focus:

go
ctx.Stack().Push("editor")
ctx.Router().Focus(1, "editor-panel")  // override: zone name differs from layer ID

Opt Out Entirely

To disable auto-routing globally (revert to the fully manual model):

go
app.Router().SetAutoFocus(false)

// Now you must call Focus() manually again, as before:
app.Stack().Push("search")
app.Router().Focus(1, "search")

PushLayer / PopLayer — Explicit High-Level Variants

If you want to be explicit that you're using the high-level behavior, use the named methods on the router:

go
// These are equivalent to Stack.Push/Pop when autoFocus is true,
// but their name communicates intent clearly in code reviews.
app.Router().PushLayer("search")
app.Router().PopLayer()

Auto-Routing and Stack Guards

Auto-routing fires only after a successful push or pop. If a guard blocks the operation, the observer is never called — focus stays where it was.

go
app.Stack().BeforePush(func(layer string) (bool, string) {
    if layer == "admin" && !isAdmin {
        return false, "admin required"
    }
    return true, ""
})

app.Stack().Push("admin")
// If blocked: stack unchanged, focus unchanged.
// If allowed: stack updated, focus auto-shifts to "admin".

Summary

ScenarioWhat to do
Default (same zone name as layer ID)Just call Stack.Push() — nothing else needed
Custom zone nameCall Stack.Push() then Router.Focus(level, customZone)
Fully manual focus managementCall Router.SetAutoFocus(false) once at startup
Explicit high-level intentUse Router.PushLayer() / Router.PopLayer()

Up next: 04.02 — OverlayManager — show and dismiss overlays with a single call, replacing the old Stack + Focus + Hooks coordination.