Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
3b56ebf
Consolidate the easter eggs behind one shared module
daniilperkin Aug 25, 2026
852ae97
Replace the 404 auto-open with a discoverable rocket teaser
daniilperkin Aug 25, 2026
b386e14
Let header icons hint that they hide an easter egg
daniilperkin Aug 25, 2026
b79bcf6
Point the dashboard header-icon egg at 2048 instead of dino
daniilperkin Aug 25, 2026
026a9b0
Bring the dino waiting-game and chat easter-egg phrases to the buddy
daniilperkin Aug 25, 2026
a22d244
Remove the Ctrl+Shift dev shortcuts and stale easter-egg leftovers
daniilperkin Aug 25, 2026
73f05f2
Let "party" in the chat rain confetti
daniilperkin Aug 26, 2026
a90a45f
Make the sidebar logo fall off its shelf when clicked five times
daniilperkin Aug 26, 2026
5eda917
feat(dashboard): add egg hint to header icon and enable Escape key cl…
daniilperkin Aug 26, 2026
c600973
Merge remote-tracking branch 'origin/feature/minor-upgrades' into fea…
daniilperkin Aug 26, 2026
f7753e8
Merge remote-tracking branch 'origin/feature/minor-upgrades' into fea…
daniilperkin Aug 28, 2026
c9fe6a3
Merge branch 'feature/269-confluence-connector' into feature/easter-e…
daniilperkin Aug 29, 2026
46ff21a
Merge branch 'dev' into feature/easter-eggs-overhaul
daniilperkin Sep 15, 2026
d1a7ffc
Close the easter-egg review gaps: buddy Space, reduced-motion roll, o…
daniilperkin Sep 16, 2026
8a0335e
Fix the formatting drift the prettier check was failing on
daniilperkin Sep 16, 2026
9157706
Merge remote-tracking branch 'origin/dev' into feature/easter-eggs-ov…
daniilperkin Sep 16, 2026
8f0b5d1
Close the waiting-game when its wait ends and hand the slot back
daniilperkin Sep 16, 2026
a502ba3
Give a re-fired whole-window effect its own full run
daniilperkin Sep 16, 2026
e20188c
Keep the buddy caret when a submission started no turn
daniilperkin Sep 16, 2026
467f8eb
Require the hidden click gestures to be consecutive
daniilperkin Sep 16, 2026
9d5eed8
Let Escape close a game modal that is still loading
daniilperkin Sep 16, 2026
dde5650
Drop the hardcoded blue from the header icon glow keyframe
daniilperkin Sep 16, 2026
811dff3
Say why the sidebar logo keeps its pointer cursor
daniilperkin Sep 16, 2026
4f78f20
feat(chatbot): redesign source and date filter with floating glass po…
daniilperkin Sep 17, 2026
16e8cfb
fix(test): remove unused act import in ChatProvider test
daniilperkin Sep 17, 2026
e28e6bd
fix(chatbot): address review feedback on filter popover and presets
daniilperkin Sep 18, 2026
7b4ec80
feat(chat): add reply option to quote AI chat response (#227)
daniilperkin Sep 18, 2026
3265bbc
fix(chat): append quote at bottom under existing draft text
daniilperkin Sep 18, 2026
dd3bf4d
fix(chat): keep an answer's paragraphs in the Reply quote
daniilperkin Sep 19, 2026
7fb460a
Merge origin/dev into feature/easter-eggs-overhaul (backmerge, incomi…
daniilperkin Sep 19, 2026
8115ed2
Merge origin/hotfix/207-format-check-fixes into feature/easter-eggs-o…
daniilperkin Sep 19, 2026
fa82433
Merge origin/feature/230-chat-error-toasts-and-message-queue into fea…
daniilperkin Sep 19, 2026
1e6b3be
Merge origin/feature/227-in-chat-response into feature/easter-eggs-ov…
daniilperkin Sep 19, 2026
90f8522
Merge origin/feature/buddy-board-actions into feature/easter-eggs-ove…
daniilperkin Sep 19, 2026
102b7b2
Merge origin/feature/chat-source-filter into feature/easter-eggs-over…
daniilperkin Sep 19, 2026
5a6846a
Merge origin/feature/blueprint-onboarding into feature/easter-eggs-ov…
daniilperkin Sep 19, 2026
423169d
Resolve cross-PR conflicts from the backmerge round
daniilperkin Sep 19, 2026
fd63264
feat(chat): add reply option to quote AI chat response (#227)
daniilperkin Sep 18, 2026
c52e810
fix(chat): append quote at bottom under existing draft text
daniilperkin Sep 18, 2026
5733c16
fix(chat): keep an answer's paragraphs in the Reply quote
daniilperkin Sep 19, 2026
1ec1e5f
feat(chatbot): redesign source and date filter with floating glass po…
daniilperkin Sep 17, 2026
97d9074
fix(test): remove unused act import in ChatProvider test
daniilperkin Sep 17, 2026
9f5aa57
fix(chatbot): address review feedback on filter popover and presets
daniilperkin Sep 18, 2026
3040ed5
Add a tolerant parser for GitHub artifact repository metadata
daniilperkin Sep 19, 2026
2489e50
Show the source repository on GitHub knowledge artifacts
daniilperkin Sep 19, 2026
d2f5bc8
test(chat): supply ChatComposer queue props in filter tests (stack in…
daniilperkin Sep 19, 2026
d229a12
Merge origin/feature/227-in-chat-response into feature/easter-eggs-ov…
daniilperkin Sep 19, 2026
b991e73
Merge origin/feature/chat-source-filter into feature/easter-eggs-over…
daniilperkin Sep 19, 2026
8190f42
Merge origin/feature/247-KB-display-repos into feature/easter-eggs-ov…
daniilperkin Sep 19, 2026
8eeea55
Resolve backmerge round 2: stacked chat PRs and the KB repo labels
daniilperkin Sep 19, 2026
1281bee
Merge origin/dev into feature/easter-eggs-overhaul (backmerge round 3…
daniilperkin Sep 20, 2026
44f3cc5
Absorb the dev round that merged the chat stack and reworked Hire Setup
daniilperkin Sep 20, 2026
17dd0d4
Merge origin/dev into feature/easter-eggs-overhaul (backmerge round 4…
daniilperkin Sep 21, 2026
1374185
feat(easter-eggs): keep dino waiting game active during thoughts stre…
daniilperkin Sep 21, 2026
8a20fb8
feat(easter-eggs): anchor dino unlock speech bubble to settings icon
daniilperkin Sep 21, 2026
9999dd2
feat(easter-eggs): expand dino waiting game to onboarding generation …
daniilperkin Sep 21, 2026
efc9322
Merge origin/dev into feature/easter-eggs-overhaul
daniilperkin Sep 21, 2026
0b22e8b
Merge PR #228 into feature/easter-eggs-overhaul
daniilperkin Sep 21, 2026
62bd10d
Merge PR #248 into feature/easter-eggs-overhaul
daniilperkin Sep 21, 2026
1aa8868
tests(chatbot): wrap queuedMessages length assertion in waitFor
daniilperkin Sep 21, 2026
3f701f5
Fix the bugs the review of the unpushed commits found
daniilperkin Sep 21, 2026
d531e21
Merge origin/dev into feature/easter-eggs-overhaul (final backmerge: …
daniilperkin Sep 22, 2026
de05579
Upgrade the egg surfaces: honest waiting-game badges, replayable barrel
daniilperkin Sep 22, 2026
ba9f7d0
upgrade(easter-eggs): hand the knowledge-base and tooling tweaks to #264
daniilperkin Sep 24, 2026
9830978
upgrade(easter-eggs): keep focus inside the game dialog and survive f…
daniilperkin Sep 24, 2026
2908104
upgrade(settings): announce the dino unlock reliably
daniilperkin Sep 24, 2026
772a651
upgrade(layout): token the logo springs, fix the 404 teaser, drop a r…
daniilperkin Sep 24, 2026
29ea4bc
upgrade(chatbot): make the dino waiting game honest on every surface
daniilperkin Sep 24, 2026
321fc30
upgrade(ui): keep the SidePanel Escape test in its own file
daniilperkin Sep 24, 2026
17cdf9b
upgrade(easter-eggs): let Space through on a disabled control
daniilperkin Sep 24, 2026
061276e
upgrade(easter-eggs): free the game slot after commit, keep shortcuts…
daniilperkin Sep 25, 2026
b3dd474
upgrade(easter-eggs): address David's review - generation status driv…
daniilperkin Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ self-contained under `src/features/` and surfaced through dedicated routes:
- **Insights β€” Knowledge Gaps** β€” AI-detected missing documentation per component, with severity (high / medium / low), component owners, and refresh tracking.
- **Insights β€” FAQ** β€” AI-generated clusters of frequently asked questions and the documents that answer them.
- **Settings & Profile** β€” Central configuration hub with tabs for user profile management (avatar, display name, password update), appearance (light/dark/system theme), chat preferences, moments toggles, and access tokens (GitHub PAT / Jira credential management for authorized roles).
- **Moments & Easter Eggs** β€” Gamified celebrations (confetti, achievement moments, sound effects) and interactive easter eggs (`dino`, `game2048`, `space-invaders`).
- **Moments & Easter Eggs** β€” Gamified celebrations (confetti, achievement moments, sound effects) and interactive easter eggs (the dino waiting-game, `2048` behind the dashboard header icon, Space Invaders behind the 404 page's rocket).

---

Expand All @@ -70,18 +70,16 @@ src/
β”‚ β”œβ”€β”€ connectors/ # Connector + source allow/deny management
β”‚ β”œβ”€β”€ dashboard/ # Dashboard hero & NextStepWidget
β”‚ β”œβ”€β”€ data-ingestion/ # Sources, ingestion runs, artifact tables
β”‚ β”œβ”€β”€ dino/ # Dino easter-egg mini game
β”‚ β”œβ”€β”€ easter-eggs/ # Easter-egg trigger hooks & modals
β”‚ β”œβ”€β”€ easter-eggs/ # Easter eggs: egg shell + game registry, the
β”‚ β”‚ # effect bus, phrases and the dino waiting-game
β”‚ β”œβ”€β”€ faq/ # AI FAQ clusters (insights)
β”‚ β”œβ”€β”€ game2048/ # 2048 easter-egg mini game
β”‚ β”œβ”€β”€ knowledge-base/ # Artifact browsing + streamed summaries + file upload
β”‚ β”œβ”€β”€ knowledge-gaps/ # AI-detected documentation gaps (insights)
β”‚ β”œβ”€β”€ moments/ # Celebrations, confetti, rocket animations
β”‚ β”œβ”€β”€ onboarding/ # AI onboarding paths, knowledge checks, skip workflow
β”‚ β”œβ”€β”€ profile/ # Profile form components
β”‚ β”œβ”€β”€ projects/ # Multi-project switching & global ProjectContext
β”‚ β”œβ”€β”€ settings/ # User settings tabs, themes & credentials
β”‚ β”œβ”€β”€ space-invaders/ # Space Invaders easter-egg mini game
β”‚ └── team-management/ # Team overview, member detail, Skill Wizard
β”œβ”€β”€ pages/ # Route-level views (one per user-facing flow)
β”œβ”€β”€ router/ # React Router v7 config + AuthGuard
Expand Down
7 changes: 7 additions & 0 deletions public/easter-eggs/2048.html
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,13 @@
const mapped = keyMap[event.key];

if (!modifiers) {
if (event.key === "Escape") {
try {
window.parent.postMessage({ type: "EGG_EXIT" }, window.location.origin);
} catch (_) {}
return;
}

if (mapped !== undefined) {
event.preventDefault();
// Ignore auto-repeat key events so holding an arrow doesn't
Expand Down
7 changes: 7 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SelectionActions } from "./features/board/selection/SelectionActions";
import { CardMarksProvider } from "./features/board/marks/CardMarksProvider";
import { useAuth } from "./context/useAuth";
import { AuroraBackground } from "./components/layout/AuroraBackground";
import { EggEffectsLayer } from "./features/easter-eggs/components/EggEffectsLayer";
import { MyKnowledgeGapsProvider } from "./features/knowledge-gaps/MyKnowledgeGapsProvider";
import { KnowledgeGapOwnerAnnouncement } from "./features/knowledge-gaps/components/KnowledgeGapOwnerAnnouncement";
import { useScrollRestoration } from "./hooks/useScrollRestoration";
Expand Down Expand Up @@ -139,6 +140,12 @@ function AppContent() {
sits on top of the login screen, and off unless turned on in
Settings (see AppearanceSection). */}
{signedIn && showRocketPet && <RocketPet />}

{/* Whole-window egg effects (barrel roll, matrix rain), rendered
once for the whole app. Any chat surface fires them through the
bus (playEggEffect); this is where they actually draw. Not gated
on signedIn: a fired effect must always have its renderer. */}
<EggEffectsLayer />
</div>
</BuddyProvider>
);
Expand Down
60 changes: 41 additions & 19 deletions src/components/layout/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { LucideIcon } from "lucide-react";
type PageHeaderProps = {
icon: LucideIcon;
title: string;
subtitle: string;
subtitle?: string;
actions?: ReactNode;
className?: string;
/**
Expand All @@ -13,50 +13,72 @@ type PageHeaderProps = {
* where the subtitle is secondary. `undefined` keeps the subtitle always visible.
*/
hideSubtitleBelow?: "sm" | "md" | "lg";
/** Optional click handler for the header icon (e.g., for easter eggs). */
onIconClick?: () => void;
/**
* Optional click handler for the header icon (e.g., for easter eggs).
* Marks the icon as hiding an easter egg: it grows slightly on hover and
* plays a one-shot glow pulse on mount. Purely decorative, so it is not
* reflected in the accessible name; users who prefer reduced motion get
* neither effect (see the page-header-icon rules in styles/index.css).
*/
onIconClick?: () => void;
eggHint?: boolean;
/**
* Optional popover or speech bubble anchored directly to the header icon
* (e.g. for easter-egg hints or status badges).
*/
iconPopover?: ReactNode;
};

export function PageHeader({
icon: Icon,
title,
subtitle,
subtitle = "",
actions,
className = "",
hideSubtitleBelow,
onIconClick,
eggHint = false,
iconPopover,
}: PageHeaderProps) {
return (
<div className={className}>
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<div className="flex min-w-0 items-center gap-3">
{onIconClick ? (
<button
onClick={onIconClick}
className="rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-app-brand"
aria-label={`${title} icon`}
>
<Icon className="h-6 w-6 shrink-0 text-app-brand-text transition-transform active:scale-95" />
</button>
<div className="relative inline-flex shrink-0">
<button
type="button"
onClick={onIconClick}
data-egg-hint={eggHint ? "true" : undefined}
className="page-header-icon-button rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-app-brand"
aria-label={`${title} icon`}
>
<Icon className="page-header-icon h-6 w-6 shrink-0 text-app-brand-text transition-transform active:scale-95" />
</button>
{iconPopover}
</div>
) : (
<Icon className="h-6 w-6 shrink-0 text-app-brand-text" />
<div className="relative inline-flex shrink-0">
<Icon className="h-6 w-6 shrink-0 text-app-brand-text" />
{iconPopover}
</div>
)}

<h1 className="min-w-0 text-xl leading-tight font-semibold text-app-text sm:text-2xl">
{title}
</h1>
</div>

<p
className={`mt-2 max-w-2xl text-sm leading-6 text-app-text-subtle ${
hideSubtitleBelow ? `hidden ${hideSubtitleBelow}:block` : ""
}`}
>
{subtitle}
</p>
{subtitle ? (
<p
className={`mt-2 max-w-2xl text-sm leading-6 text-app-text-subtle ${
hideSubtitleBelow ? `hidden ${hideSubtitleBelow}:block` : ""
}`}
>
{subtitle}
</p>
) : null}
</div>

{actions ? (
Expand Down
99 changes: 93 additions & 6 deletions src/components/layout/SidebarLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,59 @@
import { motion, useReducedMotion, type Variants } from "framer-motion";
import { useState } from "react";
import { motion, useReducedMotion, type TargetAndTransition, type Variants } from "framer-motion";
import { useRepeatClicks } from "../../features/easter-eggs/hooks/useRepeatClicks";
import { hoverSpringToken, logoHopSpringToken } from "../../styles/tokens";

const BADGE_SIZE = 44;
const MARK_SIZE = 28;

/**
* How far the badge falls before it hits the floor. Chosen to clear the
* header row visually (parents are overflow-visible) so the drop reads as
* "fell past its shelf, bounced, hopped back" without portals or measured
* rects β€” a fixed translateY keeps the effect layout-independent.
*/
const DROP_DISTANCE = 140;

/**
* The gravity choreography, one entry per phase. Each phase animates the
* whole badge; `onAnimationComplete` walks idle β†’ falling β†’ impact β†’
* bouncing β†’ hopping β†’ idle, so every beat lands exactly when the previous
* one finishes instead of relying on hand-tuned delays.
*/
const DROP_PHASES: Record<Exclude<DropPhase, "idle">, TargetAndTransition> = {
// Gravity accelerates: easeIn, with a slight forward tumble.
falling: { y: DROP_DISTANCE, rotate: 6, transition: { duration: 0.45, ease: "easeIn" } },
// Sideways squash on impact β€” the classic cartoon beat.
impact: {
scaleX: 1.14,
scaleY: 0.8,
rotate: 0,
transition: { duration: 0.09, ease: "easeOut" },
},
// Two diminishing bounces; each landing re-squashes a little less.
bouncing: {
y: [DROP_DISTANCE, 94, DROP_DISTANCE, 119, DROP_DISTANCE],
scaleX: [1.14, 1.02, 1.1, 1.03, 1.1],
scaleY: [0.8, 1, 0.87, 1, 0.93],
transition: {
duration: 0.62,
times: [0, 0.26, 0.5, 0.74, 1],
ease: ["easeOut", "easeIn", "easeOut", "easeIn"],
},
},
// Spring back to the shelf with a small overshoot, rotation settling.
hopping: {
y: 0,
scaleX: 1,
scaleY: 1,
rotate: 0,
transition: logoHopSpringToken,
},
};

/** The gravity sequence phases: idle at rest, then falling β†’ impact β†’ bouncing β†’ hopping. */
type DropPhase = "idle" | "falling" | "impact" | "bouncing" | "hopping";

/**
* Where the exhaust meets the body, in viewBox units. The flame scales from
* here so the plume grows away from the engine instead of stretching from its
Expand All @@ -29,13 +80,31 @@ const NOZZLE_ORIGIN = { transformOrigin: "7px 17px", transformBox: "view-box" }
export function SidebarLogo({ className = "" }: { className?: string }) {
const prefersReducedMotion = useReducedMotion();

// The gravity easter egg: five clicks drop the badge off its shelf.
// Reduced motion skips the animation entirely (pure motion has no honest
// static equivalent) β€” the counter still consumes, nothing plays.
const [dropPhase, setDropPhase] = useState<DropPhase>("idle");
const handleLogoClick = useRepeatClicks(5, () => {
if (!prefersReducedMotion && dropPhase === "idle") setDropPhase("falling");
});

/** Walks the choreography forward as each beat's animation completes. */
const advanceDrop = () => {
setDropPhase((current) => {
if (current === "falling") return "impact";
if (current === "impact") return "bouncing";
if (current === "bouncing") return "hopping";
return "idle";
});
};

const badgeVariants: Variants = prefersReducedMotion
? { rest: {}, lift: {} }
: {
rest: { scale: 1 },
lift: {
scale: 1.08,
transition: { type: "spring", stiffness: 400, damping: 16 },
transition: hoverSpringToken,
},
};

Expand All @@ -51,7 +120,7 @@ export function SidebarLogo({ className = "" }: { className?: string }) {
lift: {
x: 1.5,
y: -1.5,
transition: { type: "spring", stiffness: 420, damping: 14 },
transition: hoverSpringToken,
},
};

Expand Down Expand Up @@ -94,11 +163,29 @@ export function SidebarLogo({ className = "" }: { className?: string }) {
return (
<motion.div
initial="rest"
animate="rest"
whileHover="lift"
animate={dropPhase === "idle" ? "rest" : DROP_PHASES[dropPhase]}
// Hover lift is suppressed while the gravity sequence plays so the
// two transforms never fight over the same element.
whileHover={dropPhase === "idle" ? "lift" : undefined}
variants={badgeVariants}
onAnimationComplete={advanceDrop}
onClick={handleLogoClick}
data-drop-phase={dropPhase}
// The logo is deliberately decorative markup β€” the egg behind it is
// pure whimsy with no function, and making it tab-focusable on both
// in-app surfaces (the sidebar header and the mobile header) would
// worsen keyboard navigation for everyone to serve a secret. The
// login page's copy of the mark carries no egg at all.
//
// `data-drop-phase` exposes the choreography's state machine so tests
// (and devtools) can observe the egg without a running animation loop.
//
// The pointer cursor stays: unlike a plain decorative wrapper this one
// does answer clicks (that is the whole egg), and it is the only hint
// anybody gets that the badge is worth touching. It is not a home link
// on any surface, so it promises no navigation.
style={{ width: BADGE_SIZE, height: BADGE_SIZE }}
className={`relative flex shrink-0 items-center justify-center rounded-[12px] bg-app-brand shadow-lg ${className}`}
className={`relative flex shrink-0 cursor-pointer items-center justify-center rounded-[12px] bg-app-brand shadow-lg select-none ${className}`}
>
<motion.svg
variants={rocketVariants}
Expand Down
8 changes: 8 additions & 0 deletions src/components/ui/SidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,15 @@ export function SidePanel({
useEffect(() => {
if (!isOpen) return;

/**
* Escape closes the drawer only when nothing inside it has already claimed
* the key. Inner consumers (e.g. the dino mini-game, which exits on Escape)
* call `preventDefault()`; without this check one keypress would close both
* the inner widget and the whole drawer.
*/
function handleKeyDown(event: KeyboardEvent) {
if (event.key === "Escape" && event.defaultPrevented) return;

if (event.key === "Escape" && closeOnEscape) {
onClose();
return;
Expand Down
17 changes: 17 additions & 0 deletions src/context/ChatContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ export type ChatContextValue = {
*/
streamingChatId: string | null;

/**
* How the most recent turn ended, per chat. `null` while a turn is running
* or before the first one. Exists so waiting UI (the dino easter egg) can
* say "Stopped" / "Reply failed" instead of claiming a reply is ready when
* the user pressed Stop or the stream errored.
*/
lastTurnOutcome: ChatTurnOutcome | null;

selectedCitation: SelectedCitation | null;
setSelectedCitation: (value: SelectedCitation | null) => void;

Expand Down Expand Up @@ -170,4 +178,13 @@ export type ChatContextValue = {
deleteChat: (chatId: string) => Promise<void>;
};

/**
* Outcome of a finished chat turn, keyed by the chat it belongs to so a
* background chat's result never leaks into the one on screen.
*/
export type ChatTurnOutcome = {
chatId: string;
kind: "done" | "stopped" | "failed";
};

export const ChatContext = createContext<ChatContextValue | undefined>(undefined);
Loading
Loading