Skip to content
ilinxa/pro-ui

Team Feedback Loop

alphav0.2.1

Non-blocking celebration layer — a brief skippable overlay when team progress advances, plus a gentle dismissible next-task nudge.

Category: GamificationUpdated: 2026-08-11Created: 2026-07-01Author: ilinxa

Context

The gamification-system's feedback layer (E6, Competence) — closes the engagement + progression loops. The host pushes a FeedbackEvent (controlled event prop OR imperative celebrate() — both funnel into one reducer, newest wins, never stack) and the component renders a brief celebration band and a standing next-task nudge. The cardinal constraint is D-10 NON-BLOCKING: the board stays fully interactive during a celebration (pointer-events:none except the skip button), focus is never moved or trapped, and celebrationDurationMs is clamped to <1000ms so a lingering modal is impossible; skip via ✕ or Esc. Reduced-motion is a real static branch (no movement, no confetti, still time-boxed + skippable). The default flourish is token CSS reveal-up (zero library); an opt-in canvas-confetti burst is React.lazy for milestone/badge only, so the default consumer never loads it. Ships as a shadcn-style compound — headless TeamFeedbackLoopRoot (the reducer + timer + reduced-motion + imperative handle) + flat parts (TeamFeedbackCelebration, TeamFeedbackNudge) + Tier-C primitives (CelebrationOverlay, NextTaskNudge, lazy ConfettiBurst). D-16: neither this nor team-trophy-shelf triggers the other; the host routes each event kind to exactly one celebrator (set the shelf's animateAward=false to let this own it). Cooperative + team-scoped (D-08): no individual-subject copy, no per-member call-out, no inter-team/public surface. Portable: zero next/*, SSR-safe, imports no other registry component. Third component of the gamification-system.

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/team-feedback-loop

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/team-feedback-loop-fixtures

Preview

Fire a celebration

Imperative celebrate() — a brief (<1s), skippable, NON-BLOCKING band at the bottom. Click behind it: the page stays fully interactive. Press Esc or ✕ to skip.

Options

Confetti is opt-in + lazy (milestone/badge only, never under reduced motion). Toggle your OS reduced-motion setting to see the static branch.

Live loop (celebration + nudge)

The inline nudge renders here; accept/dismiss are penalty-free. Watch the console for callbacks.

Next upPick up: wire the win screen

Composed / lighter (nudge only)

Hand-assembled Root + only TeamFeedbackNudge — no celebration, and the confetti chunk never loads.

Next upPick up: wire the win screen

Demo source

demo.tsxtsx

Usage

When to use

Reach for TeamFeedbackLoop to close the cooperative feedback loops: a brief, skippable, NON-BLOCKING celebration when the host says "team progress just advanced," followed by a gentle next-task nudge. It owns no milestone/badge/task state — the host triggers it, it renders the moment and gets out of the way. Every reward is about the team, never an individual (D-08).

The cardinal constraint: non-blocking (D-10)

  • The board stays fully interactive during a celebration — clicks pass through.
  • Never moves or traps focus; the skip button is the only clickable element.
  • Auto-dismisses in < 1scelebrationDurationMs is clamped to [200, 999), so a lingering modal is impossible.
  • Skip with a click on ✕ or Esc at any time.

Two trigger paths (one reducer)

import { TeamFeedbackLoop } from "@/components/team-feedback-loop"

// Controlled (declarative hosts): set event → open; null → close.
<TeamFeedbackLoop
  teamId={team.id}
  event={lastEvent}                 // { kind: "milestone", title: "Your team…" }
  enableConfetti
  nextTask={nextTask}               // { taskId, label: "Pick up: wire the win screen" }
  onNextTask={(s) => openTask(s.taskId)}
/>

// Imperative (event-driven hosts): fire from a callback.
const ref = useRef<TeamFeedbackLoopHandle>(null)
ref.current?.celebrate({ kind: "task-complete", title: "Your team cleared the column" })

Both funnel into one internal reducer — identical behavior regardless of entry point. Newest event wins; overlays never stack.

Confetti + reduced motion

  • enableConfetti (default off) adds a lazy canvas-confetti burst for milestone/badge only. The default CSS flourish keeps the confetti chunk out of the bundle entirely.
  • Under prefers-reduced-motion: reduce the celebration renders static — no movement, no confetti — still time-boxed and skippable.

D-16 celebration ownership

If you also use team-trophy-shelf, route each event kind to exactly one celebrator: set the shelf's animateAward={false} and push badge/milestonehere, OR let the shelf own the in-place reveal and don't push that kind here. Neither component triggers the other.

Notes

  • Compound: drop TeamFeedbackCelebration or TeamFeedbackNudge for a subset. onEvent is accepted for symmetry but E6 emits nothing.
  • Portable + SSR-safe: no next/*, no animation on first paint; only the shadcn button primitive + lucide + the lazy confetti dep.

Features

  • Brief (<1s), skippable, NON-BLOCKING celebration overlay — clamped timer, click-through, no focus trap (D-10)
  • Two trigger paths (controlled event prop + imperative celebrate()) funnel into one reducer — newest wins, never stack
  • Gentle, penalty-free next-task nudge (inline or corner), independent of the celebration lifecycle
  • Reduced-motion static branch — no movement, no confetti, still time-boxed + skippable
  • Opt-in canvas-confetti burst, React.lazy for milestone/badge — the default CSS flourish keeps it out of the bundle
  • Team-scoped copy only — never an individual (D-08); onEvent accepted for symmetry, emits nothing (E6)
  • Compound: headless Root + flat parts + lazy confetti; drop either surface for a subset
  • D-16 celebration ownership — set the trophy shelf's animateAward=false to defer the moment here

Tags

team-feedback-loopgamificationcelebrationconfettinudgenon-blockingteamcooperative

Dependencies

shadcn primitives: button
npm peer deps: canvas-confetti@^1.9.4, lucide-react@^1.11.0