@burdenoff/microfe-timecampus 2026.825.2 → 2026.828.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.d.ts.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +75 -63
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/AnalyticsPage.d.ts.map +1 -1
- package/dist/timecampus/pages/dashboard/AnalyticsPage.js +130 -104
- package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.d.ts.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +567 -222
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/recommendationEvidence.d.ts +55 -0
- package/dist/timecampus/pages/lifetime/recommendationEvidence.d.ts.map +1 -0
- package/dist/timecampus/pages/lifetime/recommendationEvidence.js +73 -0
- package/dist/timecampus/pages/lifetime/recommendationEvidence.js.map +1 -0
- package/dist/timecampus/pages/timeline/TimelinePage.d.ts.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +154 -126
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecommendationsPage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/RecommendationsPage.tsx"],"sourcesContent":["import { useMemo, useState, type ComponentType } from \"react\";\nimport {\n Sparkles,\n Check,\n CheckCircle2,\n X,\n Clock,\n ShieldCheck,\n Lightbulb,\n Briefcase,\n Moon,\n HeartPulse,\n Users,\n GraduationCap,\n Coffee,\n Bus,\n Hourglass,\n Home,\n Heart,\n Handshake,\n Wallet,\n Circle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n useMyRecommendationsQuery,\n useGenerateMyRecommendationsMutation,\n useAcceptRecommendationMutation,\n useDismissRecommendationMutation,\n useSnoozeRecommendationMutation,\n type RecommendationFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { lifeDomainLabel } from \"./domains\";\n\ntype RecItem = RecommendationFieldsFragment;\n\n// ---------------------------------------------------------------------------\n// Recommendation states are stored as free strings on the backend\n// (NEW / ACCEPTED / DISMISSED / SNOOZED / EXPIRED — see the svc schema). We\n// segment the feed by them; unknown states simply don't match a tab.\n// ---------------------------------------------------------------------------\nconst REC_STATE = {\n New: \"NEW\",\n Snoozed: \"SNOOZED\",\n Accepted: \"ACCEPTED\",\n Dismissed: \"DISMISSED\",\n Expired: \"EXPIRED\",\n} as const;\n\nconst SEGMENTS = [\n { key: REC_STATE.New, label: \"New\" },\n { key: REC_STATE.Snoozed, label: \"Snoozed\" },\n { key: REC_STATE.Accepted, label: \"Accepted\" },\n { key: REC_STATE.Dismissed, label: \"Dismissed\" },\n] as const;\n\ntype SegmentKey = (typeof SEGMENTS)[number][\"key\"];\n\n/** A NEW or SNOOZED recommendation is still actionable (accept / snooze / dismiss). */\nfunction isActionable(state: string): boolean {\n return state === REC_STATE.New || state === REC_STATE.Snoozed;\n}\n\n/** Snooze a week out — long enough to be non-nagging, short enough to resurface. */\nfunction snoozeUntilIso(): string {\n return new Date(Date.now() + 7 * 86_400_000).toISOString();\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\nconst asPercent = (n: number) => `${Math.round(clamp01(n) * 100)}%`;\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n });\n}\n\n/** Humanise a machine \"kind\" (e.g. FOCUS_DROP → \"Focus Drop\") for the eyebrow. */\nfunction humaniseKind(kind: string): string {\n return kind\n .toLowerCase()\n .split(/[_\\s]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\nconst DOMAIN_ICON: Record<\n TimeCampusLifeDomain,\n ComponentType<{ className?: string }>\n> = {\n [TimeCampusLifeDomain.Work]: Briefcase,\n [TimeCampusLifeDomain.Sleep]: Moon,\n [TimeCampusLifeDomain.Health]: HeartPulse,\n [TimeCampusLifeDomain.Family]: Users,\n [TimeCampusLifeDomain.Learning]: GraduationCap,\n [TimeCampusLifeDomain.Leisure]: Coffee,\n [TimeCampusLifeDomain.Commute]: Bus,\n [TimeCampusLifeDomain.Waiting]: Hourglass,\n [TimeCampusLifeDomain.Chores]: Home,\n [TimeCampusLifeDomain.SelfCare]: Heart,\n [TimeCampusLifeDomain.Social]: Handshake,\n [TimeCampusLifeDomain.FinanceAdmin]: Wallet,\n [TimeCampusLifeDomain.Reflection]: Sparkles,\n [TimeCampusLifeDomain.Other]: Circle,\n};\n\nfunction recIcon(\n domain?: TimeCampusLifeDomain | null,\n): ComponentType<{ className?: string }> {\n if (!domain) return Lightbulb;\n return DOMAIN_ICON[domain] ?? Lightbulb;\n}\n\ntype BadgeTone = \"success\" | \"warning\" | \"muted\" | \"info\";\n\nfunction stateBadge(state: string): { variant: BadgeTone; label: string } {\n switch (state) {\n case REC_STATE.Accepted:\n return { variant: \"success\", label: \"Accepted\" };\n case REC_STATE.Snoozed:\n return { variant: \"warning\", label: \"Snoozed\" };\n case REC_STATE.Dismissed:\n return { variant: \"muted\", label: \"Dismissed\" };\n case REC_STATE.Expired:\n return { variant: \"muted\", label: \"Expired\" };\n default:\n return { variant: \"info\", label: \"New\" };\n }\n}\n\nconst primaryBtn =\n \"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\";\nconst neutralBtn =\n \"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\";\nconst chipClass =\n \"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\";\n\n// ---------------------------------------------------------------------------\n// Recommendation card\n// ---------------------------------------------------------------------------\ninterface RecommendationCardProps {\n rec: RecItem;\n busy: boolean;\n onAccept: (id: string) => void;\n onSnooze: (id: string) => void;\n onDismiss: (id: string) => void;\n}\n\nfunction RecommendationCard({\n rec,\n busy,\n onAccept,\n onSnooze,\n onDismiss,\n}: RecommendationCardProps) {\n const Icon = recIcon(rec.domain);\n const actionable = isActionable(rec.state);\n const badge = stateBadge(rec.state);\n\n return (\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-4 sm:p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {humaniseKind(rec.kind)}\n </p>\n <h3 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {rec.title}\n </h3>\n </div>\n </div>\n {rec.confidence != null && (\n <StatusBadge\n variant=\"info\"\n label={`${asPercent(rec.confidence)} confident`}\n className=\"shrink-0\"\n />\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary\">{rec.rationale}</p>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {rec.domain && (\n <span className={chipClass}>{lifeDomainLabel(rec.domain)}</span>\n )}\n {rec.valueAlignment != null && (\n <span className={chipClass}>\n <ShieldCheck className=\"h-3 w-3\" />\n Aligns with your values · {asPercent(rec.valueAlignment)}\n </span>\n )}\n {rec.state === REC_STATE.Snoozed && rec.snoozeUntil && (\n <span className={chipClass}>\n <Clock className=\"h-3 w-3\" />\n Snoozed until {formatDate(rec.snoozeUntil)}\n </span>\n )}\n </div>\n\n {actionable ? (\n <div className=\"flex flex-wrap items-center gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={() => onAccept(rec.id)}\n disabled={busy}\n className={primaryBtn}\n >\n <Check className=\"h-4 w-4\" />\n Accept\n </button>\n <button\n type=\"button\"\n onClick={() => onSnooze(rec.id)}\n disabled={busy}\n className={neutralBtn}\n >\n <Clock className=\"h-4 w-4\" />\n Snooze\n </button>\n <button\n type=\"button\"\n onClick={() => onDismiss(rec.id)}\n disabled={busy}\n className={neutralBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 pt-1\">\n <StatusBadge variant={badge.variant} label={badge.label} />\n <span className=\"text-xs text-text-secondary\">\n {formatDate(rec.createdAt)}\n </span>\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\nfunction segmentEmptyCopy(segment: SegmentKey): {\n title: string;\n description: string;\n} {\n switch (segment) {\n case REC_STATE.Snoozed:\n return {\n title: \"Nothing snoozed\",\n description:\n \"Suggestions you snooze for later will wait here quietly until they're worth another look.\",\n };\n case REC_STATE.Accepted:\n return {\n title: \"Nothing accepted yet\",\n description:\n \"When you accept a suggestion it moves here, so you can see the changes you chose to make.\",\n };\n case REC_STATE.Dismissed:\n return {\n title: \"Nothing dismissed\",\n description:\n \"Suggestions you dismiss are kept here — TimeCampus learns from them and won't keep nagging.\",\n };\n default:\n return {\n title: \"You're all caught up\",\n description:\n \"No new suggestions right now. Generate again after a few more days of tracked time.\",\n };\n }\n}\n\nexport function RecommendationsPage() {\n const { workspaceId } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const [segment, setSegment] = useState<SegmentKey>(REC_STATE.New);\n const [busyId, setBusyId] = useState<string | null>(null);\n\n const { data, loading, error, refetch } = useMyRecommendationsQuery({\n variables: { state: null },\n skip: !workspaceId,\n });\n\n const [generate, { loading: generating }] =\n useGenerateMyRecommendationsMutation();\n const [acceptRec] = useAcceptRecommendationMutation();\n const [dismissRec] = useDismissRecommendationMutation();\n const [snoozeRec] = useSnoozeRecommendationMutation();\n\n const recommendations: RecItem[] = useMemo(\n () => data?.myRecommendations ?? [],\n [data?.myRecommendations],\n );\n\n const counts = useMemo(() => {\n const map: Record<string, number> = {};\n for (const rec of recommendations) {\n map[rec.state] = (map[rec.state] ?? 0) + 1;\n }\n return map;\n }, [recommendations]);\n\n const visible = useMemo(\n () => recommendations.filter((rec) => rec.state === segment),\n [recommendations, segment],\n );\n\n const newCount = counts[REC_STATE.New] ?? 0;\n const acceptedCount = counts[REC_STATE.Accepted] ?? 0;\n const snoozedCount = counts[REC_STATE.Snoozed] ?? 0;\n\n const handleGenerate = async () => {\n try {\n const res = await generate();\n const generated = res.data?.generateMyRecommendations ?? [];\n addToast({\n title:\n generated.length > 0\n ? `${generated.length} suggestion${generated.length === 1 ? \"\" : \"s\"} ready`\n : \"No new suggestions\",\n description:\n generated.length > 0\n ? \"Fresh, values-aware ideas are in your New feed.\"\n : \"You're all caught up — nothing new to suggest right now.\",\n variant: generated.length > 0 ? \"success\" : \"info\",\n });\n setSegment(REC_STATE.New);\n await refetch();\n } catch {\n addToast({\n title: \"Couldn't generate recommendations\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleAccept = async (id: string) => {\n setBusyId(id);\n try {\n await acceptRec({ variables: { id } });\n addToast({ title: \"Accepted — nice\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't accept that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleSnooze = async (id: string) => {\n setBusyId(id);\n try {\n await snoozeRec({ variables: { id, until: snoozeUntilIso() } });\n addToast({\n title: \"Snoozed for a week\",\n description: \"It'll resurface later — no nagging in between.\",\n variant: \"info\",\n });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't snooze that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleDismiss = async (id: string) => {\n setBusyId(id);\n try {\n await dismissRec({ variables: { id } });\n addToast({ title: \"Dismissed\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't dismiss that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const generateButton = (\n <button\n type=\"button\"\n onClick={handleGenerate}\n disabled={generating || !workspaceId}\n className={primaryBtn}\n >\n <Sparkles className=\"h-4 w-4\" />\n {generating ? \"Generating…\" : \"Generate recommendations\"}\n </button>\n );\n\n const purpose =\n \"Gentle, values-aware nudges built from your real timeline — protect a focus block, \" +\n \"reclaim a commute, guard family time. Accept the ones that fit, snooze the rest, and \" +\n \"dismiss what doesn't. TimeCampus never optimises for more output — only for time that \" +\n \"matches what you value.\";\n\n const footerNote = (\n <GlassCard treatment=\"insight\" className=\"mt-6 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg text-status-info-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Never optimises for more output.\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Every suggestion protects something you value — rest, focus,\n relationships — not your throughput. Snooze or dismiss freely;\n TimeCampus takes the hint and won't keep nagging.\n </p>\n </div>\n </div>\n </GlassCard>\n );\n\n if (!workspaceId || loading) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Couldn't load your recommendations right now. Please try again in\n a moment.\n </div>\n </PageLayout>\n );\n }\n\n const hasAny = recommendations.length > 0;\n const emptyCopy = segmentEmptyCopy(segment);\n\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n {!hasAny ? (\n <>\n <EmptyState\n illustration=\"empty-data\"\n title=\"No recommendations yet\"\n description=\"TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.\"\n action={generateButton}\n />\n {footerNote}\n </>\n ) : (\n <div className=\"space-y-6\">\n {/* Summary of where each suggestion stands — from the query only. */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"New for you\"\n value={String(newCount)}\n icon={<Sparkles className=\"h-5 w-5\" />}\n description=\"Awaiting your call\"\n />\n <StatCard\n title=\"Accepted\"\n value={String(acceptedCount)}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n description=\"Changes you chose to make\"\n />\n <StatCard\n title=\"Snoozed\"\n value={String(snoozedCount)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Waiting for a better moment\"\n />\n </div>\n\n {/* Segmented control by state. */}\n <div className=\"overflow-x-auto\">\n <div\n className=\"inline-flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\"\n role=\"tablist\"\n aria-label=\"Filter recommendations by state\"\n >\n {SEGMENTS.map((seg) => {\n const active = seg.key === segment;\n const count = counts[seg.key] ?? 0;\n return (\n <button\n key={seg.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n onClick={() => setSegment(seg.key)}\n className={\n active\n ? \"flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm\"\n : \"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n }\n >\n {seg.label}\n <span\n className={\n active\n ? \"rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary\"\n : \"rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary\"\n }\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* The feed for the active segment. */}\n {visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyCopy.title}\n description={emptyCopy.description}\n action={segment === REC_STATE.New ? generateButton : undefined}\n />\n ) : (\n <div className=\"space-y-4\">\n {visible.map((rec) => (\n <RecommendationCard\n key={rec.id}\n rec={rec}\n busy={busyId === rec.id}\n onAccept={handleAccept}\n onSnooze={handleSnooze}\n onDismiss={handleDismiss}\n />\n ))}\n </div>\n )}\n\n {footerNote}\n </div>\n )}\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RecommendationsPage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/RecommendationsPage.tsx"],"sourcesContent":["import { useMemo, useState, type ComponentType } from \"react\";\nimport {\n Sparkles,\n Check,\n CheckCircle2,\n ChevronDown,\n X,\n Clock,\n BellOff,\n CalendarClock,\n Microscope,\n ShieldCheck,\n Lightbulb,\n Briefcase,\n Moon,\n HeartPulse,\n Users,\n GraduationCap,\n Coffee,\n Bus,\n Hourglass,\n Home,\n Heart,\n Handshake,\n Wallet,\n Circle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n Modal,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { cn } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n useMyRecommendationsQuery,\n useMyRecommendationPreferencesQuery,\n useGenerateMyRecommendationsMutation,\n useAcceptRecommendationMutation,\n useDismissRecommendationMutation,\n useSnoozeRecommendationMutation,\n useUpdateMyRecommendationPreferencesMutation,\n type RecommendationFieldsFragment,\n type RecommendationPreferenceFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { lifeDomainLabel } from \"./domains\";\nimport {\n formatEvidenceRef,\n formatHourOfDay,\n humaniseToken,\n parseEvidenceRefs,\n parseSuggestedAction,\n} from \"./recommendationEvidence\";\n\ntype RecItem = RecommendationFieldsFragment;\ntype PrefItem = RecommendationPreferenceFieldsFragment;\n\n// ---------------------------------------------------------------------------\n// Recommendation states are stored as free strings on the backend\n// (NEW / ACCEPTED / DISMISSED / SNOOZED / EXPIRED — see the svc schema). We\n// segment the feed by them; unknown states simply don't match a tab.\n// ---------------------------------------------------------------------------\nconst REC_STATE = {\n New: \"NEW\",\n Snoozed: \"SNOOZED\",\n Accepted: \"ACCEPTED\",\n Dismissed: \"DISMISSED\",\n Expired: \"EXPIRED\",\n} as const;\n\nconst SEGMENTS = [\n { key: REC_STATE.New, label: \"New\" },\n { key: REC_STATE.Snoozed, label: \"Snoozed\" },\n { key: REC_STATE.Accepted, label: \"Accepted\" },\n { key: REC_STATE.Dismissed, label: \"Dismissed\" },\n] as const;\n\ntype SegmentKey = (typeof SEGMENTS)[number][\"key\"];\n\n/** A NEW or SNOOZED recommendation is still actionable (accept / snooze / dismiss). */\nfunction isActionable(state: string): boolean {\n return state === REC_STATE.New || state === REC_STATE.Snoozed;\n}\n\n/** Snooze a week out — long enough to be non-nagging, short enough to resurface. */\nfunction snoozeUntilIso(): string {\n return new Date(Date.now() + 7 * 86_400_000).toISOString();\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\nconst asPercent = (n: number) => `${Math.round(clamp01(n) * 100)}%`;\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n });\n}\n\n/** Hours of the day offered by the quiet-hours pickers. */\nconst HOUR_OPTIONS = Array.from({ length: 24 }, (_, hour) => hour);\n\nconst DOMAIN_ICON: Record<\n TimeCampusLifeDomain,\n ComponentType<{ className?: string }>\n> = {\n [TimeCampusLifeDomain.Work]: Briefcase,\n [TimeCampusLifeDomain.Sleep]: Moon,\n [TimeCampusLifeDomain.Health]: HeartPulse,\n [TimeCampusLifeDomain.Family]: Users,\n [TimeCampusLifeDomain.Learning]: GraduationCap,\n [TimeCampusLifeDomain.Leisure]: Coffee,\n [TimeCampusLifeDomain.Commute]: Bus,\n [TimeCampusLifeDomain.Waiting]: Hourglass,\n [TimeCampusLifeDomain.Chores]: Home,\n [TimeCampusLifeDomain.SelfCare]: Heart,\n [TimeCampusLifeDomain.Social]: Handshake,\n [TimeCampusLifeDomain.FinanceAdmin]: Wallet,\n [TimeCampusLifeDomain.Reflection]: Sparkles,\n [TimeCampusLifeDomain.Other]: Circle,\n};\n\nfunction recIcon(\n domain?: TimeCampusLifeDomain | null,\n): ComponentType<{ className?: string }> {\n if (!domain) return Lightbulb;\n return DOMAIN_ICON[domain] ?? Lightbulb;\n}\n\ntype BadgeTone = \"success\" | \"warning\" | \"muted\" | \"info\";\n\nfunction stateBadge(state: string): { variant: BadgeTone; label: string } {\n switch (state) {\n case REC_STATE.Accepted:\n return { variant: \"success\", label: \"Accepted\" };\n case REC_STATE.Snoozed:\n return { variant: \"warning\", label: \"Snoozed\" };\n case REC_STATE.Dismissed:\n return { variant: \"muted\", label: \"Dismissed\" };\n case REC_STATE.Expired:\n return { variant: \"muted\", label: \"Expired\" };\n default:\n return { variant: \"info\", label: \"New\" };\n }\n}\n\nconst primaryBtn =\n \"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\";\nconst neutralBtn =\n \"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\";\nconst chipClass =\n \"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\";\n\n/**\n * Local, token-styled switch (the shared UI kit isn't imported here). The knob\n * translates only on the checked state — never on hover — per the repo's\n * no-hover-movement mandate.\n */\nfunction ToggleSwitch({\n checked,\n onChange,\n label,\n disabled,\n}: {\n checked: boolean;\n onChange: (next: boolean) => void;\n label: string;\n disabled?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n className={cn(\n \"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50\",\n checked\n ? \"border-transparent bg-action-primary-bg\"\n : \"border-border-subtle bg-bg-sunken\",\n )}\n >\n <span\n className={cn(\n \"inline-block h-3.5 w-3.5 transform rounded-full shadow-sm transition-transform\",\n checked\n ? \"translate-x-4 bg-action-primary-text\"\n : \"translate-x-0.5 bg-text-secondary\",\n )}\n />\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Recommendation card\n// ---------------------------------------------------------------------------\ninterface RecommendationCardProps {\n rec: RecItem;\n busy: boolean;\n muted: boolean;\n onAccept: (id: string) => void;\n onSnooze: (id: string) => void;\n onDismiss: (rec: RecItem) => void;\n onMuteKind: (kind: string) => void;\n}\n\nfunction RecommendationCard({\n rec,\n busy,\n muted,\n onAccept,\n onSnooze,\n onDismiss,\n onMuteKind,\n}: RecommendationCardProps) {\n const Icon = recIcon(rec.domain);\n const actionable = isActionable(rec.state);\n const badge = stateBadge(rec.state);\n const [evidenceOpen, setEvidenceOpen] = useState(false);\n\n // Both come off this card's own query result — the JSON the generator wrote\n // when it raised the recommendation. Nothing is derived or estimated here.\n const evidence = parseEvidenceRefs(rec.evidenceRefs);\n const action = parseSuggestedAction(rec.suggestedAction);\n const evidenceId = `rec-evidence-${rec.id}`;\n\n return (\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-4 sm:p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {humaniseToken(rec.kind)}\n </p>\n <h3 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {rec.title}\n </h3>\n </div>\n </div>\n {rec.confidence != null && (\n <StatusBadge\n variant=\"info\"\n label={`${asPercent(rec.confidence)} confident`}\n className=\"shrink-0\"\n />\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary\">{rec.rationale}</p>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {rec.domain && (\n <span className={chipClass}>{lifeDomainLabel(rec.domain)}</span>\n )}\n {rec.valueAlignment != null && (\n <span className={chipClass}>\n <ShieldCheck className=\"h-3 w-3\" />\n Aligns with your values · {asPercent(rec.valueAlignment)}\n </span>\n )}\n {rec.state === REC_STATE.Snoozed && rec.snoozeUntil && (\n <span className={chipClass}>\n <Clock className=\"h-3 w-3\" />\n Snoozed until {formatDate(rec.snoozeUntil)}\n </span>\n )}\n {actionable && rec.expiresAt && (\n <span className={chipClass}>\n <CalendarClock className=\"h-3 w-3\" />\n Expires {formatDate(rec.expiresAt)}\n </span>\n )}\n </div>\n\n {/* The concrete next step the generator attached, if any. */}\n {action && (\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Suggested action\n </p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {humaniseToken(action.type)}\n </p>\n {action.details.length > 0 && (\n <dl className=\"mt-1.5 flex flex-wrap gap-x-4 gap-y-1\">\n {action.details.map((detail) => (\n <div key={detail.key} className=\"flex items-center gap-1.5\">\n <dt className=\"text-xs text-text-secondary\">{detail.key}</dt>\n <dd className=\"text-xs font-medium tabular-nums text-text-primary\">\n {detail.value}\n </dd>\n </div>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {/* Why this was raised — the exact metrics the generator measured. */}\n {evidence.length > 0 && (\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40\">\n <button\n type=\"button\"\n onClick={() => setEvidenceOpen((open) => !open)}\n aria-expanded={evidenceOpen}\n aria-controls={evidenceId}\n className=\"flex w-full items-center justify-between gap-2 px-3 py-2 text-left\"\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n <Microscope className=\"h-3.5 w-3.5 shrink-0 text-text-secondary\" />\n <span className=\"truncate text-xs text-text-secondary\">\n Evidence: {formatEvidenceRef(evidence[0])}\n {evidence.length > 1 ? ` · +${evidence.length - 1} more` : \"\"}\n </span>\n </span>\n <ChevronDown\n className={cn(\n \"h-4 w-4 shrink-0 text-text-secondary transition-transform\",\n evidenceOpen && \"rotate-180\",\n )}\n />\n </button>\n {evidenceOpen && (\n <dl\n id={evidenceId}\n className=\"space-y-1.5 border-t border-border-seam px-3 py-2.5\"\n >\n {evidence.map((ref, index) => (\n <div\n key={`${ref.metric}-${ref.period ?? ref.domain ?? index}`}\n className=\"flex items-baseline justify-between gap-3\"\n >\n <dt className=\"text-xs text-text-secondary\">\n {formatEvidenceRef(ref).split(\": \")[0]}\n </dt>\n <dd className=\"text-xs font-medium tabular-nums text-text-primary\">\n {formatEvidenceRef(ref).split(\": \").slice(1).join(\": \")}\n </dd>\n </div>\n ))}\n <p className=\"pt-1 text-xs text-text-tertiary\">\n Measured from your own tracked week. Nothing is applied until\n you accept.\n </p>\n </dl>\n )}\n </div>\n )}\n\n {actionable ? (\n <div className=\"flex flex-wrap items-center gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={() => onAccept(rec.id)}\n disabled={busy}\n className={primaryBtn}\n >\n <Check className=\"h-4 w-4\" />\n Accept\n </button>\n <button\n type=\"button\"\n onClick={() => onSnooze(rec.id)}\n disabled={busy}\n className={neutralBtn}\n >\n <Clock className=\"h-4 w-4\" />\n Snooze\n </button>\n <button\n type=\"button\"\n onClick={() => onDismiss(rec)}\n disabled={busy}\n className={neutralBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n <button\n type=\"button\"\n onClick={() => onMuteKind(rec.kind)}\n disabled={busy || muted}\n className={neutralBtn}\n title={\n muted\n ? `${humaniseToken(rec.kind)} suggestions are already muted`\n : `Stop generating ${humaniseToken(rec.kind)} suggestions`\n }\n >\n <BellOff className=\"h-4 w-4\" />\n {muted ? \"Muted\" : \"Mute this kind\"}\n </button>\n </div>\n ) : (\n <div className=\"flex flex-col gap-2 pt-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <StatusBadge variant={badge.variant} label={badge.label} />\n <span className=\"text-xs text-text-secondary\">\n {formatDate(rec.createdAt)}\n </span>\n </div>\n {rec.state === REC_STATE.Dismissed && rec.dismissReason && (\n <p className=\"text-xs text-text-secondary\">\n Reason given: {rec.dismissReason}\n </p>\n )}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Guardrails — the preferences that decide what may be suggested at all.\n// Every value here is read from `myRecommendationPreferences`; the controls\n// write back through `updateMyRecommendationPreferences`.\n// ---------------------------------------------------------------------------\ninterface GuardrailsPanelProps {\n prefs: PrefItem | undefined;\n loading: boolean;\n saving: boolean;\n onToggleOutputGuard: (next: boolean) => void;\n onQuietHoursChange: (start: number | null, end: number | null) => void;\n onUnmuteKind: (kind: string) => void;\n}\n\nfunction GuardrailsPanel({\n prefs,\n loading,\n saving,\n onToggleOutputGuard,\n onQuietHoursChange,\n onUnmuteKind,\n}: GuardrailsPanelProps) {\n if (loading) {\n return <LoadingSkeleton variant=\"card\" count={1} />;\n }\n if (!prefs) return null;\n\n const quietStart = formatHourOfDay(prefs.quietHoursStart);\n const quietEnd = formatHourOfDay(prefs.quietHoursEnd);\n const quietSet = quietStart !== null && quietEnd !== null;\n\n const hourSelectClass =\n \"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 disabled:opacity-50\";\n\n return (\n <GlassCard treatment=\"glass\" className=\"p-4 sm:p-5\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">Guardrails</h3>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n What TimeCampus is allowed to suggest, and when.\n </p>\n\n <div className=\"mt-4 space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n Never suggest more work\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Output-increasing suggestions are filtered out before they reach\n you.\n </p>\n </div>\n <ToggleSwitch\n checked={prefs.neverOptimiseForOutput}\n disabled={saving}\n label=\"Never suggest more work\"\n onChange={onToggleOutputGuard}\n />\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Quiet hours</p>\n <span className=\"text-xs text-text-secondary\">\n {quietSet ? `${quietStart}–${quietEnd}` : \"Not set\"}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n No nudges are sent inside this window.\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <label className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n From\n <select\n className={hourSelectClass}\n disabled={saving}\n value={prefs.quietHoursStart ?? \"\"}\n onChange={(event) =>\n onQuietHoursChange(\n event.target.value === \"\"\n ? null\n : Number(event.target.value),\n prefs.quietHoursEnd ?? null,\n )\n }\n >\n <option value=\"\">—</option>\n {HOUR_OPTIONS.map((hour) => (\n <option key={hour} value={hour}>\n {formatHourOfDay(hour)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n To\n <select\n className={hourSelectClass}\n disabled={saving}\n value={prefs.quietHoursEnd ?? \"\"}\n onChange={(event) =>\n onQuietHoursChange(\n prefs.quietHoursStart ?? null,\n event.target.value === \"\"\n ? null\n : Number(event.target.value),\n )\n }\n >\n <option value=\"\">—</option>\n {HOUR_OPTIONS.map((hour) => (\n <option key={hour} value={hour}>\n {formatHourOfDay(hour)}\n </option>\n ))}\n </select>\n </label>\n {quietSet && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => onQuietHoursChange(null, null)}\n className=\"text-xs font-medium text-text-secondary underline-offset-2 hover:text-text-primary hover:underline disabled:opacity-50\"\n >\n Clear\n </button>\n )}\n </div>\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Muted kinds</p>\n <span className={chipClass}>{prefs.mutedKinds.length}</span>\n </div>\n {prefs.mutedKinds.length === 0 ? (\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Nothing muted — mute a kind from any suggestion to stop it coming\n back.\n </p>\n ) : (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {prefs.mutedKinds.map((kind) => (\n <button\n key={kind}\n type=\"button\"\n disabled={saving}\n onClick={() => onUnmuteKind(kind)}\n className={cn(\n chipClass,\n \"hover:text-text-primary disabled:opacity-50\",\n )}\n title={`Unmute ${humaniseToken(kind)} suggestions`}\n >\n {humaniseToken(kind)}\n <X className=\"h-3 w-3\" />\n </button>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Cadence</p>\n <span className=\"text-xs text-text-secondary\">\n {humaniseToken(prefs.cadence)}\n </span>\n </div>\n {prefs.optedInDomains.length > 0 && (\n <>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n Opted-in domains\n </p>\n <div className=\"mt-1.5 flex flex-wrap gap-1.5\">\n {prefs.optedInDomains.map((domain) => (\n <span key={domain} className={chipClass}>\n {lifeDomainLabel(domain)}\n </span>\n ))}\n </div>\n </>\n )}\n </div>\n </div>\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\nfunction segmentEmptyCopy(segment: SegmentKey): {\n title: string;\n description: string;\n} {\n switch (segment) {\n case REC_STATE.Snoozed:\n return {\n title: \"Nothing snoozed\",\n description:\n \"Suggestions you snooze for later will wait here quietly until they're worth another look.\",\n };\n case REC_STATE.Accepted:\n return {\n title: \"Nothing accepted yet\",\n description:\n \"When you accept a suggestion it moves here, so you can see the changes you chose to make.\",\n };\n case REC_STATE.Dismissed:\n return {\n title: \"Nothing dismissed\",\n description:\n \"Suggestions you dismiss are kept here — TimeCampus learns from them and won't keep nagging.\",\n };\n default:\n return {\n title: \"You're all caught up\",\n description:\n \"No new suggestions right now. Generate again after a few more days of tracked time.\",\n };\n }\n}\n\nexport function RecommendationsPage() {\n const { workspaceId } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const [segment, setSegment] = useState<SegmentKey>(REC_STATE.New);\n const [busyId, setBusyId] = useState<string | null>(null);\n const [dismissTarget, setDismissTarget] = useState<RecItem | null>(null);\n const [dismissReason, setDismissReason] = useState(\"\");\n\n const { data, loading, error, refetch } = useMyRecommendationsQuery({\n variables: { state: null },\n skip: !workspaceId,\n });\n\n const {\n data: prefsData,\n loading: prefsLoading,\n refetch: refetchPrefs,\n } = useMyRecommendationPreferencesQuery({ skip: !workspaceId });\n\n const [generate, { loading: generating }] =\n useGenerateMyRecommendationsMutation();\n const [acceptRec] = useAcceptRecommendationMutation();\n const [dismissRec] = useDismissRecommendationMutation();\n const [snoozeRec] = useSnoozeRecommendationMutation();\n const [updatePrefs, { loading: savingPrefs }] =\n useUpdateMyRecommendationPreferencesMutation();\n\n const prefs = prefsData?.myRecommendationPreferences;\n const mutedKinds = useMemo<Set<string>>(\n () => new Set<string>(prefs?.mutedKinds ?? []),\n [prefs?.mutedKinds],\n );\n\n const recommendations: RecItem[] = useMemo(\n () => data?.myRecommendations ?? [],\n [data?.myRecommendations],\n );\n\n const counts = useMemo(() => {\n const map: Record<string, number> = {};\n for (const rec of recommendations) {\n map[rec.state] = (map[rec.state] ?? 0) + 1;\n }\n return map;\n }, [recommendations]);\n\n const visible = useMemo(\n () => recommendations.filter((rec) => rec.state === segment),\n [recommendations, segment],\n );\n\n const newCount = counts[REC_STATE.New] ?? 0;\n const acceptedCount = counts[REC_STATE.Accepted] ?? 0;\n const snoozedCount = counts[REC_STATE.Snoozed] ?? 0;\n\n const handleGenerate = async () => {\n try {\n const res = await generate();\n const generated = res.data?.generateMyRecommendations ?? [];\n addToast({\n title:\n generated.length > 0\n ? `${generated.length} suggestion${generated.length === 1 ? \"\" : \"s\"} ready`\n : \"No new suggestions\",\n description:\n generated.length > 0\n ? \"Fresh, values-aware ideas are in your New feed.\"\n : \"You're all caught up — nothing new to suggest right now.\",\n variant: generated.length > 0 ? \"success\" : \"info\",\n });\n setSegment(REC_STATE.New);\n await refetch();\n } catch {\n addToast({\n title: \"Couldn't generate recommendations\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleAccept = async (id: string) => {\n setBusyId(id);\n try {\n await acceptRec({ variables: { id } });\n addToast({ title: \"Accepted — nice\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't accept that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleSnooze = async (id: string) => {\n setBusyId(id);\n try {\n await snoozeRec({ variables: { id, until: snoozeUntilIso() } });\n addToast({\n title: \"Snoozed for a week\",\n description: \"It'll resurface later — no nagging in between.\",\n variant: \"info\",\n });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't snooze that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const requestDismiss = (rec: RecItem) => {\n setDismissReason(\"\");\n setDismissTarget(rec);\n };\n\n /**\n * Dismissing asks for an optional reason first — `dismissRecommendation`\n * takes one and the card reads it back, so the note the user leaves is the\n * note they later see.\n */\n const handleConfirmDismiss = async () => {\n const target = dismissTarget;\n if (!target) return;\n const reason = dismissReason.trim();\n setBusyId(target.id);\n try {\n await dismissRec({\n variables: { id: target.id, reason: reason.length > 0 ? reason : null },\n });\n addToast({ title: \"Dismissed\", variant: \"success\" });\n setDismissTarget(null);\n setDismissReason(\"\");\n await refetch();\n } catch {\n addToast({ title: \"Couldn't dismiss that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n /** One write path for every guardrail control, so failures read the same. */\n const savePrefs = async (\n input: {\n neverOptimiseForOutput?: boolean;\n quietHoursStart?: number | null;\n quietHoursEnd?: number | null;\n mutedKinds?: string[];\n },\n successTitle: string,\n ) => {\n try {\n await updatePrefs({ variables: { input } });\n addToast({ title: successTitle, variant: \"success\" });\n await refetchPrefs();\n } catch {\n addToast({\n title: \"Couldn't save that guardrail\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleToggleOutputGuard = (next: boolean) =>\n void savePrefs(\n { neverOptimiseForOutput: next },\n next\n ? \"Output-increasing suggestions are off\"\n : \"Output-increasing suggestions allowed\",\n );\n\n const handleQuietHoursChange = (start: number | null, end: number | null) =>\n void savePrefs(\n { quietHoursStart: start, quietHoursEnd: end },\n start === null && end === null\n ? \"Quiet hours cleared\"\n : \"Quiet hours saved\",\n );\n\n const handleMuteKind = (kind: string) => {\n if (mutedKinds.has(kind)) return;\n void savePrefs(\n { mutedKinds: [...mutedKinds, kind] },\n `${humaniseToken(kind)} suggestions muted`,\n );\n };\n\n const handleUnmuteKind = (kind: string) =>\n void savePrefs(\n { mutedKinds: [...mutedKinds].filter((entry) => entry !== kind) },\n `${humaniseToken(kind)} suggestions unmuted`,\n );\n\n const guardrailsPanel = (\n <GuardrailsPanel\n prefs={prefs}\n loading={prefsLoading}\n saving={savingPrefs}\n onToggleOutputGuard={handleToggleOutputGuard}\n onQuietHoursChange={handleQuietHoursChange}\n onUnmuteKind={handleUnmuteKind}\n />\n );\n\n const generateButton = (\n <button\n type=\"button\"\n onClick={handleGenerate}\n disabled={generating || !workspaceId}\n className={primaryBtn}\n >\n <Sparkles className=\"h-4 w-4\" />\n {generating ? \"Generating…\" : \"Generate recommendations\"}\n </button>\n );\n\n const purpose =\n \"Gentle, values-aware nudges built from your real timeline — protect a focus block, \" +\n \"reclaim a commute, guard family time. Accept the ones that fit, snooze the rest, and \" +\n \"dismiss what doesn't. TimeCampus never optimises for more output — only for time that \" +\n \"matches what you value.\";\n\n const footerNote = (\n <GlassCard treatment=\"insight\" className=\"mt-6 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg text-status-info-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Never optimises for more output.\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Every suggestion protects something you value — rest, focus,\n relationships — not your throughput. Snooze or dismiss freely;\n TimeCampus takes the hint and won't keep nagging.\n </p>\n </div>\n </div>\n </GlassCard>\n );\n\n if (!workspaceId || loading) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Couldn't load your recommendations right now. Please try again in\n a moment.\n </div>\n </PageLayout>\n );\n }\n\n const hasAny = recommendations.length > 0;\n const emptyCopy = segmentEmptyCopy(segment);\n\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n {!hasAny ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <div>\n <EmptyState\n illustration=\"empty-data\"\n title=\"No recommendations yet\"\n description=\"TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.\"\n action={generateButton}\n />\n {footerNote}\n </div>\n <div>{guardrailsPanel}</div>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <div className=\"min-w-0 space-y-6\">\n {/* Summary of where each suggestion stands — from the query only. */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"New for you\"\n value={String(newCount)}\n icon={<Sparkles className=\"h-5 w-5\" />}\n description=\"Awaiting your call\"\n />\n <StatCard\n title=\"Accepted\"\n value={String(acceptedCount)}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n description=\"Changes you chose to make\"\n />\n <StatCard\n title=\"Snoozed\"\n value={String(snoozedCount)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Waiting for a better moment\"\n />\n </div>\n\n {/* Segmented control by state. */}\n <div className=\"overflow-x-auto\">\n <div\n className=\"inline-flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\"\n role=\"tablist\"\n aria-label=\"Filter recommendations by state\"\n >\n {SEGMENTS.map((seg) => {\n const active = seg.key === segment;\n const count = counts[seg.key] ?? 0;\n return (\n <button\n key={seg.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n onClick={() => setSegment(seg.key)}\n className={\n active\n ? \"flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm\"\n : \"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n }\n >\n {seg.label}\n <span\n className={\n active\n ? \"rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary\"\n : \"rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary\"\n }\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* The feed for the active segment. */}\n {visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyCopy.title}\n description={emptyCopy.description}\n action={segment === REC_STATE.New ? generateButton : undefined}\n />\n ) : (\n <div className=\"space-y-4\">\n {visible.map((rec) => (\n <RecommendationCard\n key={rec.id}\n rec={rec}\n busy={busyId === rec.id || savingPrefs}\n muted={mutedKinds.has(rec.kind)}\n onAccept={handleAccept}\n onSnooze={handleSnooze}\n onDismiss={requestDismiss}\n onMuteKind={handleMuteKind}\n />\n ))}\n </div>\n )}\n\n {footerNote}\n </div>\n <div>{guardrailsPanel}</div>\n </div>\n )}\n\n {/* Dismissing captures the optional reason the mutation accepts, so the\n note is there when the card is read back under Dismissed. */}\n <Modal\n open={dismissTarget !== null}\n onClose={() => setDismissTarget(null)}\n title=\"Dismiss this suggestion\"\n description={dismissTarget?.title}\n size=\"sm\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setDismissTarget(null)}\n className={neutralBtn}\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleConfirmDismiss()}\n disabled={busyId !== null}\n className={primaryBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n </>\n }\n >\n <label\n htmlFor=\"rec-dismiss-reason\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Why isn't this one for you?{\" \"}\n <span className=\"font-normal text-text-secondary\">(optional)</span>\n </label>\n <textarea\n id=\"rec-dismiss-reason\"\n rows={3}\n value={dismissReason}\n onChange={(event) => setDismissReason(event.target.value)}\n placeholder=\"e.g. already handled, not the right week\"\n className=\"mt-1.5 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n Saved with the suggestion so you can see why you passed on it later.\n To stop this kind of suggestion entirely, use Mute this kind.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommendation evidence / suggested-action normalisers.
|
|
3
|
+
* =========================================================================
|
|
4
|
+
*
|
|
5
|
+
* `TimeCampusRecommendation.evidenceRefs` and `.suggestedAction` are `JSON`
|
|
6
|
+
* scalars, so codegen types them as `Record<string, unknown>` even though the
|
|
7
|
+
* generator writes an *array* of metric refs for the former and a single
|
|
8
|
+
* `{ type, ...qualifiers }` object for the latter. Everything here is a
|
|
9
|
+
* defensive read of that shape: anything that doesn't parse is dropped rather
|
|
10
|
+
* than guessed at, so the UI can only ever show a number the service actually
|
|
11
|
+
* sent.
|
|
12
|
+
*
|
|
13
|
+
* @module timecampus
|
|
14
|
+
*/
|
|
15
|
+
/** One measured fact behind a recommendation, as written by the generator. */
|
|
16
|
+
export interface EvidenceRef {
|
|
17
|
+
/** Metric identifier, e.g. `focusRatio` or `longestFocusBlockSeconds`. */
|
|
18
|
+
metric: string;
|
|
19
|
+
value: number;
|
|
20
|
+
/** Comparison window, when the metric is a week-over-week pair. */
|
|
21
|
+
period?: string;
|
|
22
|
+
/** Life domain the metric belongs to, when domain-scoped. */
|
|
23
|
+
domain?: string;
|
|
24
|
+
}
|
|
25
|
+
/** A suggested next step: a machine `type` plus any qualifying values. */
|
|
26
|
+
export interface SuggestedAction {
|
|
27
|
+
type: string;
|
|
28
|
+
details: Array<{
|
|
29
|
+
key: string;
|
|
30
|
+
value: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Turn `SCREAMING_SNAKE` / `camelCase` identifiers into readable text without
|
|
35
|
+
* inventing copy — the token itself is still the source of truth.
|
|
36
|
+
*/
|
|
37
|
+
export declare function humaniseToken(token: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Read the evidence array off a recommendation. Entries without a usable
|
|
40
|
+
* `metric` + finite numeric `value` are skipped — a half-parsed fact is worse
|
|
41
|
+
* than no fact.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseEvidenceRefs(raw: unknown): EvidenceRef[];
|
|
44
|
+
/** Format a metric value in the unit the service actually stores it in. */
|
|
45
|
+
export declare function formatEvidenceValue(metric: string, value: number): string;
|
|
46
|
+
/** A single evidence line, e.g. `Focus ratio (prior): 58%`. */
|
|
47
|
+
export declare function formatEvidenceRef(ref: EvidenceRef): string;
|
|
48
|
+
/**
|
|
49
|
+
* Read the suggested action off a recommendation. Returns `null` unless the
|
|
50
|
+
* payload carries a `type`; any other primitive keys become display details.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseSuggestedAction(raw: unknown): SuggestedAction | null;
|
|
53
|
+
/** `22:00` from the integer hour the service stores; `null` when unset. */
|
|
54
|
+
export declare function formatHourOfDay(hour: number | null | undefined): string | null;
|
|
55
|
+
//# sourceMappingURL=recommendationEvidence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommendationEvidence.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/lifetime/recommendationEvidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAmBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,EAAE,CAsB7D;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAczE;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAQ1D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAgBzE;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,MAAM,GAAG,IAAI,CAIf"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/timecampus/pages/lifetime/recommendationEvidence.ts
|
|
2
|
+
var e = /* @__PURE__ */ new Set([
|
|
3
|
+
"focusRatio",
|
|
4
|
+
"actualShare",
|
|
5
|
+
"desiredShare",
|
|
6
|
+
"gap",
|
|
7
|
+
"timeWellSpentScore"
|
|
8
|
+
]);
|
|
9
|
+
function t(e) {
|
|
10
|
+
return typeof e == "object" && !!e && !Array.isArray(e);
|
|
11
|
+
}
|
|
12
|
+
function n(e) {
|
|
13
|
+
return typeof e == "string" && e.length > 0 ? e : void 0;
|
|
14
|
+
}
|
|
15
|
+
function r(e) {
|
|
16
|
+
let t = e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
17
|
+
return t.length === 0 ? e : t.split(/\s+/).map((e) => e.toUpperCase() === e && e.length > 1 ? e.charAt(0) + e.slice(1).toLowerCase() : e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
|
|
18
|
+
}
|
|
19
|
+
function i(e) {
|
|
20
|
+
if (!Array.isArray(e)) return [];
|
|
21
|
+
let r = [];
|
|
22
|
+
for (let i of e) {
|
|
23
|
+
if (!t(i)) continue;
|
|
24
|
+
let e = n(i.metric), a = i.value;
|
|
25
|
+
e === void 0 || typeof a != "number" || !Number.isFinite(a) || r.push({
|
|
26
|
+
metric: e,
|
|
27
|
+
value: a,
|
|
28
|
+
period: n(i.period),
|
|
29
|
+
domain: n(i.domain)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return r;
|
|
33
|
+
}
|
|
34
|
+
function a(t, n) {
|
|
35
|
+
if (/seconds$/i.test(t)) {
|
|
36
|
+
let e = Math.max(0, Math.round(n)), t = Math.floor(e / 3600), r = Math.floor(e % 3600 / 60);
|
|
37
|
+
return t === 0 && r === 0 ? `${e}s` : t === 0 ? `${r}m` : r === 0 ? `${t}h` : `${t}h ${r}m`;
|
|
38
|
+
}
|
|
39
|
+
return e.has(t) ? `${Math.round(n * 100)}%` : Number.isInteger(n) ? String(n) : n.toFixed(2);
|
|
40
|
+
}
|
|
41
|
+
function o(e) {
|
|
42
|
+
let t = [e.period ? r(e.period).toLowerCase() : void 0, e.domain ? r(e.domain) : void 0].filter((e) => e !== void 0);
|
|
43
|
+
return `${r(e.metric)}${t.length > 0 ? ` (${t.join(", ")})` : ""}: ${a(e.metric, e.value)}`;
|
|
44
|
+
}
|
|
45
|
+
function s(e) {
|
|
46
|
+
if (!t(e)) return null;
|
|
47
|
+
let i = n(e.type);
|
|
48
|
+
if (i === void 0) return null;
|
|
49
|
+
let a = [];
|
|
50
|
+
for (let [t, n] of Object.entries(e)) t !== "type" && (typeof n == "string" && n.length > 0 ? a.push({
|
|
51
|
+
key: r(t),
|
|
52
|
+
value: r(n)
|
|
53
|
+
}) : typeof n == "number" && Number.isFinite(n) ? a.push({
|
|
54
|
+
key: r(t),
|
|
55
|
+
value: String(n)
|
|
56
|
+
}) : typeof n == "boolean" && a.push({
|
|
57
|
+
key: r(t),
|
|
58
|
+
value: n ? "Yes" : "No"
|
|
59
|
+
}));
|
|
60
|
+
return {
|
|
61
|
+
type: i,
|
|
62
|
+
details: a
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function c(e) {
|
|
66
|
+
if (e == null || !Number.isFinite(e)) return null;
|
|
67
|
+
let t = (Math.trunc(e) % 24 + 24) % 24;
|
|
68
|
+
return `${String(t).padStart(2, "0")}:00`;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { o as formatEvidenceRef, a as formatEvidenceValue, c as formatHourOfDay, r as humaniseToken, i as parseEvidenceRefs, s as parseSuggestedAction };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=recommendationEvidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommendationEvidence.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/recommendationEvidence.ts"],"sourcesContent":["/**\n * Recommendation evidence / suggested-action normalisers.\n * =========================================================================\n *\n * `TimeCampusRecommendation.evidenceRefs` and `.suggestedAction` are `JSON`\n * scalars, so codegen types them as `Record<string, unknown>` even though the\n * generator writes an *array* of metric refs for the former and a single\n * `{ type, ...qualifiers }` object for the latter. Everything here is a\n * defensive read of that shape: anything that doesn't parse is dropped rather\n * than guessed at, so the UI can only ever show a number the service actually\n * sent.\n *\n * @module timecampus\n */\n\n/** One measured fact behind a recommendation, as written by the generator. */\nexport interface EvidenceRef {\n /** Metric identifier, e.g. `focusRatio` or `longestFocusBlockSeconds`. */\n metric: string;\n value: number;\n /** Comparison window, when the metric is a week-over-week pair. */\n period?: string;\n /** Life domain the metric belongs to, when domain-scoped. */\n domain?: string;\n}\n\n/** A suggested next step: a machine `type` plus any qualifying values. */\nexport interface SuggestedAction {\n type: string;\n details: Array<{ key: string; value: string }>;\n}\n\n/** Metrics the service emits as a 0..1 ratio — rendered as a percentage. */\nconst RATIO_METRICS = new Set([\n \"focusRatio\",\n \"actualShare\",\n \"desiredShare\",\n \"gap\",\n \"timeWellSpentScore\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Turn `SCREAMING_SNAKE` / `camelCase` identifiers into readable text without\n * inventing copy — the token itself is still the source of truth.\n */\nexport function humaniseToken(token: string): string {\n const spaced = token\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[_-]+/g, \" \")\n .trim();\n if (spaced.length === 0) return token;\n return spaced\n .split(/\\s+/)\n .map((word) =>\n word.toUpperCase() === word && word.length > 1\n ? word.charAt(0) + word.slice(1).toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1),\n )\n .join(\" \");\n}\n\n/**\n * Read the evidence array off a recommendation. Entries without a usable\n * `metric` + finite numeric `value` are skipped — a half-parsed fact is worse\n * than no fact.\n */\nexport function parseEvidenceRefs(raw: unknown): EvidenceRef[] {\n if (!Array.isArray(raw)) return [];\n const refs: EvidenceRef[] = [];\n for (const entry of raw) {\n if (!isRecord(entry)) continue;\n const metric = optionalString(entry.metric);\n const value = entry.value;\n if (\n metric === undefined ||\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n continue;\n }\n refs.push({\n metric,\n value,\n period: optionalString(entry.period),\n domain: optionalString(entry.domain),\n });\n }\n return refs;\n}\n\n/** Format a metric value in the unit the service actually stores it in. */\nexport function formatEvidenceValue(metric: string, value: number): string {\n if (/seconds$/i.test(metric)) {\n const total = Math.max(0, Math.round(value));\n const hours = Math.floor(total / 3600);\n const minutes = Math.floor((total % 3600) / 60);\n if (hours === 0 && minutes === 0) return `${total}s`;\n if (hours === 0) return `${minutes}m`;\n if (minutes === 0) return `${hours}h`;\n return `${hours}h ${minutes}m`;\n }\n if (RATIO_METRICS.has(metric)) {\n return `${Math.round(value * 100)}%`;\n }\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\n/** A single evidence line, e.g. `Focus ratio (prior): 58%`. */\nexport function formatEvidenceRef(ref: EvidenceRef): string {\n const qualifiers = [\n ref.period ? humaniseToken(ref.period).toLowerCase() : undefined,\n ref.domain ? humaniseToken(ref.domain) : undefined,\n ].filter((part): part is string => part !== undefined);\n const label = humaniseToken(ref.metric);\n const scope = qualifiers.length > 0 ? ` (${qualifiers.join(\", \")})` : \"\";\n return `${label}${scope}: ${formatEvidenceValue(ref.metric, ref.value)}`;\n}\n\n/**\n * Read the suggested action off a recommendation. Returns `null` unless the\n * payload carries a `type`; any other primitive keys become display details.\n */\nexport function parseSuggestedAction(raw: unknown): SuggestedAction | null {\n if (!isRecord(raw)) return null;\n const type = optionalString(raw.type);\n if (type === undefined) return null;\n const details: Array<{ key: string; value: string }> = [];\n for (const [key, value] of Object.entries(raw)) {\n if (key === \"type\") continue;\n if (typeof value === \"string\" && value.length > 0) {\n details.push({ key: humaniseToken(key), value: humaniseToken(value) });\n } else if (typeof value === \"number\" && Number.isFinite(value)) {\n details.push({ key: humaniseToken(key), value: String(value) });\n } else if (typeof value === \"boolean\") {\n details.push({ key: humaniseToken(key), value: value ? \"Yes\" : \"No\" });\n }\n }\n return { type, details };\n}\n\n/** `22:00` from the integer hour the service stores; `null` when unset. */\nexport function formatHourOfDay(\n hour: number | null | undefined,\n): string | null {\n if (hour == null || !Number.isFinite(hour)) return null;\n const normalised = ((Math.trunc(hour) % 24) + 24) % 24;\n return `${String(normalised).padStart(2, \"0\")}:00`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimelinePage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/timeline/TimelinePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TimelinePage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/timeline/TimelinePage.tsx"],"names":[],"mappings":"AAiQA,wBAAgB,YAAY,gCAiP3B"}
|