Skip to content

Pull-to-refresh

interaction specs/interaction/pull-to-refresh.kmd

Material 3 Expressive pull-to-refresh pattern: drag from top ≥ threshold triggers refresh; spring-snap release; loading indicator morphs during drag and persists until completion. Mandatory binding to `loading-indicator.kmd` for visual consistency.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Pull-to-refresh

Mandatory binding: loading-indicator.kmd R4. Driver: motion.kmd R9.1 spring physics.

Princípios

  1. Threshold-gated trigger — drag distance ≥ threshold (40dp default).
  2. Loading indicator hosted — uses loading-indicator.kmd (NÃO custom spinner).
  3. Spring-snap release — release < threshold → snap-back; release ≥ → refresh triggered.
  4. Keyboard alternativeCmd/Ctrl+R (desktop) refresh trigger.
  5. Multi-tenant safe — refresh scope respeita workspace context.

R1 — Trigger gesture

Pre-conditions:

  • Scrollable container.
  • Scroll position at top (offset = 0).

Drag down:

  • Distance < threshold (40dp): static loading indicator at smaller scale 0.6, partial opacity.
  • Distance ≥ threshold: indicator full size + shape morph begins (per loading-indicator.kmd R2).
  • Distance > 2× threshold: indicator stays at threshold position (rubber-band resistance).

R2 — Binding com Loading Indicator

Per loading-indicator.kmd R4:

Drag stageIndicator state
0 → 40dpStatic Cookie-4 shape, scale 0.6 → 1.0, opacity 0.3 → 1.0
40dp+Morph cycle begins (Cookie-4 → Burst → Flower)
Release ≥ 40dpIndicator stays full-size, cycle continues until refresh completes
Refresh doneCycle completes current frame, indicator fades out

R3 — Release behavior

User actionBehavior
Release at < thresholdSnap back to 0 via spring motion-spatial-default; indicator fades out
Release at ≥ thresholdSnap back to indicator-position (40dp from top); refresh callback invoked; indicator persists
Refresh callback completesSnap back to 0 via spring; indicator fades out
Refresh callback errorsIndicator turns red briefly; toast/snackbar with retry per errors/user-facing-messages.kmd

R4 — Keyboard alternative

Desktop:

  • Cmd+R (macOS) / Ctrl+R (Win/Linux) triggers refresh equivalent.
  • Indicator appears briefly at top (300ms minimum visible).

CLI/TUI:

  • r key OR refresh command.

R5 — Multi-tenant + scope

Refresh action scope:

  • Conversation history: refresh current workspace + user scope only.
  • Memory drawer: idem.
  • Feed surfaces: workspace-scoped per multi-tenant-by-default.kmd.

Cross-tenant refresh NOT supported (would violate isolation).

R6 — Surface bindings

SurfaceAPI
FlutterKoderRefreshIndicator wrapper around RefreshIndicator (Flutter stock); skin custom
WebCustom gesture handler via pointerdown/move/up events; CSS overscroll-behavior: contain
Compose AndroidPullToRefreshContainer (compose.material3) with Koder skin
SwiftUI iOS.refreshable modifier (native); custom loading indicator overlay
CLI / TUIn/a (r key trigger inline)

R7 — Acessibilidade

  • Container: role="region" aria-label="Pull to refresh, refreshing...".
  • Indicator: aria-live="polite" announces "Refreshing" → "Done".
  • Keyboard: Cmd+R OR explicit refresh button.
  • Reduced-motion: drag-distance-driven morph disabled; threshold cross → instant indicator + spring-snap replaced by direct transition.

R8 — i18n

Keyen-USpt-BR
refresh.pulling"Pull to refresh""Puxe para atualizar"
refresh.release"Release to refresh""Solte para atualizar"
refresh.refreshing"Refreshing...""Atualizando..."
refresh.done"Done""Concluído"
refresh.error"Refresh failed""Falha ao atualizar"

(Inline hints abaixo do indicator são opcionais; geralmente o indicator visual basta.)

R9 — Reduced-motion

  • Drag-distance-driven morph: disabled.
  • Spring snap-back: replaced by direct opacity fade (200ms).
  • Threshold-cross: instant.

R10 — Per-preset variation

PresetPull-to-refresh style
material3 / material_expressiveDefault (loading-indicator morph + spring)
material2Circular spinner (legacy); no shape morph
terminal_classicn/a (terminal não tem pull gesture)
brutalistSquare indicator + flat snap (no spring)
cyberpunk_neonDefault + glow during refresh
minimalist_monoThin line indicator at top; no curves

T-suite

  • T1 Drag down at top → indicator appears; scale 0.6→1.0 over 40dp drag.
  • T2 Drag ≥ threshold → indicator full size + morph begins.
  • T3 Release < threshold → snap-back; indicator fades; NO refresh callback.
  • T4 Release ≥ threshold → spring-snap to threshold position; refresh callback invoked.
  • T5 Refresh completes → indicator fades; spring to 0.
  • T6 Refresh errors → indicator red briefly; toast.
  • T7 Keyboard alternative: Cmd+R triggers indicator + refresh.
  • T8 Reduced-motion: drag-driven morph disabled; threshold-cross instant.
  • T9 A11y: aria-live announces transitions.
  • T10 Multi-tenant: refresh workspace A → workspace B context unaffected.
  • N1 Container NOT scrollable: gesture inactive (no spurious refresh).

References