Pull-to-refresh
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.kmdR4. Driver:motion.kmdR9.1 spring physics.
Princípios
- *hreshold-gated trigger*— drag distance ≥ threshold (40dp default).
- *oading indicator hosted*— uses
loading-indicator.kmd(NÃO custom spinner). - *pring
snap release*— release < threshold → snapback; release ≥ → refresh triggered. - *eyboard alternative*—
Cmd/Ctrl+R(desktop) refresh trigger. - *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.kmdR2). - 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 `errorsuser |
R4 — Keyboard alternative
Desktop:
Cmd+R(macOS) /Ctrl+R(Win/Linux) triggers refresh equivalent.- Indicator appears briefly at top (300ms minimum visible).
CLI/TUI:
rkey ORrefreshcommand.
R5 — Multi-tenant + scope
Refresh action scope:
- Conversation history: refresh current workspace + user scope only.
- Memory drawer: idem.
- Feed surfaces: workspace
scoped 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.
- Reduced
motion: 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
- Drag
distancedriven morph: disabled. - Spring snap-back: replaced by direct opacity fade (200ms).
- Threshold-cross: instant.
R10 — Per-preset variation
| Preset | Pull |
|---|---|
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*Reduced
motion: 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).
Cross-link
- Indicator:
components/loading-indicator.kmdR4 - Motion:
motion.kmdR9.1 - Errors:
errors/user-facing-messages.kmd - Policies:
multi-tenant-by-default.kmd - Refs: M3 Pull
torefresh pattern (companion to Loading indicator)