@hanzo/app 0.2.9 → 0.2.10

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/App.js CHANGED
@@ -61,14 +61,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
61
61
  // so the builder's own plans are never drawn.
62
62
  import { useI18n } from '@hanzo/i18n/react';
63
63
  import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
64
- import { ArrowLeft, ArrowRight } from 'lucide-react';
64
+ import { ArrowLeft, ArrowRight, RefreshCw } from 'lucide-react';
65
65
  import { AiProvider } from '@hanzo/ai/react';
66
66
  import { XStack, YStack } from '@hanzo/gui';
67
- import { Builder, HostProvider, PrefsProvider, route } from '@hanzo/build';
67
+ import { Builder, HostProvider, PrefsProvider, route, tabs } from '@hanzo/build';
68
68
  import { nav as places } from '@hanzo/studio';
69
69
  import { useAi } from './lib/ai.js';
70
70
  import { Chat } from './Chat.js';
71
71
  import { Column } from './Column.js';
72
+ import { Learning } from './Learning.js';
72
73
  import { isPlans, leave, useDevHost } from './dev.js';
73
74
  import { Orgs } from './Orgs.js';
74
75
  import { useRooms } from './host.js';
@@ -118,6 +119,7 @@ function Window({ bar }) {
118
119
  const [closed, setClosed] = useKept('hanzo.build.rail', false);
119
120
  // The palette is open (the sidebar's search; ⌘K toggles it).
120
121
  const [finding, setFinding] = useState(false);
122
+ const [learning, setLearning] = useState(false);
121
123
  const found = useCallback(() => setFinding(false), []);
122
124
  const dev = useDevHost();
123
125
  // The builder's moves put the drawer away, in its place in the history; one to
@@ -254,7 +256,11 @@ function Window({ bar }) {
254
256
  pick(o);
255
257
  window.location.assign(mode === 'dev' ? app('') : mode === 'studio' ? studio('') : mode === 'tabs' ? TERMINALS : talk());
256
258
  };
257
- const sidebar = mode === 'studio' ? places(maker, (p) => maker.go(p)) : undefined;
259
+ const sidebar = mode === 'studio' ? places(maker, (p) => maker.go(p)) : mode === 'dev' && raised(where) === 'customize'
260
+ ? tabs(host, (p) => host.go(p), t).map((group) => ({ ...group, links: [...group.links, {
261
+ id: 'learning', label: t('Learning'), active: learning, icon: _jsx(RefreshCw, { size: 16, "aria-hidden": true }),
262
+ onPress: () => { shut(); setLearning(true); },
263
+ }] })) : undefined;
258
264
  const flip = () => setClosed(!closed);
259
265
  const seek = () => {
260
266
  shut();
@@ -262,5 +268,5 @@ function Window({ bar }) {
262
268
  };
263
269
  return (_jsx(AiProvider, { client: client ?? undefined, children: _jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsxs(YStack, { ref: windowRef, "data-slot": "window", ...(bar ? floor : null), flex: 1, minW: 0, height: "100dvh", overflow: "hidden", "$max-md": { position: 'fixed', t: TOP, l: 0, r: 0, height: HEIGHT, ...{ overflow: 'clip' } }, children: [bar ? (_jsxs(XStack, { "data-slot": "title-bar", height: BAR, shrink: 0, items: "center", gap: 2, pl: (bar.inset ?? 0) + 6, pr: "$2", ...bar.drag, children: [_jsx(XStack, { "$max-md": { display: 'none' }, children: _jsx(Toggle, { open: !closed, onPress: flip }) }), _jsx(XStack, { $md: { display: 'none' }, children: _jsx(Toggle, { open: drawer.state === 'open', onPress: drawer.open, label: t('Open sidebar') }) }), _jsx(Mark, { label: t('Back'), "data-slot": "back", onPress: () => router.back(), children: _jsx(ArrowLeft, { size: 18, "aria-hidden": true, "data-i18n-mirror": true }) }), _jsx(Mark, { label: t('Forward'), "data-slot": "forward", onPress: () => router.forward(), children: _jsx(ArrowRight, { size: 18, "aria-hidden": true, "data-i18n-mirror": true }) }), _jsx(XStack, { flex: 1, self: "stretch", ...bar.drag })] })) : null, _jsxs(XStack, { ref: drawer.pane, "data-room": "", "data-app": mode, "data-slot": "frame", ...(bar ? null : floor), ...(drawer.slide ? RAISED : null),
264
270
  // inline-style: the slide, a transform with no logical form whose sign and distance the drawer writes on the element itself, a drag every frame (Drawer.tsx).
265
- style: drawer.slide ?? undefined, flex: 1, minW: 0, minH: 0, paddingInlineEnd: "var(--pane-gap)", pb: "var(--pane-gap)", pt: 0, "$max-md": { p: 0 }, children: [drawer.slide ? _jsx(Lift, {}) : null, _jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }), closed || mode === 'tabs' ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, onBack: back, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, marginInlineStart: closed || mode === 'tabs' ? 0 : 'var(--pane-gap)', "$max-md": { marginInlineStart: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, open: drawer.state !== 'shut', go: move, onMenu: drawer.open, onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'tabs' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(Suspense, { fallback: null, children: _jsx(Terminals, {}) }) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer.state === 'shut' ? null : (_jsx(Layer, { drawer: drawer, top: bar ? BAR : 0, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, onBack: back, drawer: true, places: sidebar, rail: _jsx(Rail, { host: host, mode: mode, at: where, open: true, rows: true, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }), foot: _jsx(Foot, { host: host, onChat: fresh, onOrg: org }) }) })), finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null] }) }) }) }));
271
+ style: drawer.slide ?? undefined, flex: 1, minW: 0, minH: 0, paddingInlineEnd: "var(--pane-gap)", pb: "var(--pane-gap)", pt: 0, "$max-md": { p: 0 }, children: [drawer.slide ? _jsx(Lift, {}) : null, _jsx(YStack, { height: "100%", "$max-md": { display: 'none' }, children: _jsx(Rail, { host: host, mode: mode, at: where, open: !closed, toggle: bar ? undefined : flip, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }) }), closed || mode === 'tabs' ? null : (_jsx(YStack, { "$max-md": { display: 'none' }, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: () => setClosed(true), onSeek: seek, onBack: back, places: sidebar }) })), _jsxs(YStack, { "data-slot": "stage", flex: 1, minW: 0, minH: 0, marginInlineStart: closed || mode === 'tabs' ? 0 : 'var(--pane-gap)', "$max-md": { marginInlineStart: 0, pb: FOOT }, children: [bar ? null : _jsx(Top, { host: host, mode: mode, places: sidebar, open: drawer.state !== 'shut', go: move, onMenu: drawer.open, onChat: toChat, onDev: toDev }), _jsx(XStack, { "data-slot": "panes", flex: 1, minW: 0, minH: 0, children: mode === 'chat' ? (_jsx(Paned, { framed: true, children: _jsx(Chat, {}) })) : mode === 'tabs' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(Suspense, { fallback: null, children: _jsx(Terminals, {}) }) })) : mode === 'studio' ? (_jsx(YStack, { render: "main", "data-slot": "pane", ...pane(), overflow: "hidden", flex: 1, minW: 0, minH: 0, children: _jsx(YStack, { flex: 1, minH: 0, height: "100%", children: _jsx(Suspense, { fallback: null, children: _jsx(Studio, { host: maker, rail: false }) }) }) })) : (_jsx(YStack, { render: "main", "data-slot": "pane", ...(run ? null : { ...pane(), overflow: 'hidden' }), flex: 1, minW: 0, minH: 0, children: plans ? null : _jsx(Builder, { host: host, rail: false }) })) })] })] }), drawer.state === 'shut' ? null : (_jsx(Layer, { drawer: drawer, top: bar ? BAR : 0, children: _jsx(Column, { host: host, mode: mode, thread: thread, go: move, onShut: shut, onSeek: seek, onBack: back, drawer: true, places: sidebar, rail: _jsx(Rail, { host: host, mode: mode, at: where, open: true, rows: true, onChat: toChat, onDev: toDev, onStudio: toStudio, onTabs: toTabs, go: move, onOrg: org }), foot: _jsx(Foot, { host: host, onChat: fresh, onOrg: org }) }) })), finding ? _jsx(Palette, { go: move, onClose: found, mode: mode, host: host }) : null, learning ? _jsx(Learning, { open: true, onClose: () => setLearning(false) }) : null] }) }) }) }));
266
272
  }
@@ -0,0 +1,5 @@
1
+ /** The user's actual evidence and findings. Closed dialogs make no API requests. */
2
+ export declare function Learning({ open, onClose }: {
3
+ open: boolean;
4
+ onClose: () => void;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useI18n } from '@hanzo/i18n/react';
4
+ import { useIam } from '@hanzo/iam/react';
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import { Button, Dialog, DialogContent, DialogTitle, Input, Text, XStack, YStack } from '@hanzo/ui';
7
+ import { say } from './failure.js';
8
+ import { org } from './lib/session.js';
9
+ import { learningAnalyze, learningCompare, learningCreate, learningRead } from './learning.js';
10
+ /** The user's actual evidence and findings. Closed dialogs make no API requests. */
11
+ export function Learning({ open, onClose }) {
12
+ const { t } = useI18n();
13
+ const { accessToken } = useIam();
14
+ const tenant = org();
15
+ const identity = `${tenant}:${accessToken ?? ''}`;
16
+ const [held, setHeld] = useState({ identity: '', answers: {} });
17
+ const [revision, setRevision] = useState(0);
18
+ const [tab, setTab] = useState('research');
19
+ const [form, setForm] = useState(false);
20
+ const [busy, setBusy] = useState(false);
21
+ const [problem, setProblem] = useState(null);
22
+ const draftId = useRef('');
23
+ const [measuring, setMeasuring] = useState(false);
24
+ const [measured, setMeasured] = useState({ identity: '' });
25
+ const [pair, setPair] = useState({ benchmark: '', a: '', b: '' });
26
+ const results = measured.identity === identity ? measured : null;
27
+ const [proposal, setProposal] = useState({ title: '', question: '', baseline: '', target: '', metric: 'accuracy', category: 'all', direction: 'higher', minQuestions: 20, minGain: 1 });
28
+ const answers = held.identity === identity ? held.answers : {};
29
+ useEffect(() => {
30
+ if (!open || !accessToken)
31
+ return;
32
+ const controller = new AbortController();
33
+ setHeld({ identity, answers: {} });
34
+ for (const part of ['studies', 'reviews', 'experiments', 'benchmarks']) {
35
+ void learningRead(part, controller.signal).then((rows) => { if (!controller.signal.aborted)
36
+ setHeld((old) => ({ identity, answers: { ...old.answers, [part]: { rows } } })); }, (error) => { if (!controller.signal.aborted)
37
+ setHeld((old) => ({ identity, answers: { ...old.answers, [part]: { error } } })); });
38
+ }
39
+ return () => controller.abort();
40
+ }, [open, accessToken, identity, revision]);
41
+ const create = async () => {
42
+ if (busy)
43
+ return;
44
+ setBusy(true);
45
+ setProblem(null);
46
+ try {
47
+ if (!draftId.current)
48
+ draftId.current = `improvement-${crypto.randomUUID()}`;
49
+ await learningCreate(proposal, draftId.current);
50
+ draftId.current = '';
51
+ setForm(false);
52
+ setRevision((n) => n + 1);
53
+ }
54
+ catch (error) {
55
+ setProblem(error);
56
+ }
57
+ finally {
58
+ setBusy(false);
59
+ }
60
+ };
61
+ const analyze = async (id) => {
62
+ setMeasuring(true);
63
+ setMeasured({ identity });
64
+ try {
65
+ setMeasured({ identity, analysis: await learningAnalyze(id) });
66
+ }
67
+ catch (error) {
68
+ setMeasured({ identity, error });
69
+ }
70
+ finally {
71
+ setMeasuring(false);
72
+ }
73
+ };
74
+ const compare = async () => {
75
+ setMeasuring(true);
76
+ setMeasured({ identity });
77
+ try {
78
+ setMeasured({ identity, comparison: await learningCompare(pair.benchmark, pair.a, pair.b) });
79
+ }
80
+ catch (error) {
81
+ setMeasured({ identity, error });
82
+ }
83
+ finally {
84
+ setMeasuring(false);
85
+ }
86
+ };
87
+ const state = (part) => answers[part]?.error
88
+ ? _jsx(Text, { role: "alert", color: "$soft", children: say(answers[part]?.error, t('your learning activity'), 'read', t) })
89
+ : !answers[part]?.rows ? _jsx(Text, { color: "$soft", children: t('Loading…') }) : null;
90
+ const field = (key, label) => (_jsxs(YStack, { gap: "$1", children: [_jsx(Text, { fontSize: "$2", children: label }), _jsx(Input, { "aria-label": label, value: proposal[key], onChangeText: (value) => setProposal((old) => ({ ...old, [key]: value })) })] }));
91
+ return _jsx(Dialog, { open: open, onOpenChange: (next) => { if (!next)
92
+ onClose(); }, children: _jsx(DialogContent, { width: "100%", maxW: 900, p: "$5", children: _jsxs(YStack, { gap: "$4", maxH: "80vh", overflow: "scroll", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: _jsx(Text, { fontSize: "$5", children: t('Learning') }) }), _jsx(Button, { size: "sm", onPress: onClose, children: t('Close') })] }), _jsx(Text, { color: "$soft", children: t('Research, test ideas, and review improvements backed by your workspace’s evidence.') }), !accessToken ? _jsx(Text, { role: "status", children: t('Sign in to see your learning activity.') }) : _jsxs(_Fragment, { children: [_jsxs(XStack, { gap: "$2", flexWrap: "wrap", role: "tablist", "aria-label": t('Learning'), children: [_jsx(Button, { role: "tab", "aria-selected": tab === 'research', onPress: () => setTab('research'), children: t('Research') }), _jsx(Button, { role: "tab", "aria-selected": tab === 'experiments', onPress: () => setTab('experiments'), children: t('Experiments') }), _jsx(Button, { role: "tab", "aria-selected": tab === 'benchmarks', onPress: () => setTab('benchmarks'), children: t('Benchmarks') }), _jsx(Button, { size: "sm", onPress: () => setRevision((n) => n + 1), children: t('Refresh') })] }), tab === 'research' ? _jsxs(YStack, { gap: "$3", children: [_jsx(Text, { color: "$soft", children: t('A study with a baseline and acceptance criteria reviews new results as they arrive. Findings remain drafts until reviewed.') }), _jsx(Button, { size: "sm", onPress: () => setForm(!form), children: form ? t('Cancel') : t('New improvement study') }), form ? _jsx("form", { onSubmit: (event) => { event.preventDefault(); void create(); }, children: _jsxs(YStack, { gap: "$2", children: [field('title', t('Title')), field('question', t('Research question')), field('baseline', t('Baseline run ID')), field('target', t('App, harness, skill or connector')), field('category', t('Metric category')), field('metric', t('Primary metric')), _jsx(XStack, { gap: "$2", children: _jsx(Button, { type: "button", size: "sm", onPress: () => setProposal((p) => ({ ...p, direction: p.direction === 'higher' ? 'lower' : 'higher' })), children: proposal.direction === 'higher' ? t('Higher is better') : t('Lower is better') }) }), _jsx(Text, { fontSize: "$2", children: t('Minimum completed evaluation size') }), _jsx(Input, { "aria-label": t('Minimum completed evaluation size'), type: "number", value: String(proposal.minQuestions), onChangeText: (v) => setProposal((p) => ({ ...p, minQuestions: Number(v) })) }), _jsx(Text, { fontSize: "$2", children: t('Minimum gain in metric units') }), _jsx(Input, { "aria-label": t('Minimum gain in metric units'), type: "number", value: String(proposal.minGain), onChangeText: (v) => setProposal((p) => ({ ...p, minGain: Number(v) })) }), _jsx(Text, { color: "$soft", children: t('This enables evaluation of recorded results. It does not start a model call or change a live app.') }), problem ? _jsx(Text, { role: "alert", children: say(problem, t('this study'), 'save', t) }) : null, _jsx(Button, { type: "submit", disabled: busy || !proposal.baseline.trim() || !proposal.target.trim() || !proposal.title.trim() || !proposal.question.trim() || !proposal.metric.trim() || !Number.isInteger(proposal.minQuestions) || proposal.minQuestions < 1 || !Number.isFinite(proposal.minGain) || proposal.minGain < 0, children: busy ? t('Saving…') : t('Enable study') })] }) }) : null, state('studies'), answers.studies?.rows?.map((study) => _jsxs(YStack, { gap: "$1", p: "$3", bg: "$panel", rounded: "$3", children: [_jsx(Text, { children: study.title || study.id }), _jsx(Text, { color: "$soft", children: study.question }), _jsx(Text, { fontSize: "$2", children: study.improvement ? t('Automatic review enabled') : t('Evidence only') })] }, `${study.project}:${study.id}`)), answers.studies?.rows?.length === 0 ? _jsx(Text, { color: "$soft", children: t('No studies yet. Create one to review recorded results continuously.') }) : null, _jsx(Text, { fontSize: "$4", children: t('Improvement findings') }), state('reviews'), answers.reviews?.rows?.map((row) => _jsxs(YStack, { gap: "$1", p: "$3", bg: "$panel", rounded: "$3", children: [_jsx(Text, { children: row.action.review?.target }), _jsxs(Text, { children: [row.action.review?.status, " \u00B7 ", row.status] }), _jsx(Text, { color: "$soft", children: row.action.description }), row.action.review?.blocks?.map((block, i) => _jsx(Text, { fontSize: "$2", color: "$soft", children: block }, i))] }, row.id)), answers.reviews?.rows?.length === 0 ? _jsx(Text, { color: "$soft", children: t('Findings appear when a study has baseline and candidate results. No result is being claimed yet.') }) : null] }) : tab === 'experiments' ? _jsxs(YStack, { gap: "$3", children: [_jsx(Text, { color: "$soft", children: t('Controlled experiments assign variants and measure their outcomes. Choosing a winner changes a live rollout and requires an authorized review.') }), state('experiments'), answers.experiments?.rows?.map((row) => _jsxs(YStack, { gap: "$1", p: "$3", bg: "$panel", rounded: "$3", children: [_jsx(Text, { children: row.name || row.id }), _jsxs(Text, { color: "$soft", children: [row.status, row.winner ? ` · ${row.winner}` : ''] }), _jsx(Button, { size: "sm", disabled: measuring, onPress: () => void analyze(row.id), children: t('Analyze outcomes') })] }, row.id)), results?.error ? _jsx(Text, { role: "alert", children: say(results.error, t('this experiment'), 'read', t) }) : null, results?.analysis?.results?.map((row) => _jsxs(YStack, { gap: "$1", children: [_jsx(Text, { children: row.variant }), _jsx(Text, { color: "$soft", children: t('{converted} of {exposed} converted', { converted: row.converted, exposed: row.exposed }) })] }, row.variant)), answers.experiments?.rows?.length === 0 ? _jsx(Text, { color: "$soft", children: t('No controlled experiments yet.') }) : null] }) : _jsxs(YStack, { gap: "$3", children: [_jsx(Text, { color: "$soft", children: t('Benchmarks compare actual measurements. Requesting a benchmark result does not execute an evaluation harness.') }), state('benchmarks'), _jsx(Input, { "aria-label": t('Benchmark ID'), placeholder: t('Benchmark ID'), value: pair.benchmark, onChangeText: (benchmark) => setPair((p) => ({ ...p, benchmark })) }), _jsx(Input, { "aria-label": t('First model ID'), placeholder: t('First model ID'), value: pair.a, onChangeText: (a) => setPair((p) => ({ ...p, a })) }), _jsx(Input, { "aria-label": t('Second model ID'), placeholder: t('Second model ID'), value: pair.b, onChangeText: (b) => setPair((p) => ({ ...p, b })) }), _jsx(Button, { disabled: measuring || !pair.benchmark.trim() || !pair.a.trim() || !pair.b.trim(), onPress: () => void compare(), children: t('Compare measurements') }), results?.error ? _jsx(Text, { role: "alert", children: say(results.error, t('these benchmark results'), 'read', t) }) : null, results?.comparison ? _jsx(YStack, { gap: "$1", children: results.comparison.n_common === 0 ? _jsx(Text, { children: t('No shared measured items. Record both evaluations before comparing.') }) : _jsxs(_Fragment, { children: [_jsx(Text, { children: t('{count} shared measured items', { count: results.comparison.n_common }) }), _jsxs(Text, { children: [results.comparison.a, ": ", results.comparison.a_correct, " \u00B7 ", results.comparison.b, ": ", results.comparison.b_correct] })] }) }) : null, answers.benchmarks?.rows?.map((row) => _jsxs(YStack, { gap: "$1", p: "$3", bg: "$panel", rounded: "$3", children: [_jsx(Text, { children: row.name || row.id }), _jsx(Text, { color: "$soft", children: row.direction === 'lower' ? t('Lower is better') : t('Higher is better') }), _jsx(Button, { size: "sm", onPress: () => setPair((p) => ({ ...p, benchmark: row.id })), children: t('Use this benchmark') })] }, row.id))] })] })] }) }) });
93
+ }
package/dist/Me.js CHANGED
@@ -32,11 +32,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
32
32
  // the plan from billing and the limits. An organization is created by the same
33
33
  // `POST /v1/account/orgs` the no-organization gate uses (Orgs.tsx); the next load
34
34
  // mints a token that carries it.
35
- import { phrase } from '@hanzo/i18n';
35
+ import { phrase, locales } from '@hanzo/i18n';
36
36
  import { useI18n } from '@hanzo/i18n/react';
37
37
  import { useEffect, useMemo, useRef, useState } from 'react';
38
38
  import { SizableText, XStack, YStack } from '@hanzo/gui';
39
- import { ArrowUpRight, BookOpen, Building2, Check, ChevronsUpDown, CircleHelp, CircleUserRound, Download, FileText, FlaskConical, Info, Keyboard, LifeBuoy, LogOut, MessagesSquare, Palette, Plus, ScrollText, Settings, ShieldCheck, Sparkles, Users, X, } from 'lucide-react';
39
+ import { ArrowUpRight, BookOpen, Building2, Check, ChevronsUpDown, CircleHelp, CircleUserRound, Download, FileText, FlaskConical, Info, Keyboard, Languages, LifeBuoy, LogOut, MessagesSquare, Palette, Plus, ScrollText, Settings, ShieldCheck, Sparkles, Users, X, } from 'lucide-react';
40
40
  import { label, Meter, said as spoken, useStanding } from '@hanzo/build';
41
41
  import { useOrganizations } from '@hanzo/iam/react';
42
42
  import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, Kbd, } from '@hanzo/ui';
@@ -130,7 +130,7 @@ export function Face({ host, size = 28 }) {
130
130
  }
131
131
  /** A row's face: its icon in a 16px well, its words, and what trails them. */
132
132
  function Line({ icon, children, sub, trail }) {
133
- return (_jsxs(_Fragment, { children: [_jsx(XStack, { width: 16, height: 16, items: "center", justify: "center", shrink: 0, children: icon }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: children }), sub ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: sub })) : null] }), trail] }));
133
+ return (_jsxs(_Fragment, { children: [icon !== undefined ? (_jsx(XStack, { width: 16, height: 16, items: "center", justify: "center", shrink: 0, children: icon })) : null, _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: children }), sub ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: sub })) : null] }), trail] }));
134
134
  }
135
135
  /** An organization's mark in the list: its initials on a rounded square. */
136
136
  const Mark = ({ org }) => (_jsx(XStack, { width: 16, height: 16, rounded: "$2", items: "center", justify: "center", bg: "$raised", shrink: 0, children: org.personal ? (_jsx(SizableText, { size: "$1", fontWeight: "600", color: "$ink", children: initials(org.display).charAt(0) })) : (_jsx(Building2, { size: 12, "aria-hidden": true })) }));
@@ -143,10 +143,11 @@ export const upgrade = (family) => family === 'Enterprise' ? phrase('Enterprise
143
143
  /** The keys a row prints, in this platform's spelling. */
144
144
  const mac = () => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
145
145
  export function Me({ host, narrow = false, above = false, onOrg, }) {
146
- const { t, dir } = useI18n();
146
+ const { t, dir, locale, setLocale } = useI18n();
147
147
  const rtl = dir === 'rtl';
148
148
  const [open, setOpen] = useState(false);
149
149
  const [account, setAccount] = useState(false);
150
+ const [lang, setLang] = useState(false);
150
151
  const [help, setHelp] = useState(false);
151
152
  const [creating, setCreating] = useState(false);
152
153
  const [keys, setKeys] = useState(false);
@@ -206,6 +207,7 @@ export function Me({ host, narrow = false, above = false, onOrg, }) {
206
207
  if (next)
207
208
  return;
208
209
  setAccount(false);
210
+ setLang(false);
209
211
  setHelp(false);
210
212
  },
211
213
  // Toward the content from the rail, and aligned as the row's own edge is: mirrored right to left.
@@ -218,7 +220,9 @@ export function Me({ host, narrow = false, above = false, onOrg, }) {
218
220
  $touchable: narrow ? { width: 44, minH: 44 } : undefined, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 26 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "start", children: who }), working ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "start", children: working })) : null] }), isFree ? (_jsx(Button, { size: "xs", variant: "outline", "data-slot": "me-upgrade", px: "$2", py: "$0.5", rounded: "$3", onPress: (e) => {
219
221
  e?.stopPropagation?.();
220
222
  getPro();
221
- }, cursor: "pointer", children: t('Upgrade') })) : null, _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsxs(DropdownMenuContent, { minW: 260, maxW: "calc(100vw - 16px)", "aria-labelledby": undefined, "aria-label": t('Account'), onCloseAutoFocus: back, children: [_jsxs(DropdownMenuSub, { open: account, onOpenChange: setAccount, children: [_jsxs(DropdownMenuSubTrigger, { minH: 48, gap: "$2.5", children: [_jsx(Face, { host: host, size: 28 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", fontWeight: "600", color: "$ink", numberOfLines: 1, children: who }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: host.person.email || working })] })] }), account ? (_jsxs(DropdownMenuSubContent, { minW: 260, maxW: "calc(100vw - 16px)", pointerEvents: "auto", children: [_jsx(Meter, { read: read, onPlans: () => leave('here'), onBilling: () => (setOpen(false), showSettings('billing')) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: t('Organizations') }), all.map((o) => (_jsx(DropdownMenuItem, { onSelect: () => o.name !== current && onOrg(o.name), children: _jsx(Line, { icon: _jsx(Mark, { org: o }), sub: [o.personal ? t('Personal') : said(o.role, t), planFor(o) || (isFree && o.personal ? t('Free') : '')].filter(Boolean).join(' - '), trail: o.name === current ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: o.display }) }, o.name))), _jsx(DropdownMenuItem, { onSelect: () => setCreating(true), children: _jsx(Line, { icon: _jsx(Plus, { size: 16, "aria-hidden": true }), children: t('Create organization') }) }), _jsx(DropdownMenuItem, { onSelect: () => out(site(settings('organization'))), children: _jsx(Line, { icon: _jsx(Settings, { size: 16, "aria-hidden": true }), children: t('Organization settings') }) }), where().team ? null : here && !here.personal ? (_jsx(DropdownMenuItem, { onSelect: () => out(`${ENTRY}&org=${encodeURIComponent(here.name)}`), children: _jsx(Line, { icon: _jsx(Users, { size: 16, "aria-hidden": true }), trail: _jsx(ArrowUpRight, { size: 14, "aria-hidden": true, opacity: 0.6 }), children: t('Open in Hanzo Team') }) })) : (_jsx(DropdownMenuItem, { onSelect: () => setCreating(true), children: _jsx(Line, { icon: _jsx(Users, { size: 16, "aria-hidden": true }), children: t('Create a team') }) })), superAdmin() ? (_jsx(DropdownMenuItem, { onSelect: choose, children: _jsx(Line, { icon: _jsx(Building2, { size: 16, "aria-hidden": true }), children: t('All organizations') }) })) : null] })) : null] }), team ? (_jsx(DropdownMenuItem, { "data-slot": "me-team", onSelect: () => team.name !== current && onOrg(team.name), children: _jsx(Line, { icon: _jsx(Mark, { org: team }), trail: team.name === current ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: t('{org} · {plan}', { org: team.display, plan: teamPlan || t('No plan') }) }) })) : null, personal ? (_jsx(DropdownMenuItem, { "data-slot": "me-personal", onSelect: () => personal.name !== current && onOrg(personal.name), actionsVisibility: "always", actions: ownPlan ? undefined : (_jsx(Button, { size: "xs", variant: "outline", onPress: getPro, children: t('Get Pro') })), children: _jsx(Line, { icon: _jsx(CircleUserRound, { size: 16, "aria-hidden": true }), trail: personal.name === current ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: t('Personal · {plan}', { plan: ownPlan || t('No plan') }) }) })) : null, _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onSelect: () => leave('here'), children: _jsx(Line, { icon: _jsx(Sparkles, { size: 16, "aria-hidden": true }), children: t(plans) }) }), _jsx(DropdownMenuItem, { onSelect: () => showSettings('general'), children: _jsx(Line, { icon: _jsx(Palette, { size: 16, "aria-hidden": true }), children: t('Personalization') }) }), _jsx(DropdownMenuItem, { onSelect: () => showSettings('account'), children: _jsx(Line, { icon: _jsx(CircleUserRound, { size: 16, "aria-hidden": true }), children: t('Profile') }) }), _jsx(DropdownMenuItem, { "aria-keyshortcuts": "Control+, Meta+,", onSelect: () => showSettings('general'), children: _jsx(Line, { icon: _jsx(Settings, { size: 16, "aria-hidden": true }), trail: _jsx(DropdownMenuShortcut, { "aria-hidden": true, children: _jsx("bdi", { dir: "ltr", children: apple ? '⌘,' : 'Ctrl+,' }) }), children: t('Settings') }) }), _jsx(DropdownMenuItem, { onSelect: () => window.location.assign(site('/beta/status')), children: _jsx(Line, { icon: _jsx(FlaskConical, { size: 16, "aria-hidden": true }), children: t('Beta') }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuSub, { open: help, onOpenChange: setHelp, children: [_jsx(DropdownMenuSubTrigger, { children: _jsx(Line, { icon: _jsx(LifeBuoy, { size: 16, "aria-hidden": true }), children: t('Help') }) }), help ? (_jsxs(DropdownMenuSubContent, { minW: 240, maxW: "calc(100vw - 16px)", pointerEvents: "auto", children: [pages().map((p) => (_jsx(DropdownMenuItem, { onSelect: () => out(p.href), children: _jsx(Line, { icon: ICONS[p.key], trail: p.key === 'help' ? _jsx(ArrowUpRight, { size: 14, "aria-hidden": true, opacity: 0.6 }) : null, children: t(p.label) }) }, p.key))), _jsx(DropdownMenuItem, { "aria-keyshortcuts": "Control+/ Meta+/", onSelect: () => setKeys(true), children: _jsx(Line, { icon: _jsx(Keyboard, { size: 16, "aria-hidden": true }), trail: _jsx(DropdownMenuShortcut, { "aria-hidden": true, children: _jsx("bdi", { dir: "ltr", children: apple ? '⌘/' : 'Ctrl+/' }) }), children: t('Keyboard shortcuts') }) }), _jsx(DropdownMenuSeparator, {}), legal().map((p) => (_jsx(DropdownMenuItem, { onSelect: () => out(p.href), children: _jsx(Line, { icon: ICONS[p.key], children: t(p.label) }) }, p.key)))] })) : null] }), host.signOut ? (_jsx(DropdownMenuItem, { onSelect: () => host.signOut?.(), children: _jsx(Line, { icon: _jsx(LogOut, { size: 16, "aria-hidden": true, "data-i18n-mirror": true }), children: t('Log out') }) })) : null] })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), _jsx(Shortcuts, { open: keys, onOpenChange: setKeys, apple: apple })] }));
223
+ }, cursor: "pointer", children: t('Upgrade') })) : null, _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsxs(DropdownMenuContent, { minW: 260, maxW: "calc(100vw - 16px)", "aria-labelledby": undefined, "aria-label": t('Account'), onCloseAutoFocus: back, children: [_jsxs(DropdownMenuSub, { open: account, onOpenChange: setAccount, children: [_jsxs(DropdownMenuSubTrigger, { minH: 48, gap: "$2.5", children: [_jsx(Face, { host: host, size: 28 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", fontWeight: "600", color: "$ink", numberOfLines: 1, children: who }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: host.person.email || working })] })] }), account ? (_jsxs(DropdownMenuSubContent, { minW: 260, maxW: "calc(100vw - 16px)", pointerEvents: "auto", children: [_jsx(Meter, { read: read, onPlans: () => leave('here'), onBilling: () => (setOpen(false), showSettings('billing')) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: t('Organizations') }), all.map((o) => (_jsx(DropdownMenuItem, { onSelect: () => o.name !== current && onOrg(o.name), children: _jsx(Line, { icon: _jsx(Mark, { org: o }), sub: [o.personal ? t('Personal') : said(o.role, t), planFor(o) || (isFree && o.personal ? t('Free') : '')].filter(Boolean).join(' - '), trail: o.name === current ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: o.display }) }, o.name))), _jsx(DropdownMenuItem, { onSelect: () => setCreating(true), children: _jsx(Line, { icon: _jsx(Plus, { size: 16, "aria-hidden": true }), children: t('Create organization') }) }), _jsx(DropdownMenuItem, { onSelect: () => out(site(settings('organization'))), children: _jsx(Line, { icon: _jsx(Settings, { size: 16, "aria-hidden": true }), children: t('Organization settings') }) }), where().team ? null : here && !here.personal ? (_jsx(DropdownMenuItem, { onSelect: () => out(`${ENTRY}&org=${encodeURIComponent(here.name)}`), children: _jsx(Line, { icon: _jsx(Users, { size: 16, "aria-hidden": true }), trail: _jsx(ArrowUpRight, { size: 14, "aria-hidden": true, opacity: 0.6 }), children: t('Open in Hanzo Team') }) })) : (_jsx(DropdownMenuItem, { onSelect: () => setCreating(true), children: _jsx(Line, { icon: _jsx(Users, { size: 16, "aria-hidden": true }), children: t('Create a team') }) })), superAdmin() ? (_jsx(DropdownMenuItem, { onSelect: choose, children: _jsx(Line, { icon: _jsx(Building2, { size: 16, "aria-hidden": true }), children: t('All organizations') }) })) : null] })) : null] }), team ? (_jsx(DropdownMenuItem, { "data-slot": "me-team", onSelect: () => team.name !== current && onOrg(team.name), children: _jsx(Line, { icon: _jsx(Mark, { org: team }), trail: team.name === current ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: t('{org} · {plan}', { org: team.display, plan: teamPlan || t('No plan') }) }) })) : null, personal ? (_jsx(DropdownMenuItem, { "data-slot": "me-personal", onSelect: () => personal.name !== current && onOrg(personal.name), actionsVisibility: "always", actions: ownPlan ? undefined : (_jsx(Button, { size: "xs", variant: "outline", onPress: getPro, children: t('Get Pro') })), children: _jsx(Line, { icon: _jsx(CircleUserRound, { size: 16, "aria-hidden": true }), trail: personal.name === current ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: t('Personal · {plan}', { plan: ownPlan || t('No plan') }) }) })) : null, _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onSelect: () => leave('here'), children: _jsx(Line, { icon: _jsx(Sparkles, { size: 16, "aria-hidden": true }), children: t(plans) }) }), _jsx(DropdownMenuItem, { onSelect: () => showSettings('general'), children: _jsx(Line, { icon: _jsx(Palette, { size: 16, "aria-hidden": true }), children: t('Personalization') }) }), _jsx(DropdownMenuItem, { onSelect: () => showSettings('account'), children: _jsx(Line, { icon: _jsx(CircleUserRound, { size: 16, "aria-hidden": true }), children: t('Profile') }) }), _jsx(DropdownMenuItem, { "aria-keyshortcuts": "Control+, Meta+,", onSelect: () => showSettings('general'), children: _jsx(Line, { icon: _jsx(Settings, { size: 16, "aria-hidden": true }), trail: _jsx(DropdownMenuShortcut, { "aria-hidden": true, children: _jsx("bdi", { dir: "ltr", children: apple ? '⌘,' : 'Ctrl+,' }) }), children: t('Settings') }) }), _jsx(DropdownMenuItem, { onSelect: () => window.location.assign(site('/beta/status')), children: _jsx(Line, { icon: _jsx(FlaskConical, { size: 16, "aria-hidden": true }), children: t('Beta') }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuSub, { open: lang, onOpenChange: setLang, children: [_jsx(DropdownMenuSubTrigger, { children: _jsx(Line, { icon: _jsx(Languages, { size: 16, "aria-hidden": true }), children: t('Language') }) }), lang ? (_jsx(DropdownMenuSubContent, { minW: 220, maxW: "calc(100vw - 16px)", pointerEvents: "auto", children: locales
224
+ .filter((l) => !l.review || l.code === locale)
225
+ .map((l) => (_jsx(DropdownMenuItem, { onSelect: () => void setLocale(l.code), children: _jsx(Line, { sub: l.englishName !== l.name ? l.englishName : undefined, trail: l.code === locale ? _jsx(Check, { size: 16, "aria-label": t('Current') }) : null, children: l.name }) }, l.code))) })) : null] }), _jsxs(DropdownMenuSub, { open: help, onOpenChange: setHelp, children: [_jsx(DropdownMenuSubTrigger, { children: _jsx(Line, { icon: _jsx(LifeBuoy, { size: 16, "aria-hidden": true }), children: t('Help') }) }), help ? (_jsxs(DropdownMenuSubContent, { minW: 240, maxW: "calc(100vw - 16px)", pointerEvents: "auto", children: [pages().map((p) => (_jsx(DropdownMenuItem, { onSelect: () => out(p.href), children: _jsx(Line, { icon: ICONS[p.key], trail: p.key === 'help' ? _jsx(ArrowUpRight, { size: 14, "aria-hidden": true, opacity: 0.6 }) : null, children: t(p.label) }) }, p.key))), _jsx(DropdownMenuItem, { "aria-keyshortcuts": "Control+/ Meta+/", onSelect: () => setKeys(true), children: _jsx(Line, { icon: _jsx(Keyboard, { size: 16, "aria-hidden": true }), trail: _jsx(DropdownMenuShortcut, { "aria-hidden": true, children: _jsx("bdi", { dir: "ltr", children: apple ? '⌘/' : 'Ctrl+/' }) }), children: t('Keyboard shortcuts') }) }), _jsx(DropdownMenuSeparator, {}), legal().map((p) => (_jsx(DropdownMenuItem, { onSelect: () => out(p.href), children: _jsx(Line, { icon: ICONS[p.key], children: t(p.label) }) }, p.key)))] })) : null] }), host.signOut ? (_jsx(DropdownMenuItem, { onSelect: () => host.signOut?.(), children: _jsx(Line, { icon: _jsx(LogOut, { size: 16, "aria-hidden": true, "data-i18n-mirror": true }), children: t('Log out') }) })) : null] })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), _jsx(Shortcuts, { open: keys, onOpenChange: setKeys, apple: apple })] }));
222
226
  }
223
227
  /** The app's keys (help.ts `KEYS`), one row each, spelled for this platform. */
224
228
  function Shortcuts({ open, onOpenChange, apple }) {
package/dist/Setup.js CHANGED
@@ -4,7 +4,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
4
4
  // Runs INSIDE the app as a compact card in the sidebar column and as an interactive
5
5
  // popup modal dialog. Never a full-screen redirect away from the user's workspace.
6
6
  //
7
- // 1. GitHub: Sync repositories, PRs, and team codebase context
7
+ // 1. GitHub: Sync repositories, PRs, and team repository context
8
8
  // 2. Slack: Route notifications, agent conversations, and bot alerts
9
9
  // 3. Wallet: Web3 multi-chain wallet (MetaMask/EVM/Base/Solana) for credits & payouts
10
10
  import { useEffect, useState } from 'react';
@@ -203,5 +203,5 @@ export function Setup() {
203
203
  }
204
204
  };
205
205
  const truncate = (s) => (s.length > 12 ? `${s.slice(0, 6)}…${s.slice(-4)}` : s);
206
- return (_jsxs(_Fragment, { children: [_jsxs(YStack, { "data-slot": "setup", role: "region", "aria-label": t('Onboarding (1-2-3)'), gap: "$2.5", p: "$3", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", width: "100%", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(Sparkles, { size: 15, color: "var(--primary)", "aria-hidden": true }), _jsx(SizableText, { size: "$2", fontWeight: "600", color: "$ink", children: allDone ? t('Onboarding complete') : t('Quick setup ({n}/3)', { n: totalDone }) })] }), _jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Box, { render: "button", "aria-label": t('Expand onboarding popup'), onClick: () => setModalOpen(true), p: "$1", rounded: 999, hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Maximize2, { size: 13, "aria-hidden": true, color: "$soft" }) }), _jsx(Box, { render: "button", "aria-label": t('Dismiss'), onClick: () => setLater(true), p: "$1", rounded: 999, hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(X, { size: 13, "aria-hidden": true, color: "$soft" }) })] })] }), _jsxs(XStack, { gap: "$1", width: "100%", children: [_jsx(Box, { flex: 1, height: 4, rounded: 2, bg: githubDone ? GOOD : step === 1 ? '$ink' : '$edge' }), _jsx(Box, { flex: 1, height: 4, rounded: 2, bg: slackDone ? GOOD : step === 2 ? '$ink' : '$edge' }), _jsx(Box, { flex: 1, height: 4, rounded: 2, bg: walletDone ? GOOD : step === 3 ? '$ink' : '$edge' })] }), allDone ? (_jsxs(YStack, { gap: "$2", pt: "$1", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Check, { size: 16, color: GOOD, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", color: "$ink", fontWeight: "500", children: t('GitHub, Slack & Wallet connected') })] }), _jsx(Box, { render: "button", onClick: () => setLater(true), p: "$1.5", rounded: "$3", bg: "$edge", hoverStyle: { bg: '$hover' }, items: "center", justify: "center", cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "500", children: t('Done') }) })] })) : step === 1 ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(ProviderMark, { org: "github", size: 18 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: t('1. Connect GitHub') })] }), githubDone ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Check, { size: 12, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : null] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Sync repositories, PRs, and team codebase context.') }), _jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", children: [_jsx(Box, { render: "button", onClick: handleConnectGithub, "aria-disabled": busy === 'github', px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", opacity: busy === 'github' ? 0.6 : 1, children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'github' ? t('Connecting…') : githubDone ? t('Reconnect') : t('Connect GitHub') }) }), _jsx(Box, { render: "button", onClick: () => setStep(2), px: "$2", py: "$1", rounded: "$3", bg: "transparent", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Skip') }) })] })] })) : step === 2 ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SlackMark, { size: 18 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: t('2. Connect Slack') })] }), slackDone ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Check, { size: 12, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : null] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Route agent notifications and chat in team channels.') }), _jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", children: [_jsx(Box, { render: "button", onClick: handleConnectSlack, "aria-disabled": busy === 'slack', px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", opacity: busy === 'slack' ? 0.6 : 1, children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'slack' ? t('Connecting…') : slackDone ? t('Reconnect') : t('Connect Slack') }) }), _jsx(Box, { render: "button", onClick: () => setStep(3), px: "$2", py: "$1", rounded: "$3", bg: "transparent", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Skip') }) })] })] })) : (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Wallet, { size: 18, color: "var(--primary)" }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: t('3. Connect Wallet') })] }), walletDone ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Check, { size: 12, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: walletAddr ? truncate(walletAddr) : t('Connected') })] })) : null] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Link Web3 wallet for compute credits and rewards.') }), showWalletInput ? (_jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", children: [_jsx(Input, { value: manualWallet, onChangeText: setManualWallet, placeholder: "0x\u2026 or Solana/Base", height: 30, flex: 1, fontSize: "$1" }), _jsx(Box, { render: "button", onClick: () => void saveWalletAddress(manualWallet), px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: t('Save') }) })] })) : (_jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", flexWrap: "wrap", children: [_jsx(Box, { render: "button", onClick: handleConnectWalletWeb3, "aria-disabled": busy === 'wallet', px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'wallet' ? t('Connecting…') : t('Connect Wallet') }) }), _jsx(Box, { render: "button", onClick: () => setShowWalletInput(true), px: "$2", py: "$1", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$ink", children: t('Enter address') }) })] }))] })), errorMsg ? (_jsx(Text, { fontSize: "$1", color: "$red10", children: errorMsg })) : null] }), _jsx(Dialog, { open: modalOpen, onOpenChange: setModalOpen, children: _jsxs(DialogContent, { maxW: 520, rounded: "$6", gap: "$4", p: "$5", children: [_jsx(DialogTitle, { children: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Sparkles, { size: 20, color: "var(--primary)", "aria-hidden": true }), _jsx(Text, { fontSize: "$5", fontWeight: "600", color: "$ink", children: t('Set up {brand} in 3 steps', { brand: brand() }) })] }) }), _jsx(Text, { fontSize: "$2", color: "$soft", children: t('Connect your essential developer tools and Web3 wallet directly inside {brand()}.', { brand: brand() }) }), _jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { p: "$3", rounded: "$4", borderWidth: 1, borderColor: githubDone ? GOOD : '$borderColor', bg: "$raised", items: "center", justify: "space-between", gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$3", flex: 1, children: [_jsx(ProviderMark, { org: "github", size: 24 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: t('1. GitHub') }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Sync repositories, PRs, and code context') })] })] }), githubDone ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 14, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : (_jsx(Box, { render: "button", onClick: handleConnectGithub, px: "$3", py: "$1.5", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'github' ? t('Connecting…') : t('Connect') }) }))] }), _jsxs(XStack, { p: "$3", rounded: "$4", borderWidth: 1, borderColor: slackDone ? GOOD : '$borderColor', bg: "$raised", items: "center", justify: "space-between", gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$3", flex: 1, children: [_jsx(SlackMark, { size: 24 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: t('2. Slack') }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Notifications and agent bot chat in channels') })] })] }), slackDone ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 14, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : (_jsx(Box, { render: "button", onClick: handleConnectSlack, px: "$3", py: "$1.5", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'slack' ? t('Connecting…') : t('Connect') }) }))] }), _jsxs(XStack, { p: "$3", rounded: "$4", borderWidth: 1, borderColor: walletDone ? GOOD : '$borderColor', bg: "$raised", items: "center", justify: "space-between", gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$3", flex: 1, children: [_jsx(Wallet, { size: 24, color: "var(--primary)" }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: t('3. Web3 Wallet') }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('On-chain identity, compute credits, and rewards') })] })] }), walletDone ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 14, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: walletAddr ? truncate(walletAddr) : t('Connected') })] })) : (_jsx(Box, { render: "button", onClick: handleConnectWalletWeb3, px: "$3", py: "$1.5", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'wallet' ? t('Connecting…') : t('Connect') }) }))] })] }), _jsx(XStack, { justify: "flex-end", pt: "$2", children: _jsx(Box, { render: "button", onClick: () => setModalOpen(false), px: "$4", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "500", color: "$ink", children: t('Close') }) }) })] }) })] }));
206
+ return (_jsxs(_Fragment, { children: [_jsxs(YStack, { "data-slot": "setup", role: "region", "aria-label": t('Onboarding (1-2-3)'), gap: "$2.5", p: "$3", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", width: "100%", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(Sparkles, { size: 15, color: "var(--primary)", "aria-hidden": true }), _jsx(SizableText, { size: "$2", fontWeight: "600", color: "$ink", children: allDone ? t('Onboarding complete') : t('Quick setup ({n}/3)', { n: totalDone }) })] }), _jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Box, { render: "button", "aria-label": t('Expand onboarding popup'), onClick: () => setModalOpen(true), p: "$1", rounded: 999, hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Maximize2, { size: 13, "aria-hidden": true, color: "$soft" }) }), _jsx(Box, { render: "button", "aria-label": t('Dismiss'), onClick: () => setLater(true), p: "$1", rounded: 999, hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(X, { size: 13, "aria-hidden": true, color: "$soft" }) })] })] }), _jsxs(XStack, { gap: "$1", width: "100%", children: [_jsx(Box, { flex: 1, height: 4, rounded: 2, bg: githubDone ? GOOD : step === 1 ? '$ink' : '$edge' }), _jsx(Box, { flex: 1, height: 4, rounded: 2, bg: slackDone ? GOOD : step === 2 ? '$ink' : '$edge' }), _jsx(Box, { flex: 1, height: 4, rounded: 2, bg: walletDone ? GOOD : step === 3 ? '$ink' : '$edge' })] }), allDone ? (_jsxs(YStack, { gap: "$2", pt: "$1", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Check, { size: 16, color: GOOD, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", color: "$ink", fontWeight: "500", children: t('GitHub, Slack & Wallet connected') })] }), _jsx(Box, { render: "button", onClick: () => setLater(true), p: "$1.5", rounded: "$3", bg: "$edge", hoverStyle: { bg: '$hover' }, items: "center", justify: "center", cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "500", children: t('Done') }) })] })) : step === 1 ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(ProviderMark, { org: "github", size: 18 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: t('1. Connect GitHub') })] }), githubDone ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Check, { size: 12, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : null] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Sync repositories, PRs, and team repository context.') }), _jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", children: [_jsx(Box, { render: "button", onClick: handleConnectGithub, "aria-disabled": busy === 'github', px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", opacity: busy === 'github' ? 0.6 : 1, children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'github' ? t('Connecting…') : githubDone ? t('Reconnect') : t('Connect GitHub') }) }), _jsx(Box, { render: "button", onClick: () => setStep(2), px: "$2", py: "$1", rounded: "$3", bg: "transparent", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Skip') }) })] })] })) : step === 2 ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SlackMark, { size: 18 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: t('2. Connect Slack') })] }), slackDone ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Check, { size: 12, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : null] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Route agent notifications and chat in team channels.') }), _jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", children: [_jsx(Box, { render: "button", onClick: handleConnectSlack, "aria-disabled": busy === 'slack', px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", opacity: busy === 'slack' ? 0.6 : 1, children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'slack' ? t('Connecting…') : slackDone ? t('Reconnect') : t('Connect Slack') }) }), _jsx(Box, { render: "button", onClick: () => setStep(3), px: "$2", py: "$1", rounded: "$3", bg: "transparent", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Skip') }) })] })] })) : (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Wallet, { size: 18, color: "var(--primary)" }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: t('3. Connect Wallet') })] }), walletDone ? (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Check, { size: 12, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: walletAddr ? truncate(walletAddr) : t('Connected') })] })) : null] }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Link Web3 wallet for compute credits and rewards.') }), showWalletInput ? (_jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", children: [_jsx(Input, { value: manualWallet, onChangeText: setManualWallet, placeholder: "0x\u2026 or Solana/Base", height: 30, flex: 1, fontSize: "$1" }), _jsx(Box, { render: "button", onClick: () => void saveWalletAddress(manualWallet), px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: t('Save') }) })] })) : (_jsxs(XStack, { gap: "$1.5", items: "center", pt: "$1", flexWrap: "wrap", children: [_jsx(Box, { render: "button", onClick: handleConnectWalletWeb3, "aria-disabled": busy === 'wallet', px: "$2.5", py: "$1", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'wallet' ? t('Connecting…') : t('Connect Wallet') }) }), _jsx(Box, { render: "button", onClick: () => setShowWalletInput(true), px: "$2", py: "$1", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$ink", children: t('Enter address') }) })] }))] })), errorMsg ? (_jsx(Text, { fontSize: "$1", color: "$red10", children: errorMsg })) : null] }), _jsx(Dialog, { open: modalOpen, onOpenChange: setModalOpen, children: _jsxs(DialogContent, { maxW: 520, rounded: "$6", gap: "$4", p: "$5", children: [_jsx(DialogTitle, { children: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Sparkles, { size: 20, color: "var(--primary)", "aria-hidden": true }), _jsx(Text, { fontSize: "$5", fontWeight: "600", color: "$ink", children: t('Set up {brand} in 3 steps', { brand: brand() }) })] }) }), _jsx(Text, { fontSize: "$2", color: "$soft", children: t('Connect your essential developer tools and Web3 wallet directly inside {brand()}.', { brand: brand() }) }), _jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { p: "$3", rounded: "$4", borderWidth: 1, borderColor: githubDone ? GOOD : '$borderColor', bg: "$raised", items: "center", justify: "space-between", gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$3", flex: 1, children: [_jsx(ProviderMark, { org: "github", size: 24 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: t('1. GitHub') }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Sync repositories, PRs, and code context') })] })] }), githubDone ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 14, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : (_jsx(Box, { render: "button", onClick: handleConnectGithub, px: "$3", py: "$1.5", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'github' ? t('Connecting…') : t('Connect') }) }))] }), _jsxs(XStack, { p: "$3", rounded: "$4", borderWidth: 1, borderColor: slackDone ? GOOD : '$borderColor', bg: "$raised", items: "center", justify: "space-between", gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$3", flex: 1, children: [_jsx(SlackMark, { size: 24 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: t('2. Slack') }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('Notifications and agent bot chat in channels') })] })] }), slackDone ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 14, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: t('Connected') })] })) : (_jsx(Box, { render: "button", onClick: handleConnectSlack, px: "$3", py: "$1.5", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'slack' ? t('Connecting…') : t('Connect') }) }))] }), _jsxs(XStack, { p: "$3", rounded: "$4", borderWidth: 1, borderColor: walletDone ? GOOD : '$borderColor', bg: "$raised", items: "center", justify: "space-between", gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$3", flex: 1, children: [_jsx(Wallet, { size: 24, color: "var(--primary)" }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: t('3. Web3 Wallet') }), _jsx(Text, { fontSize: "$1", color: "$soft", children: t('On-chain identity, compute credits, and rewards') })] })] }), walletDone ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 14, color: GOOD }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: walletAddr ? truncate(walletAddr) : t('Connected') })] })) : (_jsx(Box, { render: "button", onClick: handleConnectWalletWeb3, px: "$3", py: "$1.5", rounded: "$3", bg: "var(--accent)", hoverStyle: { bg: 'var(--accent-hover)' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "var(--accent-foreground)", children: busy === 'wallet' ? t('Connecting…') : t('Connect') }) }))] })] }), _jsx(XStack, { justify: "flex-end", pt: "$2", children: _jsx(Box, { render: "button", onClick: () => setModalOpen(false), px: "$4", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$2", fontWeight: "500", color: "$ink", children: t('Close') }) }) })] }) })] }));
207
207
  }
@@ -0,0 +1,79 @@
1
+ export interface LearningStudy {
2
+ id: string;
3
+ project: string;
4
+ title: string;
5
+ question: string;
6
+ improvement?: {
7
+ baseline: string;
8
+ target: string;
9
+ };
10
+ }
11
+ export interface LearningReview {
12
+ id: string;
13
+ status: string;
14
+ action: {
15
+ rule?: string;
16
+ description?: string;
17
+ review?: {
18
+ target: string;
19
+ candidate: string;
20
+ baseline: string;
21
+ status: string;
22
+ blocks: string[];
23
+ };
24
+ };
25
+ }
26
+ export interface LearningRows {
27
+ studies: LearningStudy[];
28
+ reviews: LearningReview[];
29
+ experiments: {
30
+ id: string;
31
+ name: string;
32
+ status: string;
33
+ winner?: string;
34
+ }[];
35
+ benchmarks: {
36
+ id: string;
37
+ name: string;
38
+ direction: string;
39
+ }[];
40
+ }
41
+ export type LearningPart = keyof LearningRows;
42
+ export declare const LEARNING_PATHS: Record<LearningPart, string>;
43
+ /** Every read uses the same IAM/org headers as the rest of the workspace. */
44
+ export declare function learningRead<K extends LearningPart>(part: K, signal?: AbortSignal): Promise<LearningRows[K]>;
45
+ export interface LearningProposal {
46
+ title: string;
47
+ question: string;
48
+ baseline: string;
49
+ target: string;
50
+ metric: string;
51
+ category: string;
52
+ direction: 'higher' | 'lower';
53
+ minQuestions: number;
54
+ minGain: number;
55
+ }
56
+ /** Creating a study arms deterministic evaluation; it starts no model or rollout. */
57
+ export declare function learningCreate(proposal: LearningProposal, id: string): Promise<void>;
58
+ export interface LearningAnalysis {
59
+ winner: string;
60
+ results: {
61
+ variant: string;
62
+ exposed: number;
63
+ converted: number;
64
+ rate: number;
65
+ significant: boolean;
66
+ }[];
67
+ }
68
+ export interface LearningComparison {
69
+ a: string;
70
+ b: string;
71
+ n_common: number;
72
+ a_correct: number;
73
+ b_correct: number;
74
+ mcnemar_p: number;
75
+ }
76
+ /** Analyze measured outcomes; this never calls the live-rollout decide route. */
77
+ export declare function learningAnalyze(id: string): Promise<LearningAnalysis>;
78
+ /** Pair actual completed items. Zero common items is displayed as no evidence. */
79
+ export declare function learningCompare(benchmark: string, a: string, b: string): Promise<LearningComparison>;
@@ -0,0 +1,48 @@
1
+ import { api } from './lib/api.js';
2
+ import { scope } from './lib/session.js';
3
+ export const LEARNING_PATHS = {
4
+ studies: '/v1/research/studies',
5
+ reviews: '/v1/research/actions',
6
+ experiments: '/v1/experiment',
7
+ benchmarks: '/v1/benchmark/catalog',
8
+ };
9
+ /** Every read uses the same IAM/org headers as the rest of the workspace. */
10
+ export async function learningRead(part, signal) {
11
+ const r = await fetch(`${api()}${LEARNING_PATHS[part]}`, { headers: scope(), signal });
12
+ if (!r.ok)
13
+ throw Object.assign(new Error(`${r.status}`), { status: r.status });
14
+ const body = await r.json();
15
+ if (!Array.isArray(body.data))
16
+ throw new Error('Learning response is missing its data array');
17
+ return (part === 'reviews' ? body.data.filter((row) => row.action?.rule === 'research.improvement.v1') : body.data);
18
+ }
19
+ /** Creating a study arms deterministic evaluation; it starts no model or rollout. */
20
+ export async function learningCreate(proposal, id) {
21
+ const r = await fetch(`${api()}/v1/research/studies`, {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json', ...scope() },
24
+ body: JSON.stringify({ studies: [{ id, title: proposal.title, question: proposal.question,
25
+ improvement: { baseline: proposal.baseline, target: proposal.target, min_questions: proposal.minQuestions,
26
+ require_intervals: false, metrics: [{ category: proposal.category, metric: proposal.metric, direction: proposal.direction, min_gain: proposal.minGain, max_regression: 0 }] },
27
+ }] }),
28
+ });
29
+ if (!r.ok)
30
+ throw Object.assign(new Error(`${r.status}`), { status: r.status });
31
+ }
32
+ /** Analyze measured outcomes; this never calls the live-rollout decide route. */
33
+ export async function learningAnalyze(id) {
34
+ const r = await fetch(`${api()}/v1/experiment/${encodeURIComponent(id)}/analyze`, {
35
+ method: 'POST', headers: { 'Content-Type': 'application/json', ...scope() }, body: '{}',
36
+ });
37
+ if (!r.ok)
38
+ throw Object.assign(new Error(`${r.status}`), { status: r.status });
39
+ return await r.json();
40
+ }
41
+ /** Pair actual completed items. Zero common items is displayed as no evidence. */
42
+ export async function learningCompare(benchmark, a, b) {
43
+ const query = new URLSearchParams({ benchmark, a, b });
44
+ const r = await fetch(`${api()}/v1/benchmark/compare?${query}`, { headers: scope() });
45
+ if (!r.ok)
46
+ throw Object.assign(new Error(`${r.status}`), { status: r.status });
47
+ return await r.json();
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/app",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "Hanzo's app: the shell and its apps (Home, Chat, Inbox, Contacts, Meet, Calendar, Drive, Board, Work, Bots), once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",