@hanzo/build 0.2.9 → 0.2.11

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.
Files changed (152) hide show
  1. package/README.md +126 -23
  2. package/lib/api/agents.d.ts +80 -0
  3. package/lib/api/agents.js +148 -0
  4. package/lib/api/billing.d.ts +205 -0
  5. package/lib/api/billing.js +300 -0
  6. package/lib/api/call.d.ts +6 -0
  7. package/lib/api/call.js +11 -0
  8. package/lib/api/capabilities.d.ts +25 -0
  9. package/lib/api/capabilities.js +40 -0
  10. package/lib/api/changes.d.ts +56 -0
  11. package/lib/api/changes.js +63 -0
  12. package/lib/api/coding.d.ts +42 -4
  13. package/lib/api/coding.js +62 -3
  14. package/lib/api/connectors.d.ts +90 -0
  15. package/lib/api/connectors.js +109 -0
  16. package/lib/api/consent.d.ts +22 -0
  17. package/lib/api/consent.js +28 -0
  18. package/lib/api/environment.d.ts +57 -0
  19. package/lib/api/environment.js +85 -0
  20. package/lib/api/git.d.ts +6 -2
  21. package/lib/api/git.js +13 -5
  22. package/lib/api/github.d.ts +13 -0
  23. package/lib/api/github.js +9 -0
  24. package/lib/api/harness.d.ts +44 -0
  25. package/lib/api/harness.js +348 -0
  26. package/lib/api/keys.d.ts +41 -0
  27. package/lib/api/keys.js +55 -0
  28. package/lib/api/machines.d.ts +56 -0
  29. package/lib/api/machines.js +84 -0
  30. package/lib/api/members.d.ts +47 -0
  31. package/lib/api/members.js +88 -0
  32. package/lib/api/memory.d.ts +25 -0
  33. package/lib/api/memory.js +34 -0
  34. package/lib/api/places.d.ts +2 -2
  35. package/lib/api/places.js +3 -26
  36. package/lib/api/plugins.d.ts +55 -0
  37. package/lib/api/plugins.js +62 -0
  38. package/lib/api/pref.d.ts +49 -0
  39. package/lib/api/pref.js +68 -0
  40. package/lib/api/profile.d.ts +21 -0
  41. package/lib/api/profile.js +40 -0
  42. package/lib/api/projects.d.ts +19 -2
  43. package/lib/api/projects.js +29 -4
  44. package/lib/api/provider.d.ts +42 -0
  45. package/lib/api/provider.js +58 -0
  46. package/lib/api/sandbox.d.ts +30 -0
  47. package/lib/api/sandbox.js +49 -0
  48. package/lib/api/sessions.d.ts +39 -4
  49. package/lib/api/sessions.js +58 -6
  50. package/lib/api/skills.d.ts +70 -0
  51. package/lib/api/skills.js +90 -0
  52. package/lib/api/tools.d.ts +34 -0
  53. package/lib/api/tools.js +37 -0
  54. package/lib/api/turn.d.ts +84 -16
  55. package/lib/api/turn.js +224 -4
  56. package/lib/api/webhooks.d.ts +60 -0
  57. package/lib/api/webhooks.js +81 -0
  58. package/lib/ask.d.ts +18 -0
  59. package/lib/ask.js +69 -0
  60. package/lib/builder.js +39 -19
  61. package/lib/customize/agents.d.ts +2 -0
  62. package/lib/customize/agents.js +125 -0
  63. package/lib/customize/connectors.d.ts +2 -0
  64. package/lib/customize/connectors.js +197 -0
  65. package/lib/customize/index.d.ts +6 -0
  66. package/lib/customize/index.js +50 -0
  67. package/lib/customize/plugins.d.ts +2 -0
  68. package/lib/customize/plugins.js +93 -0
  69. package/lib/customize/skills.d.ts +2 -0
  70. package/lib/customize/skills.js +125 -0
  71. package/lib/customize/ui.d.ts +110 -0
  72. package/lib/customize/ui.js +99 -0
  73. package/lib/desk.d.ts +8 -1
  74. package/lib/desk.js +29 -35
  75. package/lib/door.d.ts +7 -0
  76. package/lib/door.js +89 -0
  77. package/lib/environment.d.ts +15 -0
  78. package/lib/environment.js +132 -0
  79. package/lib/files.d.ts +14 -0
  80. package/lib/files.js +111 -0
  81. package/lib/find.d.ts +7 -0
  82. package/lib/find.js +77 -0
  83. package/lib/foot.d.ts +16 -0
  84. package/lib/foot.js +36 -0
  85. package/lib/forge.js +8 -4
  86. package/lib/git.d.ts +9 -0
  87. package/lib/git.js +74 -0
  88. package/lib/host.d.ts +5 -0
  89. package/lib/landing.d.ts +10 -0
  90. package/lib/landing.js +47 -14
  91. package/lib/markdown.d.ts +49 -0
  92. package/lib/markdown.js +123 -0
  93. package/lib/plans.d.ts +1 -0
  94. package/lib/plans.js +113 -0
  95. package/lib/prefs.d.ts +30 -0
  96. package/lib/prefs.js +83 -0
  97. package/lib/prose.d.ts +4 -0
  98. package/lib/prose.js +53 -0
  99. package/lib/route.d.ts +16 -1
  100. package/lib/route.js +32 -1
  101. package/lib/run.js +207 -28
  102. package/lib/section.js +2 -2
  103. package/lib/settings/account.d.ts +5 -0
  104. package/lib/settings/account.js +111 -0
  105. package/lib/settings/billing.d.ts +1 -0
  106. package/lib/settings/billing.js +104 -0
  107. package/lib/settings/capabilities.d.ts +1 -0
  108. package/lib/settings/capabilities.js +55 -0
  109. package/lib/settings/card.d.ts +31 -0
  110. package/lib/settings/card.js +105 -0
  111. package/lib/settings/code.d.ts +1 -0
  112. package/lib/settings/code.js +51 -0
  113. package/lib/settings/environments.d.ts +1 -0
  114. package/lib/settings/environments.js +53 -0
  115. package/lib/settings/general.d.ts +1 -0
  116. package/lib/settings/general.js +44 -0
  117. package/lib/settings/index.d.ts +4 -0
  118. package/lib/settings/index.js +22 -0
  119. package/lib/settings/integrations.d.ts +1 -0
  120. package/lib/settings/integrations.js +81 -0
  121. package/lib/settings/keys.d.ts +1 -0
  122. package/lib/settings/keys.js +102 -0
  123. package/lib/settings/machines.d.ts +1 -0
  124. package/lib/settings/machines.js +118 -0
  125. package/lib/settings/members.d.ts +1 -0
  126. package/lib/settings/members.js +67 -0
  127. package/lib/settings/memory.d.ts +1 -0
  128. package/lib/settings/memory.js +56 -0
  129. package/lib/settings/notifications.d.ts +1 -0
  130. package/lib/settings/notifications.js +97 -0
  131. package/lib/settings/privacy.d.ts +1 -0
  132. package/lib/settings/privacy.js +63 -0
  133. package/lib/settings/sections.d.ts +16 -0
  134. package/lib/settings/sections.js +32 -0
  135. package/lib/settings/ui.d.ts +49 -0
  136. package/lib/settings/ui.js +47 -0
  137. package/lib/settings/usage.d.ts +1 -0
  138. package/lib/settings/usage.js +114 -0
  139. package/lib/shelf.d.ts +6 -0
  140. package/lib/shelf.js +86 -16
  141. package/lib/switch.js +1 -2
  142. package/lib/transcript.d.ts +12 -0
  143. package/lib/transcript.js +56 -0
  144. package/lib/voice.d.ts +3 -1
  145. package/lib/voice.js +10 -4
  146. package/package.json +1 -1
  147. package/lib/account.d.ts +0 -14
  148. package/lib/account.js +0 -38
  149. package/lib/e2e/site.spec.d.ts +0 -1
  150. package/lib/e2e/site.spec.js +0 -125
  151. package/lib/mcp.d.ts +0 -1
  152. package/lib/mcp.js +0 -28
package/lib/landing.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * New: the empty state. A heading at the top of the column, and at the foot of
4
- * the pane the composer — where the run goes (the sandbox or one of the org's
5
- * machines), which repository and branch it starts from, and the ask itself.
4
+ * the pane the composer — where the run goes (Cloud: the sandbox with the
5
+ * codebase's environment; or one of the org's machines, under remote control),
6
+ * which repository and branch it starts from, and the ask itself.
6
7
  * Under it: attach, dictate, the mode, and the model and effort on the right.
7
8
  *
8
9
  * Sending starts a coding run and opens it. The choices a person made here are
@@ -10,17 +11,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
11
  */
11
12
  import { SizableText, XStack, YStack } from '@hanzo/gui';
12
13
  import { Cloud, Monitor } from '@hanzogui/lucide-icons-2';
14
+ import { Button } from '@hanzo/ui';
13
15
  import { ModeSelect } from '@hanzo/ui/agents';
14
16
  import { Composer, EmptyPrompt } from '@hanzo/ui/chat';
15
- import { BranchSelect, ChipSelect, HanzoMark, RepoSelect } from '@hanzo/ui/product';
17
+ import { BranchSelect, ChipSelect, FooterLink, HanzoMark, RepoSelect } from '@hanzo/ui/product';
16
18
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
17
19
  import { start, unhonoured } from './api/coding.js';
18
20
  import { asRepo, chosen, codebases, one } from './api/codebases.js';
21
+ import { read, SETUP } from './api/environment.js';
22
+ import { SetupDialog } from './environment.js';
19
23
  import { ENSO, models } from './api/models.js';
20
24
  import { ready, SANDBOX } from './api/places.js';
21
25
  import { isForge } from './choice.js';
22
26
  import { useKept, usePlaces, useRead } from './data.js';
23
27
  import { useHost, useTarget } from './host.js';
28
+ import { usePrefs } from './prefs.js';
24
29
  import { Publish } from './publish.js';
25
30
  import { path } from './route.js';
26
31
  import { Attach, compose, Dictate, Files } from './tools.js';
@@ -29,7 +34,7 @@ export const MODES = [
29
34
  { id: 'build', label: 'Build', hint: 'Edits, commits and pushes a branch' },
30
35
  { id: 'plan', label: 'Plan', hint: 'Plans the change and writes nothing' },
31
36
  ];
32
- const EFFORTS = [
37
+ export const EFFORTS = [
33
38
  { id: 'low', label: 'Low' },
34
39
  { id: 'medium', label: 'Medium' },
35
40
  { id: 'high', label: 'High' },
@@ -40,13 +45,20 @@ export function Landing({ onStarted }) {
40
45
  const host = useHost();
41
46
  const t = useTarget();
42
47
  const signed = Boolean(host.person);
43
- const [kept, keep] = useKept(`hanzo.build.new.${host.org ?? 'none'}`, FIRST);
48
+ const { prefs } = usePrefs();
49
+ const [stored, keep] = useKept(`hanzo.build.new.${host.org ?? 'none'}`, null);
50
+ // Nothing chosen here yet: start from the person's coding defaults (Settings → Code).
51
+ const kept = stored ?? { ...FIRST, ...prefs.code };
44
52
  const set = (patch) => keep({ ...kept, ...patch });
45
53
  const [draft, setDraft] = useState(kept.ask || '');
46
54
  const [files, setFiles] = useState([]);
47
55
  const [busy, setBusy] = useState(false);
48
56
  const [note, setNote] = useState('');
49
57
  const [adding, setAdding] = useState(null);
58
+ const [offering, setOffering] = useState(false);
59
+ // The repository list is read again each time it opens; Refresh reads it again while open.
60
+ const [picking, setPicking] = useState(false);
61
+ const [round, setRound] = useState(0);
50
62
  // A codebase or an issue hands its words over once. Left in the kept choice,
51
63
  // every later visit to New would put them back.
52
64
  useEffect(() => {
@@ -57,6 +69,9 @@ export function Landing({ onStarted }) {
57
69
  }, []);
58
70
  const places = usePlaces(t, signed);
59
71
  const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
72
+ // The chosen codebase's environment, so New can say when it has none yet.
73
+ const codebase = isForge(kept.repo) ? kept.repo.name : '';
74
+ const env = useRead(signed && codebase ? () => read(t, codebase) : null, null, [t, signed, codebase]);
60
75
  const place = places.value.find((p) => p.id === kept.place) ?? SANDBOX;
61
76
  const known = useRef(new Map());
62
77
  const loadRepos = useCallback(async (q) => {
@@ -110,21 +125,34 @@ export function Landing({ onStarted }) {
110
125
  setBusy(false);
111
126
  }
112
127
  };
128
+ /** Start the agent that finds this codebase's environment, and open it. A refusal is the dialog's to say. */
129
+ const setup = async () => {
130
+ const run = await start(t, { prompt: SETUP, repo: codebase, mode: 'setup' });
131
+ onStarted(run.session);
132
+ };
133
+ // Cloud is the sandbox, set up by the chosen codebase's environment.
134
+ const setUp = codebase && env.value
135
+ ? env.value.state === 'ready'
136
+ ? `${codebase} environment`
137
+ : env.value.state === 'proposed'
138
+ ? `${codebase} environment proposed`
139
+ : `no ${codebase} environment yet`
140
+ : '';
113
141
  const placeItems = useMemo(() => (places.value.length ? places.value : [SANDBOX]).map((p) => ({
114
142
  id: p.id || 'sandbox',
115
- label: p.label,
116
- hint: p.id ? [p.status, p.capacity].filter(Boolean).join(' · ') : 'The platform’s own sandbox',
143
+ label: p.id ? p.label : 'Cloud',
144
+ hint: p.id ? ['Remote control', p.status, p.capacity].filter(Boolean).join(' · ') : ['Hanzo sandbox', setUp].filter(Boolean).join(' · '),
117
145
  disabled: !ready(p),
118
146
  place: p,
119
- })), [places.value]);
147
+ })), [places.value, setUp]);
120
148
  const branch = kept.branch || kept.repo?.default_branch || 'main';
121
- const head = (_jsxs(XStack, { gap: 6, items: "center", flexWrap: "wrap", children: [_jsx(ChipSelect, { name: "Where the run runs", icon: place.id ? _jsx(Monitor, { size: 13 }) : _jsx(Cloud, { size: 13 }), label: place.id ? place.label : 'Default', chosen: placeItems.find((i) => i.place.id === place.id) ?? null, items: placeItems, onChange: (i) => set({ place: i.place.id }), placeholder: "Search machines\u2026", loading: places.loading, error: places.error?.message ?? null, footer: _jsx(SizableText, { size: "$1", color: "$soft", children: "A run on a machine uses that machine\u2019s checkout and credentials." }) }), _jsx(RepoSelect, { value: isForge(kept.repo) ? kept.repo : null, onChange: (r) => {
149
+ const head = (_jsxs(XStack, { gap: 6, items: "center", flexWrap: "wrap", children: [_jsx(ChipSelect, { name: "Where the run runs", icon: place.id ? _jsx(Monitor, { size: 13 }) : _jsx(Cloud, { size: 13 }), label: place.id ? place.label : 'Cloud', chosen: placeItems.find((i) => i.place.id === place.id) ?? null, items: placeItems, onChange: (i) => set({ place: i.place.id }), placeholder: "Search machines\u2026", loading: places.loading, error: places.error?.message ?? null, footer: _jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$1", color: "$ink", children: "Set up remote control" }), _jsxs(SizableText, { size: "$1", color: "$soft", children: ["Run", ' ', _jsx(SizableText, { size: "$1", color: "$ink", style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)' }, children: "hanzo link" }), ' ', "on a machine: it joins this list, and a run there uses its checkout and credentials."] })] }) }), _jsx(RepoSelect, { open: picking, onOpenChange: setPicking, value: isForge(kept.repo) ? kept.repo : null, onChange: (r) => {
122
150
  const row = known.current.get(r.name) ?? chosen(r);
123
151
  set({ repo: row, branch: row.default_branch });
124
152
  }, load: loadRepos, troubleshoot: {
125
- label: 'Browse codebases',
126
- onPress: () => host.go(path({ kind: 'screen', screen: 'codebases' })),
127
- }, note: "Repositories on the forge. Type to search.", action: {
153
+ label: 'Missing a GitHub repository? Bring it onto the forge',
154
+ onPress: () => host.go(path({ kind: 'screen', screen: 'sync' })),
155
+ }, connect: _jsx(XStack, { justify: "flex-end", children: _jsx(FooterLink, { label: "Refresh list", onPress: () => setRound((n) => n + 1) }) }), note: "Repositories on the forge. Type to search.", action: {
128
156
  label: 'Add to project',
129
157
  onPress: (r) => {
130
158
  const row = known.current.get(r.name);
@@ -135,7 +163,12 @@ export function Landing({ onStarted }) {
135
163
  name: r.name,
136
164
  });
137
165
  },
138
- }, placeholder: "Codebase", disabled: !signed }), isForge(kept.repo) ? _jsx(BranchSelect, { value: branch, onChange: (b) => set({ branch: b }), load: loadBranches }) : null, _jsx(Files, { files: files, onFiles: setFiles })] }));
166
+ }, placeholder: "Codebase", disabled: !signed }, round), isForge(kept.repo) ? _jsx(BranchSelect, { value: branch, onChange: (b) => set({ branch: b }), load: loadBranches }) : null, _jsx(Files, { files: files, onFiles: setFiles })] }));
139
167
  const foot = (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", flexWrap: "wrap", rowGap: "$1", children: [_jsx(Attach, { files: files, onFiles: setFiles, onNote: setNote }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote }), _jsx(ModeSelect, { modes: MODES, value: kept.mode, onChange: (m) => set({ mode: m }), bg: "transparent", minH: 24, px: "$1.5", self: "center" }), _jsx(XStack, { flex: 1 }), _jsx(ChipSelect, { quiet: true, name: "Model", label: catalog.value.find((m) => m.id === kept.model)?.label ?? 'Enso', chosen: catalog.value.find((m) => m.id === kept.model) ?? null, items: catalog.value, onChange: (m) => set({ model: m.id }), placeholder: "Search models\u2026", placement: "top-end", loading: catalog.loading, error: catalog.error?.message ?? null }), _jsx(ChipSelect, { quiet: true, name: "Effort", label: EFFORTS.find((e) => e.id === kept.effort)?.label ?? 'Medium', chosen: EFFORTS.find((e) => e.id === kept.effort) ?? null, items: EFFORTS, onChange: (e) => set({ effort: e.id }), placement: "top-end", width: 180 })] }));
140
- return (_jsxs(YStack, { flex: 1, minH: 0, minW: 0, items: "center", px: "$4", children: [_jsxs(YStack, { width: "100%", maxW: COLUMN, flex: 1, minH: 0, children: [_jsx(EmptyPrompt, { title: "What\u2019s up next?", mark: _jsx(HanzoMark, { size: 18 }), column: COLUMN }), _jsx(YStack, { flex: 1 }), _jsxs(YStack, { pb: "$2", gap: "$2", children: [note || unhonoured(kept.mode, place.id) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || unhonoured(kept.mode, place.id) })) : null, _jsx(Composer, { inline: true, value: draft, onChange: setDraft, onSend: () => void send(), busy: busy, placeholder: "Describe a task or ask a question", label: "Describe a task or ask a question", head: head, foot: foot })] })] }), _jsx(Publish, { source: adding, onClose: () => setAdding(null) })] }));
168
+ return (_jsxs(YStack, { flex: 1, minH: 0, minW: 0, items: "center", px: "$4", children: [_jsxs(YStack, { width: "100%", maxW: COLUMN, flex: 1, minH: 0, children: [_jsx(EmptyPrompt, { title: prefs.callName ? `What’s up next, ${prefs.callName}?` : 'What’s up next?', mark: _jsx(HanzoMark, { size: 18 }), column: COLUMN }), _jsx(YStack, { flex: 1 }), _jsxs(YStack, { pb: "$2", gap: "$2", children: [env.value && env.value.state !== 'ready' && !place.id ? (_jsxs(XStack, { items: "center", justify: "space-between", gap: "$3", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 0, children: env.value.state === 'proposed'
169
+ ? `A proposed environment for ${codebase} is waiting for review.`
170
+ : `${codebase} has no environment yet, so every run starts it bare.` }), env.value.state === 'proposed' && env.value.session ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(env.value.session), children: "Review" })) : (_jsx(Button, { size: "sm", onPress: () => setOffering(true), children: "Set up environment" }))] })) : null, note || unhonoured(kept.mode, place.id) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || unhonoured(kept.mode, place.id) })) : null, _jsx(Composer, { inline: true, value: draft, onChange: setDraft, onSend: () => void send(), busy: busy, placeholder: "Describe a task or ask a question", label: "Describe a task or ask a question", head: head, foot: foot })] })] }), _jsx(Publish, { source: adding, onClose: () => setAdding(null) }), codebase ? (_jsx(SetupDialog, { repo: codebase, open: offering, onOpenChange: setOffering, onStart: setup, onSaved: () => {
171
+ setOffering(false);
172
+ env.reload();
173
+ } })) : null] }));
141
174
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The markdown an agent writes, read into blocks and inline pieces: paragraphs,
3
+ * headings, lists, quotes, rules, tables and fenced code; inline code, bold,
4
+ * italics and links. What prose.tsx draws, as plain values.
5
+ *
6
+ * A link keeps an address only when it is http(s); anything else, and every
7
+ * image, is its words.
8
+ */
9
+ export type Block = {
10
+ kind: 'paragraph';
11
+ text: string;
12
+ } | {
13
+ kind: 'heading';
14
+ level: number;
15
+ text: string;
16
+ } | {
17
+ kind: 'code';
18
+ lang: string;
19
+ text: string;
20
+ } | {
21
+ kind: 'list';
22
+ ordered: boolean;
23
+ start: number;
24
+ items: {
25
+ text: string;
26
+ depth: number;
27
+ }[];
28
+ } | {
29
+ kind: 'quote';
30
+ text: string;
31
+ } | {
32
+ kind: 'rule';
33
+ } | {
34
+ kind: 'table';
35
+ head: string[];
36
+ rows: string[][];
37
+ };
38
+ /** The blocks of a markdown text, in order. */
39
+ export declare function blocks(md: string): Block[];
40
+ export type Span = {
41
+ kind: 'text' | 'code' | 'strong' | 'em';
42
+ text: string;
43
+ } | {
44
+ kind: 'link';
45
+ text: string;
46
+ href: string;
47
+ };
48
+ /** A line's inline pieces. A link to anything but http(s) is its words. */
49
+ export declare function spans(text: string): Span[];
@@ -0,0 +1,123 @@
1
+ const FENCE = /^\s{0,3}(```+|~~~+)\s*([\w+#.-]*)\s*$/;
2
+ const HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
3
+ const ITEM = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/;
4
+ const RULE = /^\s{0,3}([-*_])(\s*\1){2,}\s*$/;
5
+ const QUOTE = /^\s{0,3}>\s?(.*)$/;
6
+ const ROW = /^\s*\|(.*)\|\s*$/;
7
+ const DIVIDER = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
8
+ const cells = (line) => (ROW.exec(line)?.[1] ?? line).split('|').map((c) => c.trim());
9
+ /** The blocks of a markdown text, in order. */
10
+ export function blocks(md) {
11
+ const lines = md.replace(/\r\n?/g, '\n').split('\n');
12
+ const out = [];
13
+ let para = [];
14
+ const flush = () => {
15
+ if (para.length)
16
+ out.push({ kind: 'paragraph', text: para.join(' ').trim() });
17
+ para = [];
18
+ };
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const line = lines[i];
21
+ const fence = FENCE.exec(line);
22
+ if (fence) {
23
+ flush();
24
+ const body = [];
25
+ i++;
26
+ while (i < lines.length && !lines[i].trim().startsWith(fence[1]))
27
+ body.push(lines[i++]);
28
+ out.push({ kind: 'code', lang: fence[2] ?? '', text: body.join('\n') });
29
+ continue;
30
+ }
31
+ if (!line.trim()) {
32
+ flush();
33
+ continue;
34
+ }
35
+ const heading = HEADING.exec(line);
36
+ if (heading) {
37
+ flush();
38
+ out.push({ kind: 'heading', level: heading[1].length, text: heading[2] });
39
+ continue;
40
+ }
41
+ if (RULE.test(line)) {
42
+ flush();
43
+ out.push({ kind: 'rule' });
44
+ continue;
45
+ }
46
+ if (ROW.test(line) && DIVIDER.test(lines[i + 1] ?? '')) {
47
+ flush();
48
+ const head = cells(line);
49
+ const rows = [];
50
+ i += 2;
51
+ while (i < lines.length && ROW.test(lines[i]))
52
+ rows.push(cells(lines[i++]));
53
+ i--;
54
+ out.push({ kind: 'table', head, rows });
55
+ continue;
56
+ }
57
+ const quote = QUOTE.exec(line);
58
+ if (quote) {
59
+ flush();
60
+ const body = [quote[1]];
61
+ while (i + 1 < lines.length && QUOTE.test(lines[i + 1]))
62
+ body.push(QUOTE.exec(lines[++i])[1]);
63
+ out.push({ kind: 'quote', text: body.join(' ').trim() });
64
+ continue;
65
+ }
66
+ const item = ITEM.exec(line);
67
+ if (item) {
68
+ flush();
69
+ const ordered = /\d/.test(item[2]);
70
+ const items = [];
71
+ const start = ordered ? parseInt(item[2], 10) : 1;
72
+ let j = i;
73
+ while (j < lines.length) {
74
+ const m = ITEM.exec(lines[j]);
75
+ if (m) {
76
+ items.push({ text: m[3], depth: Math.min(3, Math.floor(m[1].replace(/\t/g, ' ').length / 2)) });
77
+ j++;
78
+ continue;
79
+ }
80
+ // A line indented under an item continues it.
81
+ if (lines[j].trim() && /^\s{2,}/.test(lines[j]) && items.length) {
82
+ items[items.length - 1].text += ` ${lines[j].trim()}`;
83
+ j++;
84
+ continue;
85
+ }
86
+ break;
87
+ }
88
+ out.push({ kind: 'list', ordered, start, items });
89
+ i = j - 1;
90
+ continue;
91
+ }
92
+ para.push(line.trim());
93
+ }
94
+ flush();
95
+ return out;
96
+ }
97
+ const INLINE = /(`+)([^`]+?)\1|!?\[([^\]]+)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)|\*\*(.+?)\*\*|__(.+?)__|\*([^*\s](?:[^*]*[^*\s])?)\*|(?<![\w])_([^_\s](?:[^_]*[^_\s])?)_(?![\w])|(https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/g;
98
+ const safe = (href) => (/^https?:\/\/[^\s]+$/i.test(href) ? href : '');
99
+ /** A line's inline pieces. A link to anything but http(s) is its words. */
100
+ export function spans(text) {
101
+ const out = [];
102
+ let at = 0;
103
+ for (const m of text.matchAll(INLINE)) {
104
+ if (m.index > at)
105
+ out.push({ kind: 'text', text: text.slice(at, m.index) });
106
+ if (m[2] !== undefined)
107
+ out.push({ kind: 'code', text: m[2] });
108
+ else if (m[3] !== undefined) {
109
+ const href = safe(m[4]);
110
+ out.push(href && !m[0].startsWith('!') ? { kind: 'link', text: m[3], href } : { kind: 'text', text: m[3] });
111
+ }
112
+ else if (m[5] !== undefined || m[6] !== undefined)
113
+ out.push({ kind: 'strong', text: (m[5] ?? m[6]) });
114
+ else if (m[7] !== undefined || m[8] !== undefined)
115
+ out.push({ kind: 'em', text: (m[7] ?? m[8]) });
116
+ else if (m[9] !== undefined)
117
+ out.push({ kind: 'link', text: m[9], href: m[9] });
118
+ at = m.index + m[0].length;
119
+ }
120
+ if (at < text.length)
121
+ out.push({ kind: 'text', text: text.slice(at) });
122
+ return out;
123
+ }
package/lib/plans.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function Plans(): import("react").JSX.Element;
package/lib/plans.js ADDED
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Plans: every plan this brand sells, priced as the checkout will charge, with
4
+ * the one the organization is on marked, and the move to another.
5
+ *
6
+ * Buying charges a saved card for the plan's first period at the catalog's
7
+ * price; the platform sells one paid plan at a time, so a move between paid
8
+ * plans is the current one ending first. Going back to Free is ending the paid
9
+ * plan at the close of the period already paid for.
10
+ */
11
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
12
+ import { Check, ChevronLeft } from '@hanzogui/lucide-icons-2';
13
+ import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
14
+ import { useState } from 'react';
15
+ import { cancel, chosen, current, label, methods, money, plans, subscribe, subscriptions } from './api/billing.js';
16
+ import { useRead } from './data.js';
17
+ import { useHost, useTarget } from './host.js';
18
+ import { path } from './route.js';
19
+ import { AddCard } from './settings/card.js';
20
+ import { day } from './settings/ui.js';
21
+ /** What a plan costs a month on a term, and what the term charges at once. */
22
+ function price(p, term) {
23
+ const month = term === 'year' && p.yearly ? p.yearly : p.monthly;
24
+ return { month, now: term === 'year' && p.yearly ? p.yearly * 12 : p.monthly };
25
+ }
26
+ export function Plans() {
27
+ const host = useHost();
28
+ const t = useTarget();
29
+ const signed = Boolean(host.person);
30
+ const list = useRead(() => plans(t), [], [t]);
31
+ const subs = useRead(signed ? () => subscriptions(t) : null, [], [t, signed]);
32
+ const cards = useRead(signed ? () => methods(t) : null, [], [t, signed]);
33
+ const [term, setTerm] = useState('month');
34
+ const [buying, setBuying] = useState(null);
35
+ const [leaving, setLeaving] = useState(false);
36
+ const [adding, setAdding] = useState(false);
37
+ const [working, setWorking] = useState(false);
38
+ const [note, setNote] = useState('');
39
+ const on = current(subs.value);
40
+ const card = chosen(cards.value);
41
+ const yearly = list.value.some((p) => p.yearly > 0);
42
+ const free = (p) => p.monthly === 0 && !p.sales;
43
+ const mine = (p) => signed && (on ? p.id === on.plan : free(p));
44
+ // Until the plan and the cards are read, no move is offered: it would be a guess.
45
+ const settled = signed && !(subs.loading && !subs.value.length) && !(cards.loading && !cards.value.length);
46
+ const act = async (what) => {
47
+ setWorking(true);
48
+ setNote('');
49
+ try {
50
+ setNote(await what());
51
+ subs.reload();
52
+ }
53
+ catch (e) {
54
+ setNote(e instanceof Error ? e.message : 'That did not work');
55
+ }
56
+ finally {
57
+ setWorking(false);
58
+ setBuying(null);
59
+ setLeaving(false);
60
+ }
61
+ };
62
+ /** The one thing a card can do, or a line saying why it does nothing. */
63
+ const move = (p) => {
64
+ if (mine(p)) {
65
+ return (_jsx(Button, { size: "sm", variant: "outline", disabled: true, children: "Current plan" }));
66
+ }
67
+ if (p.sales)
68
+ return _jsx(Line, { children: "Priced with our team for your organization." });
69
+ if (!signed)
70
+ return _jsx(Line, { children: "Sign in to choose a plan." });
71
+ if (!host.admin)
72
+ return _jsx(Line, { children: "An org admin changes the plan." });
73
+ if (!settled)
74
+ return null;
75
+ if (free(p)) {
76
+ if (!on)
77
+ return null;
78
+ if (on.ending)
79
+ return _jsx(Line, { children: `You move to ${p.name} when ${on.name} ends on ${day(on.ends)}.` });
80
+ return (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setLeaving(true), children: `Switch to ${p.name}` }));
81
+ }
82
+ if (on && on.price > 0)
83
+ return _jsx(Line, { children: `One paid plan at a time: ${on.name} ends before ${p.name} begins.` });
84
+ if (!card) {
85
+ return (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: "Add a card to upgrade" }));
86
+ }
87
+ return (_jsx(Button, { size: "sm", variant: p.popular ? 'primary' : 'outline', onPress: () => setBuying(p), children: `Upgrade to ${p.name}` }));
88
+ };
89
+ return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", children: [_jsxs(YStack, { width: "100%", maxW: 1100, mx: "auto", px: "$5", py: "$5", gap: "$5", children: [_jsxs(XStack, { render: "button", "aria-label": "Back to billing", items: "center", gap: "$1", self: "flex-start", onPress: () => host.go(path({ kind: 'settings', section: 'billing' })), children: [_jsx(ChevronLeft, { size: 16 }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Billing" })] }), _jsxs(XStack, { items: "flex-end", gap: "$4", flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minW: 240, gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$7", color: "$ink", children: "Plans that grow with you" }), _jsx(SizableText, { size: "$2", color: "$soft", children: !signed
90
+ ? 'Sign in to see the plan you are on.'
91
+ : on
92
+ ? `${host.org ?? 'This organization'} is on ${on.name}.`
93
+ : `${host.org ?? 'This organization'} is on the free plan.` })] }), yearly ? (_jsx(XStack, { role: "radiogroup", "aria-label": "Billing period", p: "$1", gap: "$1", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", children: ['month', 'year'].map((k) => (_jsx(XStack, { render: "button", role: "radio", "aria-checked": term === k, "aria-label": k === 'month' ? 'Monthly' : 'Yearly', onPress: () => setTerm(k), px: "$3", py: "$1.5", rounded: "$10", bg: term === k ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$2", color: term === k ? '$ink' : '$soft', children: k === 'month' ? 'Monthly' : 'Yearly' }) }, k))) })) : null] }), list.error ? (_jsx(Line, { children: list.error.message })) : list.loading && !list.value.length ? (_jsx(Line, { children: "Reading plans\u2026" })) : list.value.length === 0 ? (_jsx(Line, { children: "This brand sells no plans." })) : (_jsx(XStack, { flexWrap: "wrap", gap: "$4", children: list.value.map((p) => {
94
+ const cost = price(p, term);
95
+ return (_jsxs(YStack, { "aria-label": `${p.name} plan`, flex: 1, minW: 240, maxW: 360, "$max-sm": { maxW: '100%' }, gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: mine(p) || p.popular ? '$ink' : '$borderColor', bg: "$panel", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$5", color: "$ink", children: p.name }), mine(p) ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Current" })) : p.popular ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Popular" })) : null] }), p.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: p.description })) : null, _jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$7", color: "$ink", children: p.sales ? 'Custom' : cost.month === 0 ? 'Free' : money(cost.month, p.currency) }), _jsx(SizableText, { size: "$1", color: "$soft", children: p.sales
96
+ ? 'Talk to us'
97
+ : cost.month === 0
98
+ ? 'Forever'
99
+ : `${p.perSeat ? 'per seat, ' : ''}a month${term === 'year' && p.yearly ? `, ${money(cost.now, p.currency)} billed yearly` : ''}` })] }), _jsx(XStack, { children: move(p) }), p.features.length ? (_jsx(YStack, { gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", pt: "$3", children: p.features.map((f) => (_jsxs(XStack, { gap: "$2", items: "flex-start", children: [_jsx(YStack, { pt: 2, children: _jsx(Check, { size: 14 }) }), _jsx(SizableText, { flex: 1, size: "$2", color: "$soft", children: f })] }, f))) })) : null] }, p.id));
100
+ }) })), note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: note })) : null] }), _jsx(Dialog, { open: Boolean(buying), onOpenChange: (o) => (o ? undefined : setBuying(null)), children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: buying ? `Upgrade to ${buying.name}` : 'Upgrade' }), buying && card ? (_jsxs(YStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: `${label(card)} is charged ${money(price(buying, term).now, buying.currency)} now, and again every ${term} until you cancel.` }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setBuying(null), children: "Not now" }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void act(async () => {
101
+ await subscribe(t, { plan: buying.id, interval: yearly && buying.yearly ? term : 'month', method: card.id });
102
+ return `${host.org ?? 'This organization'} is on ${buying.name}`;
103
+ }), children: "Upgrade" })] })] })) : null] }) }), _jsx(Dialog, { open: leaving, onOpenChange: setLeaving, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: "Switch to the free plan?" }), on ? (_jsxs(YStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: `${on.name} stays on until ${day(on.ends) || 'the end of this period'}, and nothing is charged after that.` }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setLeaving(false), children: `Keep ${on.name}` }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => void act(async () => {
104
+ await cancel(t, on.id);
105
+ return `${on.name} ends on ${day(on.ends)}`;
106
+ }), children: "Switch" })] })] })) : null] }) }), _jsx(AddCard, { open: adding, onOpenChange: setAdding, onSaved: (m) => {
107
+ cards.reload();
108
+ setNote(`${label(m)} is saved`);
109
+ } })] }));
110
+ }
111
+ function Line({ children }) {
112
+ return (_jsx(SizableText, { size: "$2", color: "$soft", children: children }));
113
+ }
package/lib/prefs.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The person's own settings, read once for the whole builder and shared by
3
+ * everything that follows them: Settings writes them, New starts from the
4
+ * coding defaults and says the name, dictation listens in the chosen language,
5
+ * and the page takes the theme, the text size and the motion.
6
+ *
7
+ * The last document read is kept in this browser, per org, so the page is
8
+ * drawn the person's way before the platform answers. A save shows at once and
9
+ * is taken back, key by key, if the platform refuses it.
10
+ */
11
+ import { type ReactNode } from 'react';
12
+ import { type Patch, type Prefs } from './api/pref.ts';
13
+ export interface PrefState {
14
+ prefs: Prefs;
15
+ /** The first read is still in flight. */
16
+ loading: boolean;
17
+ error: Error | null;
18
+ /** Merge a change in and keep it. Throws the platform's refusal. */
19
+ save: (patch: Patch) => Promise<void>;
20
+ }
21
+ export declare function PrefsProvider({ children }: {
22
+ children: ReactNode;
23
+ }): import("react").JSX.Element;
24
+ export declare function usePrefs(): PrefState;
25
+ /** The type scale each text size is drawn at. Medium is the design system's own. */
26
+ export declare const SCALE: {
27
+ readonly small: 0.9;
28
+ readonly medium: 1;
29
+ readonly large: 1.15;
30
+ };
package/lib/prefs.js ADDED
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The person's own settings, read once for the whole builder and shared by
4
+ * everything that follows them: Settings writes them, New starts from the
5
+ * coding defaults and says the name, dictation listens in the chosen language,
6
+ * and the page takes the theme, the text size and the motion.
7
+ *
8
+ * The last document read is kept in this browser, per org, so the page is
9
+ * drawn the person's way before the platform answers. A save shows at once and
10
+ * is taken back, key by key, if the platform refuses it.
11
+ */
12
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
13
+ import { merge, read, save } from './api/pref.js';
14
+ import { useKept, useRead } from './data.js';
15
+ import { useHost, useTarget } from './host.js';
16
+ const Context = createContext(null);
17
+ export function PrefsProvider({ children }) {
18
+ const host = useHost();
19
+ const t = useTarget();
20
+ const signed = Boolean(host.person);
21
+ const [kept, keep] = useKept(`hanzo.build.pref.${host.org ?? 'none'}`, {});
22
+ const now = useRef(kept);
23
+ const doc = useRead(signed ? () => read(t) : null, null, [t, signed]);
24
+ useEffect(() => {
25
+ if (!doc.value)
26
+ return;
27
+ now.current = doc.value;
28
+ keep(doc.value);
29
+ }, [doc.value, keep]);
30
+ const write = useCallback(async (patch) => {
31
+ if (!signed)
32
+ throw new Error('Sign in to save your settings.');
33
+ const before = now.current;
34
+ now.current = merge(before, patch);
35
+ keep(now.current);
36
+ try {
37
+ now.current = await save(t, patch);
38
+ keep(now.current);
39
+ }
40
+ catch (e) {
41
+ // Only the keys this change named go back; another change since stands.
42
+ const undo = {};
43
+ for (const k of Object.keys(patch))
44
+ undo[k] = before[k] ?? null;
45
+ now.current = merge(now.current, undo);
46
+ keep(now.current);
47
+ throw e;
48
+ }
49
+ }, [signed, t, keep]);
50
+ const value = useMemo(() => ({ prefs: kept, loading: doc.loading, error: doc.error, save: write }), [kept, doc.loading, doc.error, write]);
51
+ return (_jsxs(Context.Provider, { value: value, children: [_jsx(Look, { prefs: kept }), children] }));
52
+ }
53
+ export function usePrefs() {
54
+ const p = useContext(Context);
55
+ if (!p)
56
+ throw new Error('The builder was rendered without its settings');
57
+ return p;
58
+ }
59
+ /** The type scale each text size is drawn at. Medium is the design system's own. */
60
+ export const SCALE = { small: 0.9, medium: 1, large: 1.15 };
61
+ /** Every animation and transition finished at once, the way the system setting does it. */
62
+ const STILL = '*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}';
63
+ /**
64
+ * The saved theme handed to the host, and the text size and motion put on the
65
+ * page. A stylesheet rather than a property on <html>: the design system's own
66
+ * root re-applies its appearance on mount, and a rule marked important outlasts
67
+ * that, while the builder is on the page and no longer.
68
+ */
69
+ function Look({ prefs }) {
70
+ const host = useHost();
71
+ const { theme, text, motion } = prefs;
72
+ useEffect(() => {
73
+ if (theme && theme !== host.theme)
74
+ host.chooseTheme?.(theme);
75
+ // The host's theme is followed, not watched: a choice made elsewhere stands until this one changes.
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, [theme]);
78
+ const rules = [
79
+ text && text !== 'medium' ? `html:root{--type-scale:${SCALE[text]}!important}` : '',
80
+ motion === 'reduced' ? STILL : '',
81
+ ].join('');
82
+ return rules ? _jsx("style", { "data-hanzo-build": "look", children: rules }) : null;
83
+ }
package/lib/prose.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** A markdown text, drawn. */
2
+ export declare function Prose({ text }: {
3
+ text: string;
4
+ }): import("react").JSX.Element;
package/lib/prose.js ADDED
@@ -0,0 +1,53 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Markdown an agent writes, drawn as text (markdown.ts reads it).
4
+ *
5
+ * @hanzo/ui leaves the markdown pipeline to the surface, and the builder needs
6
+ * only the part agents use. Nothing here produces HTML from the text: every
7
+ * piece is a React element holding a string, and a link is drawn only for an
8
+ * http(s) address, opened in a new tab.
9
+ */
10
+ import { ScrollView, SizableText, XStack, YStack } from '@hanzo/gui';
11
+ import { Code } from '@hanzo/ui/chat';
12
+ import { blocks, spans } from './markdown.js';
13
+ import { Out } from './out.js';
14
+ const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
15
+ function Inline({ text }) {
16
+ return (_jsx(_Fragment, { children: spans(text).map((s, i) => {
17
+ switch (s.kind) {
18
+ case 'code':
19
+ return (_jsx(SizableText, { size: "$2", color: "$ink", bg: "$hover", px: "$1", rounded: "$1", style: mono, children: s.text }, i));
20
+ case 'strong':
21
+ return (_jsx(SizableText, { size: "$3", color: "$ink", fontWeight: "600", children: s.text }, i));
22
+ case 'em':
23
+ return (_jsx(SizableText, { size: "$3", color: "$ink", fontStyle: "italic", children: s.text }, i));
24
+ case 'link':
25
+ return (_jsx(Out, { href: s.href, children: _jsx(SizableText, { size: "$3", color: "$ink", textDecorationLine: "underline", children: s.text }) }, i));
26
+ default:
27
+ return s.text;
28
+ }
29
+ }) }));
30
+ }
31
+ const SIZES = ['$6', '$5', '$4', '$4', '$3', '$3'];
32
+ function Draw({ block }) {
33
+ switch (block.kind) {
34
+ case 'heading':
35
+ return (_jsx(SizableText, { size: SIZES[block.level - 1] ?? '$3', color: "$ink", fontWeight: "600", pt: "$1", children: _jsx(Inline, { text: block.text }) }));
36
+ case 'code':
37
+ return (_jsx(Code, { language: block.lang || 'text', value: block.text, children: block.text }));
38
+ case 'rule':
39
+ return _jsx(YStack, { height: 1, bg: "$borderColor", my: "$1" });
40
+ case 'quote':
41
+ return (_jsx(YStack, { borderLeftWidth: 2, borderColor: "$borderColor", pl: "$3", children: _jsx(SizableText, { size: "$3", color: "$soft", children: _jsx(Inline, { text: block.text }) }) }));
42
+ case 'list':
43
+ return (_jsx(YStack, { gap: "$1", role: "list", children: block.items.map((item, i) => (_jsxs(XStack, { gap: "$2", pl: item.depth * 16, role: "listitem", children: [_jsx(SizableText, { size: "$3", color: "$soft", minW: 16, style: { textAlign: 'right' }, children: block.ordered ? `${block.start + i}.` : '•' }), _jsx(SizableText, { flex: 1, minW: 0, size: "$3", color: "$ink", children: _jsx(Inline, { text: item.text }) })] }, i))) }));
44
+ case 'table':
45
+ return (_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", minW: "100%", children: [block.head, ...block.rows].map((row, r) => (_jsx(XStack, { borderTopWidth: r ? 1 : 0, borderColor: "$borderColor", bg: r ? 'transparent' : '$hover', children: block.head.map((_, c) => (_jsx(SizableText, { flex: 1, minW: 96, px: "$2", py: "$1.5", size: "$2", color: "$ink", fontWeight: r ? '400' : '600', children: _jsx(Inline, { text: row[c] ?? '' }) }, c))) }, r))) }) }));
46
+ default:
47
+ return (_jsx(SizableText, { size: "$3", color: "$ink", children: _jsx(Inline, { text: block.text }) }));
48
+ }
49
+ }
50
+ /** A markdown text, drawn. */
51
+ export function Prose({ text }) {
52
+ return (_jsx(YStack, { gap: "$2.5", minW: 0, children: blocks(text).map((b, i) => (_jsx(Draw, { block: b }, i))) }));
53
+ }