Skip to content

[AI-stack FB3] fresh-ui: paced-reveal streaming-UX hooks #256

Description

@rickylabs

Add two dependency-free preact hooks (useMinVisible, usePacedReveal) that give streaming chat UIs a paced, jitter-free reveal without pulling in an npm animation/streaming-text library.

Context

Part of epic #238 · cluster fresh-ui · wave defer · depends-on FB0.

This sits in the fresh-ui plane behind the ai registry collection: the hooks are consumed by the chat primitives (StreamingText, message list, typing indicator) that FB0 registers, so they cannot land as usable registry items until FB0 exists, but the hook logic itself has no dependency on component markup and can be implemented and unit-tested independently.

Problem / motivation

Today there is no shared primitive for pacing UI updates against a fast/bursty stream (e.g. TanStack Query/token-stream updates arriving many times per animation frame) or for holding a loading/skeleton state visible for a minimum duration to avoid flicker. Hand-rolled chat surfaces (the eis-chat pattern this stack is replacing) reimplement ad-hoc setTimeout/requestAnimationFrame chains per component, which is exactly the per-app duplication the plugin-thinness/core-centralization doctrine forbids for by-design-repeating primitives. Without a shared hook, every consumer of the future streaming chat primitives (StreamingText, ChatMessageList, TypingIndicator) will re-derive this logic, drifting in timing behavior and accessibility (reduced-motion) handling.

Scope

Ships

  • packages/fresh-ui/src/runtime/paced-reveal/useMinVisible.ts — hook that keeps a boolean/state visible for at least a minimum duration once shown, to prevent skeleton/loading flicker on fast responses.
  • packages/fresh-ui/src/runtime/paced-reveal/usePacedReveal.ts — hook that takes an incoming string/token buffer and reveals it at a controlled pace (chars-per-tick or frame-paced), independent of how fast the upstream producer pushes updates; respects prefers-reduced-motion by reducing to immediate reveal.
  • packages/fresh-ui/src/runtime/paced-reveal/index.ts (or barrel re-export) grouping both hooks for the runtime layer.
  • Unit tests under packages/fresh-ui/tests/ covering timing/pacing behavior and the min-visible floor.

Out of scope

  • Any chat/streaming-text component that consumes these hooks — owned by the sibling fresh-ui chat-primitive slices (registered by FB0).
  • Registry manifest entries / ui:add wiring for the hooks themselves — hooks are runtime-only, not registry items; manifest work for the chat components is FB0.
  • Server-side stream framing, SSE/token transport, or the @netscript/ai/@netscript/fresh/ai client seam — owned by the ENGINE/fresh-ai clusters.
  • Any design-token or visual/animation-curve decisions — owned by the design-system vocabulary work under fresh-ui.

Public surface

  • New exports: useMinVisible(visible: boolean, minMs: number): boolean and usePacedReveal(input: string, options?: { charsPerTick?: number; tickMs?: number; reducedMotion?: boolean }): string (or equivalent typed signatures finalized during implementation).
  • New path: packages/fresh-ui/src/runtime/paced-reveal/ (files above), re-exported from the package's existing runtime barrel so downstream chat components can import { useMinVisible, usePacedReveal } from "@netscript/fresh-ui" (or the runtime subpath already used by sibling primitives such as accordion/tabs).
  • No new registry manifest items and no new deno.json exports entry beyond what the existing package barrel already covers.

Acceptance & fitness gates

  • F-3: hooks live in the runtime layer only, no reach into presentation/registry layers (deno task arch:check clean for packages/fresh-ui).
  • F-5: both hooks carry JSDoc with @module/doc comments on the public surface; package export count stays within the mod.ts <=20-export budget.
  • F-13: usePacedReveal's internal pacing loop is a real, cleanly-stoppable runtime construct (interval/rAF cleared on unmount/AbortSignal), not a fire-and-forget timer.
  • Unit tests demonstrate: (a) useMinVisible holds true for at least minMs after a fast true→false flip, (b) usePacedReveal never emits faster than the configured pace even when input arrives in one burst, (c) reduced-motion mode reveals immediately.
  • deno check --unstable-kv passes for packages/fresh-ui.
  • No new runtime dependency added to packages/fresh-ui/deno.json (no npm animation/streaming-text library).

Dependencies

Depends on FB0 (GitHub numbers tracked in the #238 DAG checklist) — FB0 registers the ai collection and chat primitive component shells these hooks are meant to back; FB3 itself has no runtime dependency on FB0's manifest entries and can be implemented in parallel, but is sequenced after FB0 in the wave plan since it is deferred and has no standalone consumer until the components exist. Not blocked by #239.

Size

S

Part of #238

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions