Skip to content

Commit 699433c

Browse files
committed
feat(web): add tooltip component and integrate into VCP sections
- Introduced a new Tooltip component using Radix UI for enhanced user interaction. - Updated VCP components (AxisInfoTooltip, VCPAxesGrid, RepoAxesSection, UnifiedAxesSection) to utilize the new tooltip for displaying additional information about axes. - Enhanced AxisMetadata to include long descriptions for better context in tooltips. - Refactored layout in `route.tsx` to improve QR code generation and story image rendering. This update improves user experience by providing contextual information through tooltips, enhancing the overall interactivity of the VCP display.
1 parent eb88fec commit 699433c

10 files changed

Lines changed: 204 additions & 12 deletions

File tree

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"@radix-ui/react-popover": "^1.1.15",
1414
"@radix-ui/react-toast": "^1.2.15",
15+
"@radix-ui/react-tooltip": "^1.2.8",
1516
"@supabase/ssr": "^0.5.2",
1617
"@supabase/supabase-js": "^2.47.14",
1718
"@vercel/og": "^0.8.6",

apps/web/src/app/api/share/story/[userId]/route.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,15 +280,17 @@ const renderStoryImage = async (story: StoryData, qrDataUrl: string) => {
280280
<div
281281
style={{
282282
marginTop: 40,
283-
display: "grid",
284-
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
285-
gap: 18,
283+
display: "flex",
284+
flexWrap: "wrap",
285+
justifyContent: "space-between",
286286
}}
287287
>
288288
{story.metrics.map((metric) => (
289289
<div
290290
key={metric.label}
291291
style={{
292+
width: "48%",
293+
marginBottom: 18,
292294
borderRadius: 28,
293295
padding: "18px 20px",
294296
background: "rgba(255,255,255,0.1)",
@@ -456,6 +458,7 @@ export async function GET(
456458
return NextResponse.json({ error: "not_found" }, { status: 404 });
457459
}
458460

459-
const qrDataUrl = await QRCode.toDataURL(storyData.url, { width: 260 });
461+
const qrSvg = await QRCode.toString(storyData.url, { type: "svg", width: 260, margin: 0 });
462+
const qrDataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}`;
460463
return renderStoryImage(storyData, qrDataUrl);
461464
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5+
6+
import { cn } from "@/lib/utils"
7+
8+
function TooltipProvider({
9+
delayDuration = 0,
10+
...props
11+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12+
return (
13+
<TooltipPrimitive.Provider
14+
data-slot="tooltip-provider"
15+
delayDuration={delayDuration}
16+
{...props}
17+
/>
18+
)
19+
}
20+
21+
function Tooltip({
22+
...props
23+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24+
return (
25+
<TooltipProvider>
26+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
27+
</TooltipProvider>
28+
)
29+
}
30+
31+
function TooltipTrigger({
32+
...props
33+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
34+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
35+
}
36+
37+
function TooltipContent({
38+
className,
39+
sideOffset = 0,
40+
children,
41+
...props
42+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
43+
return (
44+
<TooltipPrimitive.Portal>
45+
<TooltipPrimitive.Content
46+
data-slot="tooltip-content"
47+
sideOffset={sideOffset}
48+
className={cn(
49+
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
50+
className
51+
)}
52+
{...props}
53+
>
54+
{children}
55+
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
56+
</TooltipPrimitive.Content>
57+
</TooltipPrimitive.Portal>
58+
)
59+
}
60+
61+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Info } from "lucide-react";
2+
3+
import { cn } from "@/lib/utils";
4+
import type { AxisMetadata } from "./constants";
5+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
6+
7+
type AxisInfoTooltipVariant = "light" | "dark";
8+
9+
interface AxisInfoTooltipProps {
10+
meta: AxisMetadata;
11+
variant?: AxisInfoTooltipVariant;
12+
className?: string;
13+
}
14+
15+
const VARIANT_STYLES: Record<AxisInfoTooltipVariant, string> = {
16+
light: "text-zinc-400 hover:text-zinc-600",
17+
dark: "text-white/40 hover:text-white/70",
18+
};
19+
20+
export function AxisInfoTooltip({
21+
meta,
22+
variant = "light",
23+
className,
24+
}: AxisInfoTooltipProps) {
25+
return (
26+
<Tooltip>
27+
<TooltipTrigger asChild>
28+
<button
29+
type="button"
30+
aria-label={`${meta.name} details`}
31+
className={cn(
32+
"inline-flex items-center justify-center rounded-full transition",
33+
VARIANT_STYLES[variant],
34+
className
35+
)}
36+
>
37+
<Info className="h-3.5 w-3.5" aria-hidden="true" />
38+
</button>
39+
</TooltipTrigger>
40+
<TooltipContent side="top" sideOffset={6} className="max-w-xs">
41+
<p className="text-xs font-semibold">{meta.name}</p>
42+
<p className="mt-1 text-xs text-background/85">{meta.longDescription}</p>
43+
<p className="mt-2 text-[11px] text-background/70">
44+
Score is 0-100. Lower means {meta.lowLabel}, higher means {meta.highLabel}.
45+
</p>
46+
</TooltipContent>
47+
</Tooltip>
48+
);
49+
}

apps/web/src/components/vcp/blocks/VCPAxesGrid.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { cn } from "@/lib/utils";
22
import type { VCPAxesGridProps, AxisKey } from "../types";
33
import { VCPProgressBar } from "../primitives";
44
import { AXIS_METADATA, AXIS_ORDER } from "../constants";
5+
import { AxisInfoTooltip } from "../AxisInfoTooltip";
56

67
/**
78
* VCPAxesGrid - Grid display of all six vibe axes
@@ -26,7 +27,10 @@ export function VCPAxesGrid({
2627
{axisEntries.map(({ key, score, level, meta }) => (
2728
<div key={key} className="space-y-1">
2829
<div className="flex items-center justify-between text-sm">
29-
<span className="font-medium text-white/80">{meta.name}</span>
30+
<div className="flex items-center gap-1">
31+
<span className="font-medium text-white/80">{meta.name}</span>
32+
<AxisInfoTooltip meta={meta} variant="dark" />
33+
</div>
3034
<span className="text-white/60">{score}</span>
3135
</div>
3236
<VCPProgressBar value={score} size="md" />
@@ -52,9 +56,12 @@ export function VCPAxesGrid({
5256
className="rounded-xl bg-white/5 p-4"
5357
>
5458
<div className="flex items-center justify-between">
55-
<span className="text-xs font-semibold uppercase tracking-wider text-white/50">
56-
{meta.name}
57-
</span>
59+
<div className="flex items-center gap-1">
60+
<span className="text-xs font-semibold uppercase tracking-wider text-white/50">
61+
{meta.name}
62+
</span>
63+
<AxisInfoTooltip meta={meta} variant="dark" />
64+
</div>
5865
<span className="text-lg font-bold text-white">{score}</span>
5966
</div>
6067
<VCPProgressBar value={score} size="sm" className="mt-2" />

apps/web/src/components/vcp/constants.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface AxisMetadata {
1414
name: string;
1515
shortName: string;
1616
description: string;
17+
longDescription: string;
1718
lowLabel: string;
1819
highLabel: string;
1920
}
@@ -23,41 +24,53 @@ export const AXIS_METADATA: Record<AxisKey, AxisMetadata> = {
2324
name: "Automation",
2425
shortName: "Auto",
2526
description: "How much you leverage AI/automation for large-scale changes",
27+
longDescription:
28+
'How "agentic" your workflow looks, driven by large commits and chunky PRs. Higher scores indicate more automation-heavy changes.',
2629
lowLabel: "Manual",
2730
highLabel: "AI-Heavy",
2831
},
2932
guardrail_strength: {
3033
name: "Guardrails",
3134
shortName: "Guard",
3235
description: "How early and consistently tests/CI/docs appear in your workflow",
36+
longDescription:
37+
"How much you stabilize work with tests, CI, and docs early in the lifecycle. Higher scores mean guardrails appear sooner and more consistently.",
3338
lowLabel: "Light",
3439
highLabel: "Rigorous",
3540
},
3641
iteration_loop_intensity: {
3742
name: "Iteration",
3843
shortName: "Iter",
3944
description: "How quickly you cycle through fix-forward loops after changes",
45+
longDescription:
46+
'How often you run rapid "generate → run → fix → run" cycles. Higher scores mean faster, denser iteration loops.',
4047
lowLabel: "Stable",
4148
highLabel: "Rapid",
4249
},
4350
planning_signal: {
4451
name: "Planning",
4552
shortName: "Plan",
4653
description: "How much upfront structure (issues, specs, docs-first) appears",
54+
longDescription:
55+
"How much intent and structure appear up front: linked issues, structured commits, and docs-first patterns. Higher scores indicate more planning.",
4756
lowLabel: "Emergent",
4857
highLabel: "Structured",
4958
},
5059
surface_area_per_change: {
5160
name: "Surface Area",
5261
shortName: "Scope",
5362
description: "How many subsystems your typical change touches",
63+
longDescription:
64+
"How broad each unit of work is across subsystems. Higher scores mean typical changes span more areas of the codebase.",
5465
lowLabel: "Narrow",
5566
highLabel: "Wide",
5667
},
5768
shipping_rhythm: {
5869
name: "Rhythm",
5970
shortName: "Rhythm",
6071
description: "Your shipping pattern — steady vs bursty",
72+
longDescription:
73+
"Bursty versus steady shipping over time. Higher scores indicate bursty sessions with gaps; lower scores indicate a steadier cadence.",
6174
lowLabel: "Steady",
6275
highLabel: "Bursty",
6376
},

apps/web/src/components/vcp/repo/RepoAxesSection.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { cn } from "@/lib/utils";
44
import type { VibeAxes } from "@vibed/core";
55
import { AXIS_METADATA, AXIS_ORDER } from "../constants";
6+
import { AxisInfoTooltip } from "../AxisInfoTooltip";
67

78
interface RepoAxesSectionProps {
89
/** Vibe axes data */
@@ -37,7 +38,12 @@ export function RepoAxesSection({ axes, className }: RepoAxesSectionProps) {
3738
>
3839
<div className="flex items-start justify-between">
3940
<div>
40-
<p className="text-sm font-semibold text-zinc-900">{meta.name}</p>
41+
<div className="flex items-center gap-1">
42+
<p className="text-sm font-semibold text-zinc-900">
43+
{meta.name}
44+
</p>
45+
<AxisInfoTooltip meta={meta} />
46+
</div>
4147
<p className="mt-0.5 text-xs text-zinc-500">{meta.description}</p>
4248
</div>
4349
<div className="text-right">

apps/web/src/components/vcp/unified/UnifiedAxesSection.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { cn } from "@/lib/utils";
44
import type { VibeAxes } from "@vibed/core";
55
import { AXIS_METADATA, AXIS_ORDER } from "../constants";
6+
import { AxisInfoTooltip } from "../AxisInfoTooltip";
67

78
interface UnifiedAxesSectionProps {
89
/** Vibe axes data */
@@ -42,7 +43,12 @@ export function UnifiedAxesSection({
4243
>
4344
<div className="flex items-start justify-between">
4445
<div>
45-
<p className="text-sm font-semibold text-zinc-900">{meta.name}</p>
46+
<div className="flex items-center gap-1">
47+
<p className="text-sm font-semibold text-zinc-900">
48+
{meta.name}
49+
</p>
50+
<AxisInfoTooltip meta={meta} />
51+
</div>
4652
<p className="mt-0.5 text-xs text-zinc-500">{meta.description}</p>
4753
</div>
4854
<span className="text-xl font-bold text-zinc-900">{score}</span>

docs/implementation-trackers/information-architecture-restructure.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ This tracker covers all implementation work for the Information Architecture Res
309309

310310
### X2. Polish & Testing
311311

312-
**Status:** `[ ] Not Started`
312+
**Status:** `[~] In Progress`
313313
**Depends on:** P1, P3, P4, P5, X1
314314
**Blocks:** X3
315315

@@ -365,7 +365,7 @@ This tracker covers all implementation work for the Information Architecture Res
365365
| P5: Vertical Stories ShareCard | 7 | 6 | `[~] In Progress` |
366366
| P6: LLM Tagline Generation | 5 | 5 | `[x] Complete` |
367367
| X1: Migration & Redirects | 5 | 4 | `[~] In Progress` |
368-
| X2: Polish & Testing | 7 | 0 | `[ ] Not Started` |
368+
| X2: Polish & Testing | 7 | 0 | `[~] In Progress` |
369369
| X3: Documentation | 5 | 0 | `[ ] Not Started` |
370370
| **Total** | **91** | **68** | **75%** |
371371

0 commit comments

Comments
 (0)