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
// ❌ Before — easy to forget the Focus() call
ctx.Stack().Push("search")
ctx.Router().Focus(1, "search") // ← must remember this every timeIf 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:
// ✅ 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:
ctx.Stack().Push("editor")
ctx.Router().Focus(1, "editor-panel") // override: zone name differs from layer IDOpt Out Entirely
To disable auto-routing globally (revert to the fully manual model):
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:
// 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.
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
| Scenario | What to do |
|---|---|
| Default (same zone name as layer ID) | Just call Stack.Push() — nothing else needed |
| Custom zone name | Call Stack.Push() then Router.Focus(level, customZone) |
| Fully manual focus management | Call Router.SetAutoFocus(false) once at startup |
| Explicit high-level intent | Use Router.PushLayer() / Router.PopLayer() |
Up next: 04.02 — OverlayManager — show and dismiss overlays with a single call, replacing the old Stack + Focus + Hooks coordination.
