Back-button / ESC / Swipe-back Behaviour

mandatory

Back (Android <, iOS swipe, ESC desktop, browser back) **sempre** volta tela por tela na pilha do Navigator; só minimiza/fecha no root. Implementação canônica: KoderBackScope(enableSystemExitAtRoot: true) em engines/sdk/koder_kit v0.4.0+.

Spec — Backbutton / ESC / swipeback behaviour

Applicability

All Koder *pps*with any in-app navigation: mobile (Android back gesture, iOS edge swipe), desktop (Escape key, window close), web (browser back, Esc), TV (remote back). Not applicable to CLIs.

Required behaviour

  1. *ack always pops the in-app Navigator stack first.*Android "<" / gesture

    back, iOS edge-swipe, Desktop Escape, browser back — all honour the app's own history before any system-level action.

  2. *nly at the root screen*(Navigator stack empty) does back trigger the

    default system action: minimise to Home (Android), dismiss tab (browser), exit app (desktop window close), etc.

  3. *efault at the root is "absorb"* not "exit". The user staying in the

    app when they tap back at the home screen is not a bug — it is explicit. Apps that want the classic "press back at root → minimise" behaviour opt in via enableSystemExitAtRoot: true (see SDK widget below).

  4. *verlays pop first.*If a dialog, bottom sheet, drawer, or modal is

    open, back closes the overlay before popping the Navigator.

  5. *ptional confirm gate.*Screens with unsaved state may intercept with

    a confirm dialog via the onWillPop callback; returning false blocks the back entirely (user stays put), returning true proceeds with the default pop/exit chain.

  6. *ever minimise from a deep screen.*The Koder Pass QR-scanner bug — tap

    back, app minimises to Home instead of returning to the vault list — is *xplicitly forbidden*by this spec.

Platform primitives

Platform API / gesture Notes
Android native (API 33+) OnBackPressedDispatcher + OnBackPressedCallback(true) Predictive-back compatible; the callback runs before the activity is finished
Android native (compat) onBackPressed() override Deprecated API 33+; still works
iOS native UINavigationController.popViewController + interactivePopGestureRecognizer Default behaviour of UINavigationController already matches the spec
Flutter PopScope(canPop: false, onPopInvokedWithResult:) Covers Android back, iOS swipe, browser back on web
Flutter Navigator.maybeOf(context)?.maybePop() Pops if there's a route; returns false if at root
Desktop (all via Flutter) CallbackShortcuts + LogicalKeyboardKey.escape No system back — Escape is the convention
Web (Flutter web) Browser back covered by PopScope via Flutter's routing Escape via same CallbackShortcuts mechanism
Web (vanilla JS) history.pushState + popstate event + keydown ESC koder_web_kit (planned) will encapsulate
Flutter (system exit) SystemNavigator.pop() Call this at the root screen to minimise

SDK widget (approved implementation)

Platform SDK Widget
Flutter (Android / iOS / Linux / macOS / Windows / Web) engines/sdk/koder_kit v0.4.0+ KoderBackScope(child: …, onWillPop?, enableSystemExitAtRoot?)
Web (vanilla) engines/sdk/koder_web_kit (planned) <koder-back-scope> custom element with matching props
Native Android Kotlin koder-kit-android (deferred) @Composable fun KoderBackScope with matching API

Flutter usage

// Typical app shell — wrap the MaterialApp root once:
runApp(
  KoderApp(
    config: KoderConfig(slug: 'pass', version: '1.2.3'),
    home: const KoderBackScope(
      enableSystemExitAtRoot: true,   // minimise only at the true root
      child: HomeScreen(),
    ),
  ),
);

// Per-screen with unsaved-changes confirm:
KoderBackScope(
  onWillPop: (context) async => await showDiscardDialog(context),
  child: const EditorScreen(),
);

Native Android Kotlin pattern (until the Kotlin SDK ships)

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (navController.popBackStack()) return
                // At root — decide: finish() to minimise, or absorb.
                finish()
            }
        })
        setContent { … }
    }
}

Deterministic audit checks

/k-housekeep audit (ticket engines/sdk/koder_kit#003 extended):

  1. Any Flutter app with koder_kit in pubspec.yaml whose KoderApp.home

    is not wrapped in KoderBackScope triggers a warning.

  2. Any Flutter screen that calls SystemNavigator.pop directly (bypassing

    KoderBackScope) triggers a warning.

  3. Any landing / web app loading koder-web-kit.js without <koder-back-scope>

    on interactive screens (determined by presence of ≥2 SPA-style page transitions) triggers a warning.

Rationale

The /k-housekeep audit of February 2026 surfaced this pattern as one of the three most driftprone across the Stack (theme, safearea, back). dev/eye had it fixed only after the status bar clipping was visible in a screenshot; Koder Pass still has the bug in production (QR scanner minimises instead of popping). Every Koder app has needed it, most implemented it wrong or partially.

The SDK widget collapses the N platform-specific APIs into one symbol and one opt-in flag. Apps adopting KoderBackScope inherit the spec's compliance without ever rereading it — consistent with the SDKfirst policy at policies/sdk-first.kmd.

Canonical reference

engines/sdk/koder_kit/lib/src/back_scope.dart — Flutter reference. engines/sdk/koder_kit/test/back_scope_test.dart — 5 widget tests locking the contract.

  • ../themes/light-dark.kmd
  • ../app-layout/safe-area.kmd
  • ../errors/user-facing-messages.kmd
  • ../koder-app/behaviors.kmd

Source: ../home/koder/dev/koder/meta/docs/stack/specs/navigation/back-behavior.kmd