selftune 0.2.22 → 0.2.24
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/CHANGELOG.md +6 -0
- package/README.md +95 -15
- package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
- package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
- package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
- package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
- package/apps/local-dashboard/dist/index.html +5 -5
- package/cli/selftune/adapters/codex/install.ts +310 -78
- package/cli/selftune/adapters/opencode/install.ts +3 -4
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
- package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
- package/cli/selftune/auto-update.ts +200 -8
- package/cli/selftune/canonical-export.ts +55 -25
- package/cli/selftune/command-surface.ts +397 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/contribute/contribute.ts +64 -13
- package/cli/selftune/contribution-config.ts +57 -3
- package/cli/selftune/contribution-preferences.ts +117 -0
- package/cli/selftune/contribution-signals.ts +8 -4
- package/cli/selftune/contribution-staging.ts +13 -2
- package/cli/selftune/contributions.ts +55 -121
- package/cli/selftune/creator-contributions.ts +29 -10
- package/cli/selftune/cron/setup.ts +7 -3
- package/cli/selftune/dashboard-contract.ts +87 -0
- package/cli/selftune/dashboard-server.ts +168 -17
- package/cli/selftune/dashboard.ts +350 -17
- package/cli/selftune/eval/baseline.ts +21 -5
- package/cli/selftune/eval/execution-eval.ts +170 -0
- package/cli/selftune/eval/family-overlap.ts +2 -2
- package/cli/selftune/eval/hooks-to-evals.ts +228 -82
- package/cli/selftune/eval/import-skillsbench.ts +2 -2
- package/cli/selftune/eval/invocation-classifier.ts +56 -0
- package/cli/selftune/eval/synthetic-evals.ts +5 -3
- package/cli/selftune/eval/unit-test-cli.ts +7 -4
- package/cli/selftune/evolution/apply-proposal.ts +295 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +180 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +152 -38
- package/cli/selftune/evolution/evolve.ts +244 -52
- package/cli/selftune/evolution/rollback.ts +0 -1
- package/cli/selftune/evolution/validate-body.ts +111 -49
- package/cli/selftune/evolution/validate-host-replay.ts +510 -60
- package/cli/selftune/evolution/validate-proposal.ts +11 -150
- package/cli/selftune/evolution/validate-routing.ts +51 -108
- package/cli/selftune/evolution/validation-contract.ts +91 -0
- package/cli/selftune/grading/auto-grade.ts +11 -7
- package/cli/selftune/grading/grade-session.ts +10 -16
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/types.ts +1 -0
- package/cli/selftune/index.ts +58 -15
- package/cli/selftune/ingestors/claude-replay.ts +15 -10
- package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
- package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
- package/cli/selftune/ingestors/pi-ingest.ts +727 -0
- package/cli/selftune/init.ts +38 -4
- package/cli/selftune/localdb/direct-write.ts +120 -1
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries/cron.ts +34 -0
- package/cli/selftune/localdb/queries/dashboard.ts +834 -0
- package/cli/selftune/localdb/queries/evolution.ts +158 -0
- package/cli/selftune/localdb/queries/execution.ts +133 -0
- package/cli/selftune/localdb/queries/json.ts +18 -0
- package/cli/selftune/localdb/queries/monitoring.ts +263 -0
- package/cli/selftune/localdb/queries/raw.ts +95 -0
- package/cli/selftune/localdb/queries/staging.ts +270 -0
- package/cli/selftune/localdb/queries/trust.ts +392 -0
- package/cli/selftune/localdb/queries.ts +60 -2162
- package/cli/selftune/localdb/schema.ts +59 -0
- package/cli/selftune/monitoring/watch.ts +96 -29
- package/cli/selftune/normalization.ts +3 -0
- package/cli/selftune/observability.ts +12 -3
- package/cli/selftune/orchestrate/cli.ts +161 -0
- package/cli/selftune/orchestrate/execute.ts +295 -0
- package/cli/selftune/orchestrate/finalize.ts +157 -0
- package/cli/selftune/orchestrate/locks.ts +40 -0
- package/cli/selftune/orchestrate/plan.ts +131 -0
- package/cli/selftune/orchestrate/post-run.ts +59 -0
- package/cli/selftune/orchestrate/prepare.ts +334 -0
- package/cli/selftune/orchestrate/report.ts +182 -0
- package/cli/selftune/orchestrate/runtime.ts +120 -0
- package/cli/selftune/orchestrate/signals.ts +48 -0
- package/cli/selftune/orchestrate.ts +162 -1142
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +9 -3
- package/cli/selftune/routes/overview.ts +5 -2
- package/cli/selftune/routes/skill-report.ts +15 -2
- package/cli/selftune/schedule.ts +5 -5
- package/cli/selftune/status.ts +70 -2
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/testing-readiness.ts +597 -0
- package/cli/selftune/types.ts +46 -5
- package/cli/selftune/uninstall.ts +2 -1
- package/cli/selftune/utils/canonical-log.ts +1 -9
- package/cli/selftune/utils/cli-error.ts +9 -0
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/llm-call.ts +126 -6
- package/cli/selftune/utils/skill-discovery.ts +24 -0
- package/cli/selftune/workflows/proposals.ts +184 -0
- package/cli/selftune/workflows/skill-scaffold.ts +241 -0
- package/cli/selftune/workflows/workflows.ts +100 -26
- package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +63 -5
- package/node_modules/@selftune/telemetry-contract/src/types.ts +97 -7
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +25 -9
- package/packages/dashboard-core/AGENTS.md +18 -0
- package/packages/dashboard-core/README.md +30 -0
- package/packages/dashboard-core/index.ts +3 -0
- package/packages/dashboard-core/package.json +39 -0
- package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
- package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
- package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
- package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
- package/packages/dashboard-core/src/chrome/index.ts +14 -0
- package/packages/dashboard-core/src/chrome/types.ts +81 -0
- package/packages/dashboard-core/src/chrome/utils.ts +23 -0
- package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
- package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
- package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
- package/packages/dashboard-core/src/gates/index.ts +3 -0
- package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
- package/packages/dashboard-core/src/host/adapter.ts +47 -0
- package/packages/dashboard-core/src/host/capabilities.ts +55 -0
- package/packages/dashboard-core/src/host/index.ts +3 -0
- package/packages/dashboard-core/src/models/analytics.ts +39 -0
- package/packages/dashboard-core/src/models/index.ts +4 -0
- package/packages/dashboard-core/src/models/overview.ts +98 -0
- package/packages/dashboard-core/src/models/runtime.ts +7 -0
- package/packages/dashboard-core/src/models/skills.ts +34 -0
- package/packages/dashboard-core/src/routes/index.ts +2 -0
- package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
- package/packages/dashboard-core/src/routes/manifest.ts +451 -0
- package/packages/dashboard-core/src/routes/types.ts +39 -0
- package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
- package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
- package/packages/dashboard-core/src/screens/index.ts +37 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
- package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
- package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
- package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
- package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
- package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
- package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
- package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
- package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +63 -5
- package/packages/telemetry-contract/src/types.ts +97 -7
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +229 -464
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +693 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/primitives/tabs.tsx +7 -6
- package/packages/ui/src/types.ts +182 -4
- package/skill/SKILL.md +130 -318
- package/skill/agents/diagnosis-analyst.md +3 -3
- package/skill/agents/evolution-reviewer.md +3 -3
- package/skill/agents/integration-guide.md +3 -3
- package/skill/agents/pattern-analyst.md +2 -2
- package/skill/references/cli-quick-reference.md +89 -0
- package/skill/references/creator-playbook.md +131 -0
- package/skill/references/examples.md +48 -0
- package/skill/references/troubleshooting.md +47 -0
- package/skill/references/version-history.md +1 -1
- package/skill/selftune.contribute.json +11 -0
- package/skill/{Workflows → workflows}/Baseline.md +20 -1
- package/skill/{Workflows → workflows}/Contribute.md +23 -10
- package/skill/{Workflows → workflows}/Contributions.md +13 -5
- package/skill/workflows/CreateTestDeploy.md +170 -0
- package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
- package/skill/{Workflows → workflows}/Cron.md +1 -1
- package/skill/{Workflows → workflows}/Dashboard.md +20 -0
- package/skill/{Workflows → workflows}/Doctor.md +1 -1
- package/skill/{Workflows → workflows}/Evals.md +67 -2
- package/skill/{Workflows → workflows}/Evolve.md +119 -30
- package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
- package/skill/{Workflows → workflows}/Grade.md +1 -1
- package/skill/{Workflows → workflows}/Ingest.md +60 -2
- package/skill/{Workflows → workflows}/Initialize.md +16 -9
- package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
- package/skill/{Workflows → workflows}/PlatformHooks.md +19 -3
- package/skill/workflows/Registry.md +99 -0
- package/skill/{Workflows → workflows}/Schedule.md +3 -3
- package/skill/workflows/SignalsDashboard.md +87 -0
- package/skill/{Workflows → workflows}/Sync.md +3 -1
- package/skill/{Workflows → workflows}/UnitTest.md +19 -0
- package/skill/{Workflows → workflows}/Watch.md +42 -2
- package/skill/{Workflows → workflows}/Workflows.md +39 -2
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
- /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
- /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
- /package/skill/{Workflows → workflows}/Badge.md +0 -0
- /package/skill/{Workflows → workflows}/Composability.md +0 -0
- /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
- /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
- /package/skill/{Workflows → workflows}/Hook.md +0 -0
- /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
- /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
- /package/skill/{Workflows → workflows}/Recover.md +0 -0
- /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
- /package/skill/{Workflows → workflows}/Replay.md +0 -0
- /package/skill/{Workflows → workflows}/Rollback.md +0 -0
- /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
- /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronDownIcon, LockIcon, LogOutIcon } from "lucide-react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@selftune/ui/primitives";
|
|
7
|
+
|
|
8
|
+
import type { DashboardUser } from "../host/index";
|
|
9
|
+
import { cn, getUserInitials } from "./utils";
|
|
10
|
+
import type {
|
|
11
|
+
DashboardBrand,
|
|
12
|
+
DashboardChromeAction,
|
|
13
|
+
DashboardLinkRenderer,
|
|
14
|
+
DashboardNavItem,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
interface DashboardSidebarProps {
|
|
18
|
+
brand: DashboardBrand;
|
|
19
|
+
navItems: DashboardNavItem[];
|
|
20
|
+
renderLink: DashboardLinkRenderer;
|
|
21
|
+
sidebarAction?: DashboardChromeAction;
|
|
22
|
+
sidebarUser?: DashboardUser;
|
|
23
|
+
onSignOut?(): Promise<void> | void;
|
|
24
|
+
mobileOpen: boolean;
|
|
25
|
+
onMobileOpenChange(open: boolean): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DashboardSidebar({
|
|
29
|
+
brand,
|
|
30
|
+
navItems,
|
|
31
|
+
renderLink,
|
|
32
|
+
sidebarAction,
|
|
33
|
+
sidebarUser,
|
|
34
|
+
onSignOut,
|
|
35
|
+
mobileOpen,
|
|
36
|
+
onMobileOpenChange,
|
|
37
|
+
}: DashboardSidebarProps) {
|
|
38
|
+
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
{mobileOpen ? (
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
|
46
|
+
onClick={() => onMobileOpenChange(false)}
|
|
47
|
+
aria-label="Close sidebar overlay"
|
|
48
|
+
/>
|
|
49
|
+
) : null}
|
|
50
|
+
|
|
51
|
+
<aside
|
|
52
|
+
className={cn(
|
|
53
|
+
"fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-sidebar-border bg-sidebar",
|
|
54
|
+
"transition-transform duration-200 lg:translate-x-0",
|
|
55
|
+
mobileOpen ? "translate-x-0" : "-translate-x-full",
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
<div className="px-4 pb-8 pt-6">
|
|
59
|
+
{renderLink({
|
|
60
|
+
href: brand.href,
|
|
61
|
+
className: "flex items-center gap-3",
|
|
62
|
+
onClick: () => onMobileOpenChange(false),
|
|
63
|
+
children: (
|
|
64
|
+
<>
|
|
65
|
+
<div
|
|
66
|
+
className="size-8 shrink-0 bg-primary shadow-[0_0_12px_rgba(79,242,255,0.3)]"
|
|
67
|
+
role="img"
|
|
68
|
+
aria-label={brand.name}
|
|
69
|
+
style={{
|
|
70
|
+
WebkitMaskImage: "url(/logo.svg)",
|
|
71
|
+
WebkitMaskSize: "contain",
|
|
72
|
+
WebkitMaskRepeat: "no-repeat",
|
|
73
|
+
WebkitMaskPosition: "center",
|
|
74
|
+
maskImage: "url(/logo.svg)",
|
|
75
|
+
maskSize: "contain",
|
|
76
|
+
maskRepeat: "no-repeat",
|
|
77
|
+
maskPosition: "center",
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
<div className="flex flex-col">
|
|
81
|
+
<div className="flex items-center gap-2">
|
|
82
|
+
<span className="font-headline text-2xl font-bold tracking-tighter text-primary text-glow">
|
|
83
|
+
{brand.name}
|
|
84
|
+
</span>
|
|
85
|
+
{brand.badge ? (
|
|
86
|
+
<span className="rounded bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary">
|
|
87
|
+
{brand.badge}
|
|
88
|
+
</span>
|
|
89
|
+
) : null}
|
|
90
|
+
</div>
|
|
91
|
+
{brand.caption ? (
|
|
92
|
+
<span className="font-headline text-[10px] uppercase tracking-[0.2em] text-slate-500">
|
|
93
|
+
{brand.caption}
|
|
94
|
+
</span>
|
|
95
|
+
) : null}
|
|
96
|
+
</div>
|
|
97
|
+
</>
|
|
98
|
+
),
|
|
99
|
+
})}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<nav className="flex-1 space-y-1 px-2">
|
|
103
|
+
{navItems.map((item) => (
|
|
104
|
+
<Tooltip key={item.href}>
|
|
105
|
+
<TooltipTrigger
|
|
106
|
+
render={renderLink({
|
|
107
|
+
href: item.href,
|
|
108
|
+
onClick: () => onMobileOpenChange(false),
|
|
109
|
+
className: cn(
|
|
110
|
+
"flex items-center gap-3 rounded-lg px-4 py-2.5 font-headline text-sm tracking-tight transition-all duration-200",
|
|
111
|
+
item.isActive
|
|
112
|
+
? "bg-card font-bold text-primary shadow-[inset_0_0_0_1px_rgba(79,242,255,0.08)]"
|
|
113
|
+
: "text-slate-400 hover:bg-muted/50 hover:text-slate-200",
|
|
114
|
+
),
|
|
115
|
+
children: (
|
|
116
|
+
<>
|
|
117
|
+
{item.icon}
|
|
118
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
119
|
+
<span>{item.label}</span>
|
|
120
|
+
{item.isLocked ? <LockIcon className="size-3.5 opacity-70" /> : null}
|
|
121
|
+
</span>
|
|
122
|
+
</>
|
|
123
|
+
),
|
|
124
|
+
})}
|
|
125
|
+
/>
|
|
126
|
+
<TooltipContent side="right">{item.tooltip}</TooltipContent>
|
|
127
|
+
</Tooltip>
|
|
128
|
+
))}
|
|
129
|
+
</nav>
|
|
130
|
+
|
|
131
|
+
<div className="px-4 pb-4">
|
|
132
|
+
{sidebarAction ? (
|
|
133
|
+
<Tooltip>
|
|
134
|
+
<TooltipTrigger
|
|
135
|
+
render={
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
disabled={sidebarAction.disabled}
|
|
139
|
+
aria-disabled={sidebarAction.disabled}
|
|
140
|
+
tabIndex={sidebarAction.disabled ? -1 : 0}
|
|
141
|
+
title={sidebarAction.tooltip}
|
|
142
|
+
onClick={sidebarAction.onClick}
|
|
143
|
+
className={cn(
|
|
144
|
+
"flex w-full items-center justify-center gap-2 rounded-xl border py-2.5 font-headline text-xs uppercase tracking-wider transition-colors",
|
|
145
|
+
sidebarAction.disabled
|
|
146
|
+
? "cursor-not-allowed border-primary/15 bg-gradient-to-r from-primary/10 to-primary/5 text-primary/50 opacity-70"
|
|
147
|
+
: "border-primary/30 bg-gradient-to-r from-primary/12 to-primary/6 text-primary hover:border-primary/50 hover:bg-primary/10",
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
{sidebarAction.icon}
|
|
151
|
+
<span>{sidebarAction.label}</span>
|
|
152
|
+
</button>
|
|
153
|
+
}
|
|
154
|
+
/>
|
|
155
|
+
<TooltipContent side="right">{sidebarAction.tooltip}</TooltipContent>
|
|
156
|
+
</Tooltip>
|
|
157
|
+
) : null}
|
|
158
|
+
|
|
159
|
+
{brand.footerLabel ? (
|
|
160
|
+
<div className="mt-3 flex items-center gap-2 px-4 py-1.5 font-headline text-[10px] uppercase tracking-widest text-slate-600">
|
|
161
|
+
<span className="size-1.5 animate-pulse rounded-full bg-primary shadow-[0_0_8px_rgba(79,242,255,0.4)]" />
|
|
162
|
+
<span>{brand.footerLabel}</span>
|
|
163
|
+
</div>
|
|
164
|
+
) : null}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{sidebarUser ? (
|
|
168
|
+
<div className="border-t border-sidebar-border p-3">
|
|
169
|
+
<div className="relative">
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
onClick={() => setUserMenuOpen((open) => !open)}
|
|
173
|
+
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-sidebar-accent/50"
|
|
174
|
+
>
|
|
175
|
+
{sidebarUser.image ? (
|
|
176
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
177
|
+
<img
|
|
178
|
+
src={sidebarUser.image}
|
|
179
|
+
alt={sidebarUser.name}
|
|
180
|
+
className="size-9 rounded-full"
|
|
181
|
+
/>
|
|
182
|
+
) : (
|
|
183
|
+
<div className="flex size-9 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground">
|
|
184
|
+
{getUserInitials(sidebarUser.name)}
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
<span className="min-w-0 flex-1 text-left">
|
|
188
|
+
<span className="block truncate font-medium text-sidebar-foreground">
|
|
189
|
+
{sidebarUser.name}
|
|
190
|
+
</span>
|
|
191
|
+
<span className="block truncate text-xs text-slate-500">
|
|
192
|
+
{sidebarUser.subtitle ?? sidebarUser.email ?? "Signed in"}
|
|
193
|
+
</span>
|
|
194
|
+
</span>
|
|
195
|
+
{onSignOut ? <ChevronDownIcon className="size-4" /> : null}
|
|
196
|
+
</button>
|
|
197
|
+
|
|
198
|
+
{userMenuOpen && onSignOut ? (
|
|
199
|
+
<div className="absolute bottom-full left-0 mb-2 w-full rounded-lg border border-border bg-popover py-1 shadow-lg">
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onClick={async () => {
|
|
203
|
+
setUserMenuOpen(false);
|
|
204
|
+
await onSignOut();
|
|
205
|
+
}}
|
|
206
|
+
className="flex w-full items-center gap-2 px-4 py-2 text-sm text-muted-foreground hover:bg-sidebar-accent/50"
|
|
207
|
+
>
|
|
208
|
+
<LogOutIcon className="size-4" />
|
|
209
|
+
<span>Sign out</span>
|
|
210
|
+
</button>
|
|
211
|
+
</div>
|
|
212
|
+
) : null}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
) : null}
|
|
216
|
+
</aside>
|
|
217
|
+
</>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
import type { RuntimeBadgeProps } from "./types";
|
|
5
|
+
|
|
6
|
+
export function RuntimeBadge({
|
|
7
|
+
href,
|
|
8
|
+
label,
|
|
9
|
+
detail,
|
|
10
|
+
tone = "healthy",
|
|
11
|
+
renderLink,
|
|
12
|
+
}: RuntimeBadgeProps) {
|
|
13
|
+
const toneClassName =
|
|
14
|
+
tone === "warning"
|
|
15
|
+
? "text-amber-400 ring-amber-400/20 hover:bg-amber-400/8"
|
|
16
|
+
: tone === "critical"
|
|
17
|
+
? "text-destructive ring-destructive/20 hover:bg-destructive/8"
|
|
18
|
+
: "text-primary ring-primary/20 hover:bg-primary/8";
|
|
19
|
+
|
|
20
|
+
const dotClassName =
|
|
21
|
+
tone === "warning"
|
|
22
|
+
? "bg-amber-400"
|
|
23
|
+
: tone === "critical"
|
|
24
|
+
? "bg-destructive"
|
|
25
|
+
: "animate-pulse bg-primary shadow-[0_0_8px_color-mix(in_srgb,var(--primary)_60%,transparent)]";
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<footer className="pointer-events-none fixed bottom-4 right-4 z-20">
|
|
29
|
+
{renderLink({
|
|
30
|
+
href,
|
|
31
|
+
className: cn(
|
|
32
|
+
"glass-panel pointer-events-auto flex items-center gap-2 rounded-full border border-foreground/5 px-3 py-2 font-headline text-[10px] uppercase tracking-[0.18em] text-slate-300 shadow-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
|
|
33
|
+
toneClassName,
|
|
34
|
+
),
|
|
35
|
+
children: (
|
|
36
|
+
<>
|
|
37
|
+
<span className={cn("size-1.5 rounded-full", dotClassName)} />
|
|
38
|
+
<span>{label}</span>
|
|
39
|
+
<span className="text-foreground/25">/</span>
|
|
40
|
+
<span className="text-slate-400">{detail}</span>
|
|
41
|
+
</>
|
|
42
|
+
),
|
|
43
|
+
})}
|
|
44
|
+
</footer>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { DashboardChrome } from "./DashboardChrome";
|
|
2
|
+
export { RuntimeBadge } from "./RuntimeBadge";
|
|
3
|
+
export type {
|
|
4
|
+
DashboardBrand,
|
|
5
|
+
DashboardChromeAction,
|
|
6
|
+
DashboardChromeProps,
|
|
7
|
+
DashboardHeaderMeta,
|
|
8
|
+
DashboardLinkRenderer,
|
|
9
|
+
DashboardLinkRenderProps,
|
|
10
|
+
DashboardNavItem,
|
|
11
|
+
DashboardSearchItem,
|
|
12
|
+
RuntimeBadgeProps,
|
|
13
|
+
RuntimeBadgeTone,
|
|
14
|
+
} from "./types";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type { DashboardUser } from "../host/index";
|
|
4
|
+
|
|
5
|
+
export interface DashboardLinkRenderProps {
|
|
6
|
+
href: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
onClick?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type DashboardLinkRenderer = (props: DashboardLinkRenderProps) => ReactElement;
|
|
13
|
+
|
|
14
|
+
export interface DashboardNavItem {
|
|
15
|
+
href: string;
|
|
16
|
+
label: string;
|
|
17
|
+
tooltip: string;
|
|
18
|
+
icon: ReactNode;
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
isLocked?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DashboardSearchItem {
|
|
24
|
+
id: string;
|
|
25
|
+
group: string;
|
|
26
|
+
label: string;
|
|
27
|
+
meta?: string | null;
|
|
28
|
+
keywords?: readonly string[];
|
|
29
|
+
leading?: ReactNode;
|
|
30
|
+
trailing?: ReactNode;
|
|
31
|
+
onSelect(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DashboardHeaderMeta {
|
|
35
|
+
title: string;
|
|
36
|
+
icon?: ReactNode;
|
|
37
|
+
badge?: string;
|
|
38
|
+
backHref?: string | null;
|
|
39
|
+
backLabel?: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DashboardBrand {
|
|
43
|
+
href: string;
|
|
44
|
+
name: string;
|
|
45
|
+
caption?: string;
|
|
46
|
+
badge?: string;
|
|
47
|
+
footerLabel?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DashboardChromeAction {
|
|
51
|
+
label: string;
|
|
52
|
+
tooltip: string;
|
|
53
|
+
icon?: ReactNode;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
onClick?(): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface DashboardChromeProps {
|
|
59
|
+
brand: DashboardBrand;
|
|
60
|
+
navItems: DashboardNavItem[];
|
|
61
|
+
renderLink: DashboardLinkRenderer;
|
|
62
|
+
headerMeta: DashboardHeaderMeta;
|
|
63
|
+
searchItems?: DashboardSearchItem[];
|
|
64
|
+
headerUser?: DashboardUser;
|
|
65
|
+
sidebarUser?: DashboardUser;
|
|
66
|
+
sidebarAction?: DashboardChromeAction;
|
|
67
|
+
onSignOut?(): Promise<void> | void;
|
|
68
|
+
overlay?: ReactNode;
|
|
69
|
+
contentClassName?: string | null;
|
|
70
|
+
children: ReactNode;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type RuntimeBadgeTone = "healthy" | "warning" | "critical";
|
|
74
|
+
|
|
75
|
+
export interface RuntimeBadgeProps {
|
|
76
|
+
href: string;
|
|
77
|
+
label: string;
|
|
78
|
+
detail: string;
|
|
79
|
+
tone?: RuntimeBadgeTone;
|
|
80
|
+
renderLink: DashboardLinkRenderer;
|
|
81
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DashboardSearchItem } from "./types";
|
|
2
|
+
|
|
3
|
+
export function cn(...values: Array<string | false | null | undefined>): string {
|
|
4
|
+
return values.filter(Boolean).join(" ");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getUserInitials(name: string): string {
|
|
8
|
+
const parts = name.trim().split(/\s+/).slice(0, 2);
|
|
9
|
+
return parts.map((part) => part.charAt(0).toUpperCase()).join("") || "?";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalize(value: string): string {
|
|
13
|
+
return value.trim().toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function matchesSearchItem(item: DashboardSearchItem, query: string): boolean {
|
|
17
|
+
const needle = normalize(query);
|
|
18
|
+
if (!needle) return true;
|
|
19
|
+
|
|
20
|
+
const haystack = [item.label, item.meta ?? "", ...(item.keywords ?? [])].map(normalize).join(" ");
|
|
21
|
+
|
|
22
|
+
return haystack.includes(needle);
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface FeatureGateProps {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
fallback?: ReactNode;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function FeatureGate({ enabled, fallback = null, children }: FeatureGateProps) {
|
|
10
|
+
return enabled ? <>{children}</> : <>{fallback}</>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { UpgradeCard } from "./UpgradeCard";
|
|
2
|
+
|
|
3
|
+
interface LockedRouteProps {
|
|
4
|
+
eyebrow: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
highlights?: readonly string[];
|
|
8
|
+
primaryAction: {
|
|
9
|
+
href: string;
|
|
10
|
+
label: string;
|
|
11
|
+
};
|
|
12
|
+
secondaryAction?: {
|
|
13
|
+
href: string;
|
|
14
|
+
label: string;
|
|
15
|
+
};
|
|
16
|
+
note?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function LockedRoute(props: LockedRouteProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="@container/main flex flex-1 flex-col py-6">
|
|
22
|
+
<div className="grid grid-cols-12 gap-6 px-4 lg:px-6">
|
|
23
|
+
<div className="col-span-12">
|
|
24
|
+
<UpgradeCard {...props} />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ArrowUpRightIcon, CheckIcon, LockIcon } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
interface UpgradeAction {
|
|
4
|
+
href: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UpgradeCardProps {
|
|
9
|
+
eyebrow: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
highlights?: readonly string[];
|
|
13
|
+
primaryAction: UpgradeAction;
|
|
14
|
+
secondaryAction?: UpgradeAction;
|
|
15
|
+
note?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function UpgradeCard({
|
|
19
|
+
eyebrow,
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
highlights = [],
|
|
23
|
+
primaryAction,
|
|
24
|
+
secondaryAction,
|
|
25
|
+
note,
|
|
26
|
+
}: UpgradeCardProps) {
|
|
27
|
+
return (
|
|
28
|
+
<section className="overflow-hidden rounded-[28px] border border-primary/15 bg-[radial-gradient(circle_at_top_left,rgba(79,242,255,0.18),transparent_32%),linear-gradient(180deg,rgba(8,16,27,0.98),rgba(7,12,22,0.96))] shadow-[0_24px_80px_rgba(0,0,0,0.35)]">
|
|
29
|
+
<div className="grid gap-10 p-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(300px,0.9fr)] lg:p-10">
|
|
30
|
+
<div className="space-y-6">
|
|
31
|
+
<div className="inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.22em] text-primary">
|
|
32
|
+
<LockIcon className="size-3" />
|
|
33
|
+
<span>{eyebrow}</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div className="space-y-4">
|
|
36
|
+
<h1 className="max-w-2xl font-headline text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
|
|
37
|
+
{title}
|
|
38
|
+
</h1>
|
|
39
|
+
<p className="max-w-2xl text-base leading-7 text-slate-300">{description}</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="flex flex-wrap gap-3">
|
|
43
|
+
<a
|
|
44
|
+
href={primaryAction.href}
|
|
45
|
+
target="_blank"
|
|
46
|
+
rel="noreferrer"
|
|
47
|
+
className="inline-flex items-center gap-2 rounded-full bg-primary px-5 py-2.5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90"
|
|
48
|
+
>
|
|
49
|
+
<span>{primaryAction.label}</span>
|
|
50
|
+
<ArrowUpRightIcon className="size-4" />
|
|
51
|
+
</a>
|
|
52
|
+
{secondaryAction ? (
|
|
53
|
+
<a
|
|
54
|
+
href={secondaryAction.href}
|
|
55
|
+
target="_blank"
|
|
56
|
+
rel="noreferrer"
|
|
57
|
+
className="inline-flex items-center gap-2 rounded-full border border-border/60 bg-background/40 px-5 py-2.5 text-sm font-medium text-slate-200 transition hover:border-primary/30 hover:text-primary"
|
|
58
|
+
>
|
|
59
|
+
<span>{secondaryAction.label}</span>
|
|
60
|
+
<ArrowUpRightIcon className="size-4" />
|
|
61
|
+
</a>
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{note ? <p className="text-sm text-slate-500">{note}</p> : null}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="rounded-[24px] border border-white/8 bg-white/4 p-6 backdrop-blur-sm">
|
|
69
|
+
<div className="mb-4 text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500">
|
|
70
|
+
What unlocks
|
|
71
|
+
</div>
|
|
72
|
+
<div className="space-y-3">
|
|
73
|
+
{highlights.map((highlight) => (
|
|
74
|
+
<div
|
|
75
|
+
key={highlight}
|
|
76
|
+
className="flex items-start gap-3 rounded-2xl border border-white/6 bg-background/40 px-4 py-3"
|
|
77
|
+
>
|
|
78
|
+
<div className="mt-0.5 flex size-5 items-center justify-center rounded-full bg-primary/12 text-primary">
|
|
79
|
+
<CheckIcon className="size-3.5" />
|
|
80
|
+
</div>
|
|
81
|
+
<p className="text-sm leading-6 text-slate-200">{highlight}</p>
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type { DashboardHostAdapter } from "./adapter";
|
|
4
|
+
import type { Capabilities, DashboardFeatureKey } from "./capabilities";
|
|
5
|
+
import { canUseFeature } from "./capabilities";
|
|
6
|
+
|
|
7
|
+
export interface DashboardHostContextValue {
|
|
8
|
+
adapter: DashboardHostAdapter;
|
|
9
|
+
capabilities: Capabilities;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DashboardHostContext = createContext<DashboardHostContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
interface DashboardHostProviderProps {
|
|
15
|
+
adapter: DashboardHostAdapter;
|
|
16
|
+
capabilities: Capabilities;
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function DashboardHostProvider({
|
|
21
|
+
adapter,
|
|
22
|
+
capabilities,
|
|
23
|
+
children,
|
|
24
|
+
}: DashboardHostProviderProps) {
|
|
25
|
+
const value = useMemo(
|
|
26
|
+
() => ({
|
|
27
|
+
adapter,
|
|
28
|
+
capabilities,
|
|
29
|
+
}),
|
|
30
|
+
[adapter, capabilities],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return <DashboardHostContext.Provider value={value}>{children}</DashboardHostContext.Provider>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useDashboardHost(): DashboardHostContextValue {
|
|
37
|
+
const context = useContext(DashboardHostContext);
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error("useDashboardHost must be used within a DashboardHostProvider");
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useOptionalDashboardHost(): DashboardHostContextValue | null {
|
|
45
|
+
return useContext(DashboardHostContext);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useDashboardHostAdapter(): DashboardHostAdapter {
|
|
49
|
+
return useDashboardHost().adapter;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useOptionalDashboardHostAdapter(): DashboardHostAdapter | null {
|
|
53
|
+
return useOptionalDashboardHost()?.adapter ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useCapabilities(): Capabilities {
|
|
57
|
+
return useDashboardHost().capabilities;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useFeatureEnabled(feature: DashboardFeatureKey): boolean {
|
|
61
|
+
return canUseFeature(useCapabilities(), feature);
|
|
62
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnalyticsModel,
|
|
3
|
+
OverviewModel,
|
|
4
|
+
RuntimeHealthModel,
|
|
5
|
+
SkillsModel,
|
|
6
|
+
} from "../models/index";
|
|
7
|
+
|
|
8
|
+
export interface DashboardUser {
|
|
9
|
+
id?: string;
|
|
10
|
+
name: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
image?: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DashboardSessionState {
|
|
17
|
+
status: "loading" | "authenticated" | "anonymous";
|
|
18
|
+
user?: DashboardUser;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DashboardHostLinks {
|
|
22
|
+
upgrade: string;
|
|
23
|
+
billing?: string;
|
|
24
|
+
docs?: string;
|
|
25
|
+
cloudDashboard?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DashboardHostActions {
|
|
29
|
+
signOut?(): Promise<void>;
|
|
30
|
+
openUpgrade(): void;
|
|
31
|
+
getOverviewWatchlist?(): Promise<string[]>;
|
|
32
|
+
updateOverviewWatchlist?(skills: string[]): Promise<string[]>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DashboardApiClient {
|
|
36
|
+
fetchOverview(): Promise<OverviewModel>;
|
|
37
|
+
fetchSkills(): Promise<SkillsModel>;
|
|
38
|
+
fetchAnalytics(): Promise<AnalyticsModel>;
|
|
39
|
+
fetchRuntimeHealth?(): Promise<RuntimeHealthModel>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DashboardHostAdapter {
|
|
43
|
+
useSession(): DashboardSessionState;
|
|
44
|
+
api: DashboardApiClient;
|
|
45
|
+
links: DashboardHostLinks;
|
|
46
|
+
actions: DashboardHostActions;
|
|
47
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const FEATURE_KEYS = [
|
|
2
|
+
"analytics",
|
|
3
|
+
"registry",
|
|
4
|
+
"signals",
|
|
5
|
+
"proposals",
|
|
6
|
+
"billing",
|
|
7
|
+
"teamAdmin",
|
|
8
|
+
"runtimeStatus",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export const DISCOVERABLE_FEATURE_KEYS = ["registry", "signals", "proposals", "billing"] as const;
|
|
12
|
+
|
|
13
|
+
export type DashboardHostKind = "local" | "cloud";
|
|
14
|
+
export type DashboardPlan = "oss" | "pro" | "team";
|
|
15
|
+
export type DashboardFeatureKey = (typeof FEATURE_KEYS)[number];
|
|
16
|
+
export type DashboardDiscoverableFeatureKey = (typeof DISCOVERABLE_FEATURE_KEYS)[number];
|
|
17
|
+
|
|
18
|
+
export type DashboardFeatureFlags = Record<DashboardFeatureKey, boolean>;
|
|
19
|
+
export type DashboardDiscoverableFlags = Record<DashboardDiscoverableFeatureKey, boolean>;
|
|
20
|
+
|
|
21
|
+
export interface Capabilities {
|
|
22
|
+
host: DashboardHostKind;
|
|
23
|
+
plan: DashboardPlan;
|
|
24
|
+
features: DashboardFeatureFlags;
|
|
25
|
+
discoverable: DashboardDiscoverableFlags;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function canUseFeature(capabilities: Capabilities, feature: DashboardFeatureKey): boolean {
|
|
29
|
+
return capabilities.features[feature];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function canDiscoverFeature(
|
|
33
|
+
capabilities: Capabilities,
|
|
34
|
+
feature: DashboardDiscoverableFeatureKey,
|
|
35
|
+
): boolean {
|
|
36
|
+
return capabilities.discoverable[feature] || capabilities.features[feature];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function withCapabilityOverrides(
|
|
40
|
+
base: Capabilities,
|
|
41
|
+
overrides: Partial<Capabilities>,
|
|
42
|
+
): Capabilities {
|
|
43
|
+
return {
|
|
44
|
+
host: overrides.host ?? base.host,
|
|
45
|
+
plan: overrides.plan ?? base.plan,
|
|
46
|
+
features: {
|
|
47
|
+
...base.features,
|
|
48
|
+
...overrides.features,
|
|
49
|
+
},
|
|
50
|
+
discoverable: {
|
|
51
|
+
...base.discoverable,
|
|
52
|
+
...overrides.discoverable,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|