Team Quest Log
alphav0.2.1Team quest overlay — an editable skippable quest name plus a milestone chapter timeline with done, current, and upcoming beats.
Context
The Team Narrative Framing surface of the gamification pack (E5, Autonomy + Relatedness). Two mountable surfaces over one team + its milestones: (a) a quest-name editor — display the resolved title (questName?.trim() || name) with an inline display↔edit toggle; clearing/saving blank reverts to the literal team name (the skip path, never forced); a quiet 'name your quest' invitation on the default; and (b) a milestone-chapter timeline — one vertical-rail beat per NarrativeChapter, ordered, each framing a milestone, with done / current ('you are here') / upcoming states derived from Milestone.done (current = the first not-done beat; unresolved milestoneId renders gracefully + dev-warns, never crashes). Deliberately lighter than gantt-timeline / event-calendar — chapters, not a time axis; it consumes no TaskItem[] and imports no other registry component. Emits narrative.chapter-viewed once per chapter per mount when a beat first scrolls into view (one IntersectionObserver, fire-once Set, SSR-guarded, click fallback). Ships as a light shadcn-style compound (no React.lazy) — headless TeamQuestLogRoot (title resolution + draft/edit state + beat derivation + telemetry + a forwardRef handle: focusNameEditor / scrollToChapter) + flat parts (TeamQuestNameEditor, TeamQuestChapters) + context-free primitives (QuestTitle, QuestNameField, ChapterRail, ChapterBeat, EmptyNarrative) + the TeamQuestLog assembly — so a timeline-only or name-only subset falls out. Controlled (D-06); portable — zero next/*, own types.ts slice; SSR-safe. Sixth and final component of the gamification-system.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/team-quest-logAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/team-quest-log-fixturesPreview
Full overlay — live (name your quest)
Starts on the default team name. Name it, edit it, or clear it to revert — never forced. Beats emit narrative.chapter-viewed once when scrolled into view.
Team Aurora
Custom quest name + in-progress narrative
done · done · current (you are here) · upcoming — each visually distinct (icon + color).
The Road to Launch
All done vs none done
All done → every beat completed, no 'current'. None done → the first beat is 'current'.
The Road to Launch
Fresh Start
Edge cases
Empty narrative (quiet placeholder) · an unresolved-milestone beat (renders gracefully).
The Road to Launch
Your team's story will appear here as milestones are reached.
The Road to Launch
Composed / lighter — timeline-only + name-only subsets
Hand-assembled from Root + one part each — the compound tree-shakes to a subset.
Team Aurora
Demo source
Usage
When to use
Reach for TeamQuestLog to give a team a light sense of story over its journey — a name for the quest and milestone beats framed as chapters. It is the E5 (Autonomy + Relatedness) surface of the gamification pack. The narrative is optional and skippable: a blank quest name falls back to the team's literal name, and the chapter timeline degrades quietly when no chapters are authored. It is deliberately lighter than the Gantt / calendar — chapters, not a time axis.
Basic example
import { TeamQuestLog } from "@/components/team-quest-log"
<TeamQuestLog
team={team} // { id, name, questName? }
milestones={milestones} // host-owned shared spine
chapters={chapters} // { id, title, milestoneId, order }
onQuestNameChange={(name) => saveQuestName(team.id, name)} // "" reverts to team name
onEvent={(e) => analytics.track(e.type, e)} // narrative.chapter-viewed
/>Never forced
The displayed title is questName?.trim() || name. Clearing the field (saving blank) reverts to the literal team name — a first-class skip path, no required-field validation, no nag. On the default name a quiet Name your quest invitation shows (never a blocking prompt).
Beat states
- done — the milestone is complete (check marker).
- current — the first not-done beat by order (the you are here pin).
- upcoming — later not-done beats (hollow marker).
State is derived from milestones and conveyed by icon + color (never color alone). An unresolved milestoneId renders gracefully (as upcoming) and dev-warns — it never crashes.
Compose a lighter version
import { TeamQuestLogRoot, TeamQuestChapters } from "@/components/team-quest-log"
// Timeline-only — the name-editor code never enters this bundle:
<TeamQuestLogRoot team={team} milestones={milestones} chapters={chapters} onEvent={track}>
<TeamQuestChapters />
</TeamQuestLogRoot>Notes
- Telemetry.
onEventemitsnarrative.chapter-viewedonce per chapter per mount, when a beat first scrolls into view (IntersectionObserver). OmitonEventand no observer attaches. - Imperative handle. A ref exposes
focusNameEditor()andscrollToChapter(id)for onboarding / deep-link hosts. - Team-scoped.Only this team's narrative — no comparison, no other team's data.
- Portable. No
next/*, owntypes.tsslice, no other registry import; only the shadcninput/buttonprimitives + lucide.
Features
- Skippable quest name — displayed title = questName?.trim() || name; clearing reverts to the team name (never forced, no validation)
- Milestone-chapter timeline — one vertical-rail beat per chapter, done / current / upcoming derived from milestones
- Deterministic beat states — current = first not-done by order; all-done has no current; unresolved milestone renders gracefully + dev-warns
- narrative.chapter-viewed telemetry — one IntersectionObserver, fire-once per chapter per mount, SSR-guarded with a click fallback
- Non-color state cues (check / you-are-here pin / hollow) + accessible beat names — state reads without color
- Light compound — headless Root + flat TeamQuestNameEditor / TeamQuestChapters + context-free primitives; timeline-only + name-only subsets fall out
- Imperative handle — focusNameEditor() + scrollToChapter(id) for onboarding / deep-link hosts