Pull-to-refresh

mandatory

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.

Spec — Pulltorefresh

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

Princípios

  1. *hreshold-gated trigger*— drag distance ≥ threshold (40dp default).
  2. *oading indicator hosted*— uses loading-indicator.kmd (NÃO custom spinner).
  3. *pringsnap release*— release < threshold → snapback; release ≥ → refresh triggered.
  4. *eyboard alternative*— Cmd/Ctrl+R (desktop) refresh trigger.
  5. *ulti-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 stage Indicator state
0 → 40dp Static Cookie-4 shape, scale 0.6 → 1.0, opacity 0.3 → 1.0
40dp+ Morph cycle begins (Cookie-4 → Burst → Flower)
Release ≥ 40dp Indicator stays full-size, cycle continues until refresh completes
Refresh done Cycle completes current frame, indicator fades out

R3 — Release behavior

User action Behavior
Release at < threshold Snap back to 0 via spring motion-spatial-default; indicator fades out
Release at ≥ threshold Snap back to indicator-position (40dp from top); refresh callback invoked; indicator persists
Refresh callback completes Snap back to 0 via spring; indicator fades out
Refresh callback errors Indicator turns red briefly; toastsnackbar with retry per `errorsuserfacingmessages.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: workspacescoped per `multitenantbydefault.kmd`.

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

R6 — Surface bindings

Surface API
Flutter KoderRefreshIndicator wrapper around RefreshIndicator (Flutter stock); skin custom
Web Custom gesture handler via pointerdown/move/up events; CSS overscroll-behavior: contain
Compose Android PullToRefreshContainer (compose.material3) with Koder skin
SwiftUI iOS .refreshable modifier (native); custom loading indicator overlay
CLI / TUI n/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.
  • Reducedmotion: dragdistancedriven morph disabled; threshold cross → instant indicator + springsnap replaced by direct transition.

R8 — i18n

Key en-US pt-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

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

R10 — Per-preset variation

Preset Pulltorefresh style
material3 / material_expressive Default (loading-indicator morph + spring)
material2 Circular spinner (legacy); no shape morph
terminal_classic n/a (terminal não tem pull gesture)
brutalist Square indicator + flat snap (no spring)
cyberpunk_neon Default + glow during refresh
minimalist_mono Thin line indicator at top; no curves

T-suite

  • *1*Drag down at top → indicator appears; scale 0.6→1.0 over 40dp drag.
  • *2*Drag ≥ threshold → indicator full size + morph begins.
  • *3*Release < threshold → snap-back; indicator fades; NO refresh callback.
  • *4*Release ≥ threshold → spring-snap to threshold position; refresh callback invoked.
  • *5*Refresh completes → indicator fades; spring to 0.
  • *6*Refresh errors → indicator red briefly; toast.
  • *7*Keyboard alternative: Cmd+R triggers indicator + refresh.
  • *8*Reducedmotion: dragdriven morph disabled; threshold-cross instant.
  • *9*A11y: aria-live announces transitions.
  • *10*Multi-tenant: refresh workspace A → workspace B context unaffected.
  • *1*Container NOT scrollable: gesture inactive (no spurious refresh).

Source: ../home/koder/dev/koder/meta/docs/stack/specs/interaction/pull-to-refresh.kmd