App Safe-Area / Window-Insets
Consumo obrigatório de window-insets (status bar, nav bar, display cutout, IME) em todos os apps Koder. Implementação canônica: KoderSafeScaffold / KoderApp(safeArea: true) em engines/sdk/koder_kit v0.3.0+, Modifier.safeDrawingPadding() no Android nativo Compose, env(safe-area- inset-*) no web.
Spec — App safearea / windowinsets handling
Applicability
All Koder *pps*(mobile, desktop, TV, web PWA) — any surface where system chrome (status bar, navigation bar, display cutout, on-screen keyboard, title bar, task bar) can occlude rendered content.
Not applicable to CLIs, landing pages (static HTML with normal document flow), or pure server components.
Required behaviour
- *lways consume OS-reported insets.*The OS knows the exact pixel
dimensions of system chrome on the current device and updates them live (orientation change, gesture nav vs 3-button, keyboard open/close, foldable unfold, PiP resize). Apps must consume these values — never hard-code pixel margins.
- *o content occluded by system chrome at rest.*Every interactive
element — buttons, text fields, list items, sliders — must be fully tappable without fighting the nav bar, status bar, or cutout.
- *ext under the status bar is forbidden*unless the design is
deliberately edge
toedge (splash screen, full-screen video, camera viewfinder) and the content is non-critical. - *ME (on-screen keyboard) handling.*Forms must resize or scroll so the
focused field stays visible above the keyboard. Default to
resizeToAvoidBottomInset: true(Flutter) /WindowInsets.ime(native Android) /keyboardShouldPersistTaps='handled'(Web). - *dge
toedge on Android 15+ is the platform default.*Apps targetingAPI ≥ 35 must handle insets explicitly; the OS no longer adds automatic padding.
Platform primitives
| Platform | API | Notes |
|---|---|---|
| Android native (API 30+) | WindowInsets.getInsets(WindowInsets.Type.systemBars()) |
Returns Insets(left, top, right, bottom) in px |
| Android native (compat) | ViewCompat.getRootWindowInsets(view) + WindowInsetsCompat |
API 21+ |
| Jetpack Compose | WindowInsets.systemBars, .navigationBars, .statusBars, .safeDrawing, .ime |
Stateful; recomposes on change |
| Jetpack Compose | Modifier.safeDrawingPadding(), .systemBarsPadding(), .imePadding() |
Apply padding automatically |
| Jetpack Compose | Scaffold(contentWindowInsets = WindowInsets.safeDrawing) |
Handles all edges + IME at the Scaffold level |
| Jetpack Compose (Activity) | enableEdgeToEdge() before setContent |
Required for targetSdk ≥ 35 |
| Flutter | MediaQuery.of(context).viewPadding |
EdgeInsets in logical pixels |
| Flutter | SafeArea(child: …) widget |
Consumes padding; minimum: sets a floor |
| Flutter | Scaffold(resizeToAvoidBottomInset: true) |
IME handling |
| iOS native | UIWindow.safeAreaInsets, safeAreaLayoutGuide |
Equivalent to Android systemBars |
| Web (CSS) | env(safe-area-inset-{top,right,bottom,left}) |
Required for iOS Safari PWA + Android WebView notch devices; fallback to 0 on older browsers |
| Web (CSS) | <meta name="viewport" content="viewport-fit=cover"> |
Prerequisite for env(safe-area-inset-*) to produce non-zero values on iOS |
SDK widgets (approved implementations)
Apps *ust*use the SDK-provided widget where available instead of re-implementing per edge. Using the SDK is the spec's deterministic compliance test.
| Platform | SDK | Widget / API |
|---|---|---|
| Flutter (Android / iOS / Linux / macOS / Windows) | engines/sdk/koder_kit v0.3.0+ |
KoderSafeScaffold(appBar:, body:, floatingActionButton:, …) — dropScaffold with SafeArea preminimumPadding: floor |
| Flutter | engines/sdk/koder_kit v0.3.0+ |
KoderApp(safeArea: true) — root-level SafeArea for apps that build their own Scaffolds; default true |
| Web | engines/sdk/koder_web_kit (planned) |
<koder-safe-layout> custom element + baseline CSS vars --pad-top, --pad-bottom consuming env(safe-area-inset-*) |
| Native Android (Kotlin/Compose) | koder-kit-android (deferred) |
KoderSafeScaffold { } composable; until the lib exists, apply the pattern below manually |
Native Android Kotlin — inline pattern
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KoderEyeTheme(...) {
Surface(modifier = Modifier.fillMaxSize(), ...) {
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding() // ← consume all insets
.verticalScroll(...)
.padding(16.dp)
) { ... }
}
}
}
}
}Edgetoedge opt-out (rare)
Apps with genuine edgetoedge content (video players, splash screens, maps) pass safeArea: false on KoderApp and handle individual edges per screen — e.g. keep the video surface under the status bar but consume bottomPadding for playback controls.
Deterministic audit checks
/k-housekeep audit (ticket engines/sdk/koder_kit#003) grep-verifies:
- Every Flutter app with
koder_kitinpubspec.yamlimportsKoderApp. - Every
Scaffold(body: …)at the app entry point uses eitherSafeAreaor
KoderSafeScaffold. A rawColumn/ListView/Stackatbody:position triggers a warning. - Every landing / web app that ships
koder-web-kit.jsalso referenceskoder-web-kit.css(or equivalent providing--pad-topvars). KoderApp(safeArea: false)presence triggers an info-level annotation:"edge
toedge opt-in — verify manually that insets are consumed per screen".
Rationale
This spec exists because the pattern is rediscovered per-app, forgotten in half of them, and leads to clipped UI that is only caught in screenshot review. Encapsulating it in the SDK reduces the cost from every app re-reads this spec to every app imports one symbol, and the audit makes compliance verifiable without human review. See engines/sdk/koder_kit/docs/rfcs/RFC-001-spec-encapsulation-across-platforms.md §2.1 for the AItokensavings argument.
Canonical reference
engines/sdk/koder_kit/lib/src/safe_scaffold.dart — Flutter reference. products/dev/eye/app/android/app/src/main/kotlin/dev/koder/eye/MainActivity.kt — native Kotlin reference pattern.
Related specs
../themes/light-dark.kmd— companion cross-cutting UI spec../koder-app/behaviors.kmd— parent behaviors spec