@tapthrough/expo 0.0.0-stage → 0.1.0

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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -2
  3. package/app.plugin.d.ts +37 -0
  4. package/app.plugin.js +79 -0
  5. package/dist/Annotator.d.ts +14 -0
  6. package/dist/Annotator.d.ts.map +1 -0
  7. package/dist/Annotator.js +160 -0
  8. package/dist/Annotator.js.map +1 -0
  9. package/dist/Bubble.d.ts +27 -0
  10. package/dist/Bubble.d.ts.map +1 -0
  11. package/dist/Bubble.js +234 -0
  12. package/dist/Bubble.js.map +1 -0
  13. package/dist/Feedback.d.ts +14 -0
  14. package/dist/Feedback.d.ts.map +1 -0
  15. package/dist/Feedback.js +977 -0
  16. package/dist/Feedback.js.map +1 -0
  17. package/dist/Overlay.d.ts +13 -0
  18. package/dist/Overlay.d.ts.map +1 -0
  19. package/dist/Overlay.js +44 -0
  20. package/dist/Overlay.js.map +1 -0
  21. package/dist/Popover.d.ts +98 -0
  22. package/dist/Popover.d.ts.map +1 -0
  23. package/dist/Popover.js +531 -0
  24. package/dist/Popover.js.map +1 -0
  25. package/dist/Tapthrough.d.ts +27 -0
  26. package/dist/Tapthrough.d.ts.map +1 -0
  27. package/dist/Tapthrough.js +111 -0
  28. package/dist/Tapthrough.js.map +1 -0
  29. package/dist/core/annotations.d.ts +63 -0
  30. package/dist/core/annotations.d.ts.map +1 -0
  31. package/dist/core/annotations.js +76 -0
  32. package/dist/core/annotations.js.map +1 -0
  33. package/dist/core/bubble.d.ts +21 -0
  34. package/dist/core/bubble.d.ts.map +1 -0
  35. package/dist/core/bubble.js +25 -0
  36. package/dist/core/bubble.js.map +1 -0
  37. package/dist/core/controller.d.ts +46 -0
  38. package/dist/core/controller.d.ts.map +1 -0
  39. package/dist/core/controller.js +103 -0
  40. package/dist/core/controller.js.map +1 -0
  41. package/dist/core/diagnostics.d.ts +47 -0
  42. package/dist/core/diagnostics.d.ts.map +1 -0
  43. package/dist/core/diagnostics.js +101 -0
  44. package/dist/core/diagnostics.js.map +1 -0
  45. package/dist/core/feedback.d.ts +302 -0
  46. package/dist/core/feedback.d.ts.map +1 -0
  47. package/dist/core/feedback.js +345 -0
  48. package/dist/core/feedback.js.map +1 -0
  49. package/dist/core/flags.d.ts +13 -0
  50. package/dist/core/flags.d.ts.map +1 -0
  51. package/dist/core/flags.js +25 -0
  52. package/dist/core/flags.js.map +1 -0
  53. package/dist/core/ideas.d.ts +166 -0
  54. package/dist/core/ideas.d.ts.map +1 -0
  55. package/dist/core/ideas.js +159 -0
  56. package/dist/core/ideas.js.map +1 -0
  57. package/dist/core/launch.d.ts +43 -0
  58. package/dist/core/launch.d.ts.map +1 -0
  59. package/dist/core/launch.js +81 -0
  60. package/dist/core/launch.js.map +1 -0
  61. package/dist/core/reset.d.ts +17 -0
  62. package/dist/core/reset.d.ts.map +1 -0
  63. package/dist/core/reset.js +19 -0
  64. package/dist/core/reset.js.map +1 -0
  65. package/dist/core/review.d.ts +49 -0
  66. package/dist/core/review.d.ts.map +1 -0
  67. package/dist/core/review.js +92 -0
  68. package/dist/core/review.js.map +1 -0
  69. package/dist/core/url.d.ts +9 -0
  70. package/dist/core/url.d.ts.map +1 -0
  71. package/dist/core/url.js +23 -0
  72. package/dist/core/url.js.map +1 -0
  73. package/dist/hooks.d.ts +6 -0
  74. package/dist/hooks.d.ts.map +1 -0
  75. package/dist/hooks.js +13 -0
  76. package/dist/hooks.js.map +1 -0
  77. package/dist/index.d.ts +9 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +7 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/native-intent.d.ts +18 -0
  82. package/dist/native-intent.d.ts.map +1 -0
  83. package/dist/native-intent.js +32 -0
  84. package/dist/native-intent.js.map +1 -0
  85. package/dist/runtime.d.ts +24 -0
  86. package/dist/runtime.d.ts.map +1 -0
  87. package/dist/runtime.js +17 -0
  88. package/dist/runtime.js.map +1 -0
  89. package/dist/screenshot.d.ts +7 -0
  90. package/dist/screenshot.d.ts.map +1 -0
  91. package/dist/screenshot.js +48 -0
  92. package/dist/screenshot.js.map +1 -0
  93. package/dist/supabase.d.ts +20 -0
  94. package/dist/supabase.d.ts.map +1 -0
  95. package/dist/supabase.js +18 -0
  96. package/dist/supabase.js.map +1 -0
  97. package/dist/ui/native.d.ts +34 -0
  98. package/dist/ui/native.d.ts.map +1 -0
  99. package/dist/ui/native.js +76 -0
  100. package/dist/ui/native.js.map +1 -0
  101. package/dist/ui/parts.d.ts +62 -0
  102. package/dist/ui/parts.d.ts.map +1 -0
  103. package/dist/ui/parts.js +110 -0
  104. package/dist/ui/parts.js.map +1 -0
  105. package/dist/ui/theme.d.ts +90 -0
  106. package/dist/ui/theme.d.ts.map +1 -0
  107. package/dist/ui/theme.js +61 -0
  108. package/dist/ui/theme.js.map +1 -0
  109. package/package.json +82 -3
  110. package/src/Annotator.tsx +279 -0
  111. package/src/Bubble.tsx +331 -0
  112. package/src/Feedback.tsx +1739 -0
  113. package/src/Overlay.tsx +61 -0
  114. package/src/Popover.tsx +1393 -0
  115. package/src/Tapthrough.tsx +171 -0
  116. package/src/core/annotations.ts +116 -0
  117. package/src/core/bubble.ts +45 -0
  118. package/src/core/controller.ts +119 -0
  119. package/src/core/diagnostics.ts +128 -0
  120. package/src/core/feedback.ts +589 -0
  121. package/src/core/flags.ts +31 -0
  122. package/src/core/ideas.ts +337 -0
  123. package/src/core/launch.ts +119 -0
  124. package/src/core/reset.ts +28 -0
  125. package/src/core/review.ts +148 -0
  126. package/src/core/url.ts +21 -0
  127. package/src/hooks.ts +15 -0
  128. package/src/index.ts +8 -0
  129. package/src/native-intent.ts +36 -0
  130. package/src/runtime.ts +30 -0
  131. package/src/screenshot.ts +64 -0
  132. package/src/supabase.ts +27 -0
  133. package/src/ui/native.ts +116 -0
  134. package/src/ui/parts.tsx +249 -0
  135. package/src/ui/theme.ts +75 -0
@@ -0,0 +1,531 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { ActivityIndicator, Animated, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
4
+ import { resetMessage } from './core/feedback.js';
5
+ import { stepTime } from './core/ideas.js';
6
+ import { nextOpenScenario, otherStates, scenarioProgress, setupLineStates, stepId, } from './core/review.js';
7
+ import { useBackPress } from './Overlay.js';
8
+ import { useReduceMotion } from './ui/native.js';
9
+ import { Capsule, CircleButton, Icon, Surface } from './ui/parts.js';
10
+ import { SPRING, text, useColors } from './ui/theme.js';
11
+ /** Scenario reasons are written for the PR comment; drop its code marks here. */
12
+ function plain(text) {
13
+ return text?.replace(/`([^`]*)`/g, '$1');
14
+ }
15
+ function entryFor(checklist, key) {
16
+ const scenario = checklist.scenarios.find((s) => s.key === key);
17
+ if (scenario) {
18
+ return {
19
+ key,
20
+ title: scenario.title,
21
+ why: plain(scenario.why),
22
+ setup: scenario.setup,
23
+ scenario,
24
+ other: false,
25
+ };
26
+ }
27
+ const state = checklist.library?.find((s) => s.key === key);
28
+ if (state)
29
+ return { key, title: state.title, why: plain(state.why), setup: state.setup, other: true };
30
+ return undefined;
31
+ }
32
+ const navKey = (nav) => nav.view === 'list' || nav.view === 'prs'
33
+ ? nav.view
34
+ : nav.view === 'recheck' || nav.view === 'idea'
35
+ ? `${nav.view}:${nav.id}`
36
+ : nav.view === 'step'
37
+ ? `step:${nav.key}:${nav.index}`
38
+ : `${nav.view}:${nav.key}`;
39
+ /**
40
+ * The review popover: grows out of the Review button, lists what to test,
41
+ * sets the app up for a scenario and walks through its steps. The middle
42
+ * scrolls when it's long; the expand button turns it into a full-height sheet.
43
+ */
44
+ export function ReviewPopover(props) {
45
+ const { nav, full, placement, bounds, onNav, onClose } = props;
46
+ const colors = useColors();
47
+ const reduceMotion = useReduceMotion();
48
+ const shown = useRef(new Animated.Value(0)).current;
49
+ const slide = useRef(new Animated.Value(0)).current;
50
+ const fade = useRef(new Animated.Value(1)).current;
51
+ const key = navKey(nav);
52
+ const firstKey = useRef(key);
53
+ useEffect(() => {
54
+ if (reduceMotion) {
55
+ shown.setValue(1);
56
+ return;
57
+ }
58
+ Animated.spring(shown, { toValue: 1, ...SPRING, useNativeDriver: true }).start();
59
+ }, [shown, reduceMotion]);
60
+ // Deeper slides in from the right, back from the left.
61
+ // biome-ignore lint/correctness/useExhaustiveDependencies: runs when the view changes
62
+ useEffect(() => {
63
+ if (key === firstKey.current || reduceMotion)
64
+ return;
65
+ firstKey.current = '';
66
+ slide.setValue(props.direction === 'back' ? -18 : 18);
67
+ fade.setValue(0);
68
+ Animated.parallel([
69
+ Animated.timing(slide, { toValue: 0, duration: 220, useNativeDriver: true }),
70
+ Animated.timing(fade, { toValue: 1, duration: 180, useNativeDriver: true }),
71
+ ]).start();
72
+ }, [key]);
73
+ useBackPress(() => {
74
+ if (full)
75
+ props.onToggleFull();
76
+ else if (nav.view === 'list' || nav.view === 'setup')
77
+ onClose();
78
+ else
79
+ onNav({ view: 'list' }, 'back');
80
+ });
81
+ const view = renderView(props, colors);
82
+ const frame = full
83
+ ? { left: 8, right: 8, top: Math.max(bounds.insetTop - 8, 8), bottom: 8 }
84
+ : {
85
+ width: placement.width,
86
+ maxHeight: placement.maxHeight,
87
+ [placement.side]: placement.inset,
88
+ ...(placement.vertical === 'above'
89
+ ? { bottom: placement.offset }
90
+ : { top: placement.offset }),
91
+ };
92
+ return (_jsx(Animated.View, { style: [
93
+ styles.frame,
94
+ frame,
95
+ {
96
+ // No opacity here: fading a view that holds glass stops the glass rendering.
97
+ transform: [{ scale: shown.interpolate({ inputRange: [0, 1], outputRange: [0.6, 1] }) }],
98
+ transformOrigin: `${placement.side} ${placement.vertical === 'above' ? 'bottom' : 'top'}`,
99
+ },
100
+ ], accessibilityViewIsModal: full, children: _jsx(Surface, { radius: full ? 38 : 30, style: [styles.surface, full && styles.grow], children: _jsxs(Animated.View, { style: [
101
+ styles.column,
102
+ full && styles.grow,
103
+ { opacity: Animated.multiply(fade, shown), transform: [{ translateX: slide }] },
104
+ ], children: [_jsxs(View, { style: styles.header, children: [view.back ? (_jsx(CircleButton, { icon: "chevron.left", label: "Back", onPress: () => onNav({ view: 'list' }, 'back') })) : null, _jsxs(View, { style: [styles.headerText, !view.back && styles.headerTextAlone], children: [_jsx(Text, { style: [text.headline, { color: colors.label }], numberOfLines: 2, children: view.title }), view.subtitle ? (_jsx(Text, { style: [text.footnote, { color: colors.secondary }], numberOfLines: 1, children: view.subtitle })) : null] }), _jsx(CircleButton, { icon: full ? 'arrow.down.right.and.arrow.up.left' : 'arrow.up.left.and.arrow.down.right', label: full ? 'Shrink' : 'Expand to full screen', onPress: props.onToggleFull }), full ? _jsx(CircleButton, { icon: "xmark", label: "Close", onPress: onClose }) : null] }), _jsxs(ScrollView, { style: full ? styles.grow : styles.scroll, contentContainerStyle: styles.body, keyboardShouldPersistTaps: "handled", showsVerticalScrollIndicator: true, children: [view.body, props.error ? (_jsx(Text, { style: [text.footnote, { color: colors.fail }], accessibilityRole: "alert", children: props.error })) : null] }), view.footer ? (_jsx(View, { style: [styles.footer, full && styles.footerFull], children: view.footer })) : null] }) }) }));
105
+ }
106
+ function renderView(p, colors) {
107
+ const { nav, checklist } = p;
108
+ if (nav.view === 'list')
109
+ return listView(p, colors);
110
+ if (nav.view === 'prs')
111
+ return prsView(p, colors);
112
+ if (nav.view === 'idea') {
113
+ const idea = p.ideas.find((i) => i.id === nav.id);
114
+ return idea ? ideaView(p, idea, colors) : listView(p, colors);
115
+ }
116
+ if (nav.view === 'recheck') {
117
+ const item = checklist.recheck?.find((r) => r.id === nav.id);
118
+ return item ? recheckView(p, item, colors) : listView(p, colors);
119
+ }
120
+ const entry = entryFor(checklist, nav.key);
121
+ if (!entry)
122
+ return listView(p, colors);
123
+ switch (nav.view) {
124
+ case 'brief':
125
+ return briefView(p, entry, colors);
126
+ case 'setup':
127
+ return setupView(p, entry, colors);
128
+ case 'step':
129
+ return entry.scenario
130
+ ? stepView(p, entry, entry.scenario, nav.index, colors)
131
+ : listView(p, colors);
132
+ case 'done':
133
+ return doneView(p, entry, colors);
134
+ case 'explore':
135
+ return exploreView(p, entry, colors);
136
+ }
137
+ }
138
+ /* ---------------------------------- views --------------------------------- */
139
+ function listView(p, colors) {
140
+ const { checklist, results } = p;
141
+ const scenarios = checklist.scenarios.filter((s) => s.steps.length > 0);
142
+ const recheck = checklist.recheck ?? [];
143
+ const others = otherStates(checklist);
144
+ const counts = [
145
+ scenarios.length ? `${scenarios.length} to test` : null,
146
+ recheck.length ? `${recheck.length} to re-check` : null,
147
+ ].filter(Boolean);
148
+ return {
149
+ title: p.staff ? 'Feedback' : `PR #${checklist.pr}`,
150
+ subtitle: p.staff ?? (counts.join(' · ') || 'Nothing planned for this PR'),
151
+ body: (_jsxs(_Fragment, { children: [checklist.newerBuild ? _jsx(NewerRow, { build: checklist.newerBuild, colors: colors }) : null, checklist.reset ? _jsx(ResetRow, { reset: checklist.reset, colors: colors }) : null, recheck.map((item) => {
152
+ const done = p.rechecked[item.id] ?? (item.stillBrokenHere ? 'still_broken' : undefined);
153
+ return (_jsx(Row, { colors: colors, lead: done ? (_jsx(Mark, { kind: done === 'fixed' ? 'pass' : 'fail', colors: colors })) : (_jsx(Badge, { icon: "arrow.clockwise", colors: colors })), title: `Re-check: ${item.excerpt}`, subtitle: done === 'fixed' ? 'Fixed here' : done ? 'Still broken here' : reportedBy(item), onPress: () => p.onNav({ view: 'recheck', id: item.id }, 'forward') }, item.id));
154
+ }), scenarios.map((scenario) => {
155
+ const progress = scenarioProgress(scenario, results);
156
+ return (_jsx(Row, { colors: colors, lead: _jsx(ProgressMark, { progress: progress, colors: colors }), title: scenario.title, subtitle: scenarioSubtitle(scenario, progress, p.canSetUp), onPress: () => p.onNav(openScenario(scenario, results), 'forward') }, scenario.key));
157
+ }), others.length ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: [text.footnote, styles.section, { color: colors.secondary }], children: "Other states" }), others.map((state) => (_jsx(Row, { colors: colors, lead: _jsx(Badge, { icon: "arrow.right.circle", colors: colors }), title: state.title, subtitle: plain(state.why) ??
158
+ (p.canSetUp && state.setup?.length ? 'Sets itself up' : 'Look around'), onPress: () => p.onNav({ view: 'brief', key: state.key }, 'forward') }, state.key)))] })) : null, p.ideas.length ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: [text.footnote, styles.section, { color: colors.secondary }], children: "Ideas" }), p.ideas.slice(0, 5).map((idea) => (_jsx(Row, { colors: colors, lead: _jsx(Badge, { icon: "lightbulb", colors: colors, attention: idea.status === 'needs_answers' ||
159
+ idea.status === 'ready_to_test' ||
160
+ Boolean(idea.warning) }), title: ideaTitle(idea), subtitle: idea.warning
161
+ ? `${idea.statusLine} · taking longer than usual`
162
+ : idea.status === 'failed' || idea.status === 'not_set_up'
163
+ ? `${idea.statusLine} · tap to see why`
164
+ : idea.statusLine, onPress: () => p.onNav({ view: 'idea', id: idea.id }, 'forward') }, idea.id)))] })) : null, p.staff ? (p.ideas.length ? null : (_jsx(Text, { style: [text.subhead, { color: colors.secondary }], children: "Something wrong? Report it, and it becomes a GitHub issue with a screenshot. Got an idea? Claude writes it up, and can build it." }))) : (_jsx(Row, { colors: colors, lead: _jsx(Badge, { icon: "arrow.triangle.branch", colors: colors }), title: "Switch PR", subtitle: "Open another pull request\u2019s build", onPress: () => {
165
+ p.onShowPulls();
166
+ p.onNav({ view: 'prs' }, 'forward');
167
+ } })), _jsx(ReviewerName, { name: p.name, onChange: p.onNameChange, colors: colors })] })),
168
+ footer: (_jsxs(View, { style: styles.pair, children: [_jsx(Capsule, { title: "Report Issue", kind: "plain", onPress: p.onReportOther, style: styles.half }), _jsx(Capsule, { title: "Share Idea", kind: "plain", onPress: p.onShareIdea, style: styles.half })] })),
169
+ };
170
+ }
171
+ function ideaTitle(idea) {
172
+ if (idea.title)
173
+ return idea.title;
174
+ const flat = idea.body.replace(/\s+/g, ' ');
175
+ return flat.length > 60 ? `${flat.slice(0, 59)}…` : flat;
176
+ }
177
+ function ideaView(p, idea, colors) {
178
+ const busy = p.ideaBusy === idea.id;
179
+ const here = idea.prNumber !== null && idea.prNumber === p.checklist.pr;
180
+ const links = [
181
+ idea.prUrl ? { title: `PR #${idea.prNumber}`, url: idea.prUrl } : null,
182
+ idea.issueUrl ? { title: `Issue #${idea.issueNumber}`, url: idea.issueUrl } : null,
183
+ ].filter((l) => l !== null);
184
+ let footer = null;
185
+ if (idea.actions.includes('build')) {
186
+ footer = (_jsx(Capsule, { title: "Build It", busy: busy, onPress: () => p.onIdeaAction(idea.id, 'build') }));
187
+ }
188
+ else if (idea.actions.includes('approve')) {
189
+ footer = here ? (_jsx(Capsule, { title: "Approve", busy: busy, onPress: () => p.onIdeaAction(idea.id, 'approve') })) : (_jsx(Capsule, { title: "Open Build to Test", onPress: () => {
190
+ const pr = p.pulls?.find((pull) => pull.number === idea.prNumber);
191
+ if (pr?.openUrl)
192
+ p.onSwitchPr(pr);
193
+ else if (idea.previewUrl)
194
+ Linking.openURL(idea.previewUrl);
195
+ } }));
196
+ }
197
+ else if (idea.actions.includes('retry')) {
198
+ footer = (_jsx(Capsule, { title: "Try Again", kind: "plain", busy: busy, onPress: () => p.onIdeaAction(idea.id, 'retry') }));
199
+ }
200
+ return {
201
+ title: ideaTitle(idea),
202
+ subtitle: idea.statusLine,
203
+ back: true,
204
+ body: (_jsxs(_Fragment, { children: [idea.summary ? (_jsx(Text, { style: [text.subhead, { color: colors.label }], children: idea.summary })) : (_jsxs(Text, { style: [text.subhead, { color: colors.label }], children: ["\u201C", idea.body, "\u201D"] })), idea.warning ? (_jsxs(View, { style: [styles.newer, { backgroundColor: colors.fill }], children: [_jsx(Icon, { name: "flag.fill", size: 16, color: colors.fail }), _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { style: [text.footnote, { color: colors.label }], children: idea.warning }), _jsx(LinkRow, { links: [
205
+ idea.runUrl ? { title: 'View Run', url: idea.runUrl } : null,
206
+ idea.actionsUrl ? { title: 'Open Actions', url: idea.actionsUrl } : null,
207
+ ], colors: colors })] })] })) : null, idea.stages?.length ? (_jsx(Timeline, { stages: idea.stages, colors: colors })) : idea.note ? (_jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: idea.note })) : null, !idea.stages?.length && (idea.status === 'grooming' || idea.status === 'building') ? (_jsxs(View, { style: styles.pair, children: [_jsx(ActivityIndicator, { color: colors.secondary }), _jsx(Text, { style: [text.footnote, styles.rowText, { color: colors.secondary }], children: idea.status === 'grooming'
208
+ ? 'Claude is reading the code and writing it up. This takes a minute or two.'
209
+ : 'Claude is building it. You’ll get a build to test when the PR’s preview is ready.' })] })) : null, idea.actions.includes('answer') ? (_jsx(IdeaAnswers, { questions: idea.questions, busy: busy, onSend: (answers) => p.onAnswerIdea(idea.id, answers), colors: colors }, idea.id)) : null, idea.steps.length && idea.status !== 'needs_answers' ? (_jsx(Section, { title: "How to test it", colors: colors, children: _jsx(Numbered, { items: idea.steps.map((s) => ({ title: s.action, detail: s.expect })), colors: colors }) })) : null, idea.actions.includes('approve') ? (_jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: here
210
+ ? 'You’re on this idea’s build. Try the steps; if it’s right, approve it.'
211
+ : 'Open its build, try the steps, then approve it from there.' })) : null, _jsx(LinkRow, { links: [
212
+ ...links,
213
+ idea.runUrl && !idea.warning ? { title: 'Workflow Run', url: idea.runUrl } : null,
214
+ ], colors: colors })] })),
215
+ footer,
216
+ };
217
+ }
218
+ /** Small text links in a row, e.g. the issue, the PR and the workflow run. */
219
+ function LinkRow({ links, colors, }) {
220
+ const shown = links.filter((l) => l !== null);
221
+ if (!shown.length)
222
+ return null;
223
+ return (_jsx(View, { style: styles.links, children: shown.map((link) => (_jsx(Pressable, { onPress: () => Linking.openURL(link.url), accessibilityRole: "link", hitSlop: 8, children: _jsx(Text, { style: [text.footnote, styles.rowTitle, { color: colors.tint }], children: link.title }) }, link.url))) }));
224
+ }
225
+ /**
226
+ * Every step from sent to merged with its state, so it's always clear whether
227
+ * the idea reached GitHub, what's running now, and where it stopped.
228
+ */
229
+ function Timeline({ stages, colors }) {
230
+ return (_jsx(View, { style: styles.timeline, accessibilityRole: "list", children: stages.map((stage, i) => {
231
+ const time = stage.state === 'done' ? stepTime(stage.at) : null;
232
+ const last = i === stages.length - 1;
233
+ return (_jsxs(View, { style: styles.stage, accessible: true, accessibilityLabel: `${stage.title}: ${STATE_LABEL[stage.state]}${stage.detail ? `. ${stage.detail}` : ''}`, children: [_jsxs(View, { style: styles.stageRail, children: [_jsx(StageMark, { state: stage.state, colors: colors }), last ? null : (_jsx(View, { style: [
234
+ styles.stageLine,
235
+ { backgroundColor: stage.state === 'done' ? colors.pass : colors.separator },
236
+ ] }))] }), _jsxs(View, { style: [styles.rowText, !last && styles.stageGap], children: [_jsxs(View, { style: styles.stageHead, children: [_jsx(Text, { style: [
237
+ text.subhead,
238
+ stage.state === 'waiting' ? null : styles.rowTitle,
239
+ { color: stage.state === 'waiting' ? colors.secondary : colors.label },
240
+ ], children: stage.title }), time ? (_jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: time })) : null] }), stage.detail ? (_jsx(Text, { style: [
241
+ text.footnote,
242
+ { color: stage.state === 'failed' ? colors.fail : colors.secondary },
243
+ ], children: stage.detail })) : null] })] }, stage.key));
244
+ }) }));
245
+ }
246
+ const STATE_LABEL = {
247
+ done: 'done',
248
+ active: 'in progress',
249
+ waiting: 'not started',
250
+ failed: 'stopped',
251
+ };
252
+ function StageMark({ state, colors }) {
253
+ if (state === 'done')
254
+ return _jsx(Mark, { kind: "pass", colors: colors, size: 20 });
255
+ if (state === 'failed')
256
+ return _jsx(Mark, { kind: "fail", colors: colors, size: 20 });
257
+ if (state === 'active') {
258
+ return (_jsx(View, { style: styles.stageDot, children: _jsx(ActivityIndicator, { size: "small", color: colors.tint }) }));
259
+ }
260
+ return _jsx(View, { style: [styles.stageDot, styles.stageHollow, { borderColor: colors.separator }] });
261
+ }
262
+ /** One field per question; sending is enabled once every question has an answer. */
263
+ function IdeaAnswers({ questions, busy, onSend, colors, }) {
264
+ const [answers, setAnswers] = useState(() => questions.map(() => ''));
265
+ const ready = answers.every((a) => a.trim() !== '');
266
+ return (_jsxs(View, { style: styles.sectionBlock, children: [questions.map((question, i) => (
267
+ // biome-ignore lint/suspicious/noArrayIndexKey: questions are fixed for the round
268
+ _jsxs(View, { style: styles.question, children: [_jsx(Text, { style: [text.subhead, styles.rowTitle, { color: colors.label }], children: question }), _jsx(TextInput, { value: answers[i], onChangeText: (value) => setAnswers((current) => current.map((a, j) => (j === i ? value : a))), placeholder: "Your answer", placeholderTextColor: colors.secondary, multiline: true, style: [
269
+ text.subhead,
270
+ styles.nameInput,
271
+ { color: colors.label, backgroundColor: colors.fill },
272
+ ], accessibilityLabel: question })] }, i))), _jsx(Capsule, { title: "Send Answers", busy: busy, disabled: !ready, onPress: () => onSend(answers.map((a) => a.trim())) })] }));
273
+ }
274
+ function prsView(p, colors) {
275
+ const { pulls } = p;
276
+ return {
277
+ title: 'Switch PR',
278
+ subtitle: pulls ? `${pulls.length} open with a preview` : 'Loading…',
279
+ back: true,
280
+ body: !pulls ? (_jsx(ActivityIndicator, { color: colors.secondary })) : pulls.length === 0 ? (_jsx(Text, { style: [text.subhead, { color: colors.secondary }], children: "No other pull requests have a preview yet." })) : (pulls.map((pr) => (_jsx(Row, { colors: colors, lead: pr.current ? (_jsx(Mark, { kind: "pass", colors: colors })) : (_jsx(Badge, { icon: pr.fromIdea ? 'lightbulb' : 'arrow.triangle.branch', colors: colors })), title: `#${pr.number} ${pr.title}`, subtitle: pr.current
281
+ ? 'Open now'
282
+ : pr.needsNewBuild
283
+ ? 'Changes native code: needs a new build of this app'
284
+ : [pr.fromIdea ? 'From an idea' : null, pr.author, pr.draft ? 'Draft' : null]
285
+ .filter(Boolean)
286
+ .join(' · ') || 'Open its build', onPress: () => {
287
+ if (pr.current)
288
+ p.onNav({ view: 'list' }, 'back');
289
+ else if (pr.openUrl)
290
+ p.onSwitchPr(pr);
291
+ } }, pr.number)))),
292
+ };
293
+ }
294
+ /** A scenario not started opens its brief; one in progress opens its next step. */
295
+ function openScenario(scenario, results) {
296
+ const progress = scenarioProgress(scenario, results);
297
+ if (progress.checked === 0)
298
+ return { view: 'brief', key: scenario.key };
299
+ const open = scenario.steps.findIndex((step) => !results[stepId(scenario.key, step.index)]);
300
+ return open >= 0
301
+ ? { view: 'step', key: scenario.key, index: open }
302
+ : { view: 'done', key: scenario.key };
303
+ }
304
+ function scenarioSubtitle(scenario, progress, canSetUp) {
305
+ const steps = `${progress.total} step${progress.total === 1 ? '' : 's'}`;
306
+ if (progress.state === 'new') {
307
+ if (!scenario.setup?.length)
308
+ return steps;
309
+ return `${steps} · ${canSetUp ? 'sets itself up' : 'set up by hand'}`;
310
+ }
311
+ if (progress.state === 'partial')
312
+ return `${progress.checked} of ${progress.total} checked`;
313
+ if (progress.state === 'failed')
314
+ return `${progress.failed} failed`;
315
+ return 'All passed';
316
+ }
317
+ function briefView(p, entry, colors) {
318
+ const steps = entry.scenario?.steps ?? [];
319
+ const lines = entry.setup ?? [];
320
+ const auto = p.canSetUp && lines.length > 0;
321
+ const manual = !auto && lines.length > 0;
322
+ return {
323
+ title: entry.title,
324
+ subtitle: entry.other
325
+ ? 'Another state of the app'
326
+ : `${steps.length} step${steps.length === 1 ? '' : 's'}`,
327
+ back: true,
328
+ body: (_jsxs(_Fragment, { children: [entry.why ? (_jsx(Text, { style: [text.subhead, { color: colors.label }], children: entry.why })) : null, auto ? (_jsx(Section, { title: "Tapthrough will", colors: colors, children: lines.map((line) => (_jsx(SetupRow, { line: line, state: "waiting", colors: colors, plain: true }, line.auto))) })) : null, manual ? (_jsx(Section, { title: "Set up by hand", colors: colors, children: _jsx(Numbered, { items: lines.map((line) => ({ title: line.manual })), colors: colors }) })) : null, steps.length ? (_jsx(Section, { title: "You\u2019ll check", colors: colors, children: _jsx(Numbered, { items: steps.map((s) => ({ title: s.action, detail: s.expect })), colors: colors }) })) : null, auto ? (_jsx(Text, { style: [text.caption, { color: colors.secondary }], children: "This replaces the app\u2019s local data and signs out whoever is signed in." })) : null] })),
329
+ footer: auto ? (_jsxs(_Fragment, { children: [_jsx(Capsule, { title: entry.other ? 'Set Up' : 'Set Up and Start', onPress: () => p.onSetUp(entry.key) }), _jsx(Capsule, { title: "Skip Setup", kind: "plain", onPress: () => p.onStart(entry.key) })] })) : (_jsx(Capsule, { title: manual ? 'I’m Ready' : entry.other ? 'Look Around' : 'Start', onPress: () => p.onStart(entry.key) })),
330
+ };
331
+ }
332
+ function setupView(p, entry, colors) {
333
+ const lines = entry.setup ?? [];
334
+ const setup = p.setup?.key === entry.key ? p.setup : null;
335
+ const error = setup?.error;
336
+ const states = setupLineStates(lines, setup?.progress ?? 'requesting');
337
+ return {
338
+ title: error ? 'Couldn’t set up' : 'Setting up',
339
+ subtitle: entry.title,
340
+ back: Boolean(error),
341
+ body: (_jsxs(_Fragment, { children: [lines.map((line, i) => (_jsx(SetupRow, { line: line, state: error ? 'waiting' : (states[i] ?? 'waiting'), colors: colors }, line.auto))), error ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: [text.subhead, { color: colors.fail }], accessibilityRole: "alert", children: error }), _jsx(Section, { title: "Or set it up by hand", colors: colors, children: _jsx(Numbered, { items: lines.map((line) => ({ title: line.manual })), colors: colors }) })] })) : null] })),
342
+ footer: error ? (_jsxs(_Fragment, { children: [_jsx(Capsule, { title: "Try Again", onPress: () => p.onSetUp(entry.key) }), _jsx(Capsule, { title: "Continue Without Setup", kind: "plain", onPress: () => p.onStart(entry.key) })] })) : undefined,
343
+ };
344
+ }
345
+ function stepView(p, entry, scenario, index, colors) {
346
+ const step = scenario.steps[index];
347
+ if (!step)
348
+ return listView(p, colors);
349
+ const result = p.results[stepId(scenario.key, step.index)];
350
+ return {
351
+ title: entry.title,
352
+ subtitle: `Step ${index + 1} of ${scenario.steps.length}`,
353
+ back: true,
354
+ body: (_jsxs(_Fragment, { children: [_jsx(Text, { style: [text.headline, { color: colors.label }], children: step.action }), _jsxs(Text, { style: [text.subhead, { color: colors.secondary }], children: ["Expect: ", step.expect] }), step.result?.carriedFrom && result === step.result.result ? (_jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: `${step.result.result === 'pass' ? 'Passed' : step.result.result === 'fail' ? 'Failed' : 'Skipped'} on ${step.result.carriedFrom.slice(0, 7)}. Kept because later pushes didn’t change this screen.` })) : null] })),
355
+ footer: (_jsxs(View, { style: styles.pair, children: [_jsx(Capsule, { title: "Fail", kind: "gray", icon: "xmark", iconColor: colors.fail, fillColor: result === 'fail' ? colors.fail : undefined, accessibilityLabel: `Fail: ${step.action}`, onPress: () => p.onFail(scenario.key, index), style: styles.half }), _jsx(Capsule, { title: "Pass", kind: "gray", icon: "checkmark", iconColor: colors.pass, fillColor: result === 'pass' ? colors.pass : undefined, accessibilityLabel: `Pass: ${step.action}`, onPress: () => p.onPass(scenario.key, index), style: styles.half })] })),
356
+ };
357
+ }
358
+ function recheckView(p, item, colors) {
359
+ const done = p.rechecked[item.id] ?? (item.stillBrokenHere ? 'still_broken' : undefined);
360
+ return {
361
+ title: 'Re-check',
362
+ subtitle: reportedBy(item),
363
+ back: true,
364
+ body: (_jsxs(_Fragment, { children: [_jsx(Text, { style: [text.headline, { color: colors.label }], children: item.excerpt }), _jsx(Text, { style: [text.subhead, { color: colors.secondary }], children: "Try it again on this build. Is it fixed?" })] })),
365
+ footer: (_jsxs(View, { style: styles.pair, children: [_jsx(Capsule, { title: "Still Broken", kind: "gray", icon: "xmark", iconColor: colors.fail, fillColor: done === 'still_broken' ? colors.fail : undefined, onPress: () => p.onRecheck(item, 'still_broken'), style: styles.half }), _jsx(Capsule, { title: "Fixed", kind: "gray", icon: "checkmark", iconColor: colors.pass, fillColor: done === 'fixed' ? colors.pass : undefined, onPress: () => p.onRecheck(item, 'fixed'), style: styles.half })] })),
366
+ };
367
+ }
368
+ function doneView(p, entry, colors) {
369
+ const progress = entry.scenario ? scenarioProgress(entry.scenario, p.results) : null;
370
+ const next = nextOpenScenario(p.checklist, p.results, entry.key);
371
+ return {
372
+ title: entry.title,
373
+ subtitle: 'Checked',
374
+ back: true,
375
+ body: (_jsxs(View, { style: styles.done, children: [_jsx(Mark, { kind: progress?.failed ? 'fail' : 'pass', colors: colors, size: 52 }), _jsx(Text, { style: [text.title2, { color: colors.label }], children: progress?.failed ? `${progress.failed} failed` : 'All passed' }), progress ? (_jsx(Text, { style: [text.subhead, { color: colors.secondary }], children: `${progress.passed} of ${progress.total} passed` })) : null] })),
376
+ footer: (_jsxs(_Fragment, { children: [next ? (_jsx(Capsule, { title: `Next: ${next.title}`, onPress: () => p.onNav(openScenario(next, p.results), 'forward') })) : (_jsx(Capsule, { title: "All Done", onPress: p.onClose })), _jsx(Capsule, { title: "Back to List", kind: "plain", onPress: () => p.onNav({ view: 'list' }, 'back') })] })),
377
+ };
378
+ }
379
+ function exploreView(p, entry, colors) {
380
+ return {
381
+ title: entry.title,
382
+ subtitle: 'Ready to look around',
383
+ back: true,
384
+ body: (_jsx(Text, { style: [text.subhead, { color: colors.label }], children: "The app is in this state now. Look around, and report anything that seems off." })),
385
+ footer: (_jsxs(_Fragment, { children: [_jsx(Capsule, { title: "Report Something", onPress: p.onReportOther }), _jsx(Capsule, { title: "Back to List", kind: "plain", onPress: () => p.onNav({ view: 'list' }, 'back') })] })),
386
+ };
387
+ }
388
+ /* --------------------------------- pieces --------------------------------- */
389
+ function reportedBy(item) {
390
+ return [item.reporter, `reported on ${item.sha.slice(0, 7)}`].filter(Boolean).join(' · ');
391
+ }
392
+ function Row({ lead, title, subtitle, onPress, colors, }) {
393
+ return (_jsxs(Pressable, { onPress: onPress, accessibilityRole: "button", accessibilityLabel: `${title}. ${subtitle}`, style: ({ pressed }) => [styles.row, pressed && { backgroundColor: colors.fill }], children: [lead, _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { style: [text.subhead, styles.rowTitle, { color: colors.label }], numberOfLines: 2, children: title }), _jsx(Text, { style: [text.footnote, { color: colors.secondary }], numberOfLines: 1, children: subtitle })] }), _jsx(Icon, { name: "chevron.right", size: 13, color: colors.tertiary })] }));
394
+ }
395
+ function ResetRow({ reset, colors }) {
396
+ return (_jsxs(View, { style: [styles.newer, { backgroundColor: colors.fill }], children: [_jsx(Icon, { name: "arrow.clockwise", size: 20, color: colors.secondary }), _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { style: [text.subhead, styles.rowTitle, { color: colors.label }], children: "Checklist reset for this build" }), _jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: resetMessage(reset) })] })] }));
397
+ }
398
+ function NewerRow({ build, colors }) {
399
+ return (_jsxs(View, { style: [styles.newer, { backgroundColor: colors.fill }], children: [_jsx(Icon, { name: "arrow.down.circle.fill", size: 26, color: colors.tint }), _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { style: [text.subhead, styles.rowTitle, { color: colors.label }], children: build.hasFix ? 'Claude pushed a fix' : 'A newer build is ready' }), _jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: `${build.sha.slice(0, 7)} · you’re on an older build` })] }), build.openUrl ? (_jsx(Pressable, { onPress: () => Linking.openURL(build.openUrl), accessibilityRole: "link", accessibilityLabel: "Open the newer build", style: ({ pressed }) => [
400
+ styles.pill,
401
+ { backgroundColor: colors.tintFill },
402
+ pressed && styles.pressed,
403
+ ], children: _jsx(Text, { style: [text.subhead, styles.rowTitle, { color: colors.tint }], children: "Open" }) })) : null] }));
404
+ }
405
+ function Mark({ kind, colors, size = 26, }) {
406
+ return (_jsx(View, { style: [
407
+ styles.mark,
408
+ { width: size, height: size, borderRadius: size / 2 },
409
+ { backgroundColor: kind === 'pass' ? colors.pass : colors.fail },
410
+ ], children: _jsx(Icon, { name: kind === 'pass' ? 'checkmark' : 'xmark', size: size * 0.45, color: "#FFFFFF", weight: "bold" }) }));
411
+ }
412
+ function Badge({ icon, colors, attention = false, }) {
413
+ return (_jsxs(View, { style: [styles.mark, { backgroundColor: colors.tintFill }], children: [_jsx(Icon, { name: icon, size: 13, color: colors.tint }), attention ? _jsx(View, { style: styles.attention }) : null] }));
414
+ }
415
+ function ProgressMark({ progress, colors }) {
416
+ if (progress.state === 'passed')
417
+ return _jsx(Mark, { kind: "pass", colors: colors });
418
+ if (progress.state === 'failed')
419
+ return _jsx(Mark, { kind: "fail", colors: colors });
420
+ const started = progress.state === 'partial';
421
+ return (_jsx(View, { style: [styles.mark, styles.ring, { borderColor: started ? colors.tint : colors.tertiary }], children: started ? (_jsx(Text, { style: [styles.ringText, { color: colors.tint }], allowFontScaling: false, children: progress.checked })) : null }));
422
+ }
423
+ const LINE_ICON = {
424
+ account: 'person.fill',
425
+ data: 'tray.full.fill',
426
+ flag: 'flag.fill',
427
+ screen: 'arrow.right',
428
+ };
429
+ function SetupRow({ line, state, colors, plain = false, }) {
430
+ return (_jsxs(View, { style: [styles.setupRow, !plain && state === 'waiting' && styles.waiting], children: [_jsx(View, { style: [
431
+ styles.mark,
432
+ {
433
+ backgroundColor: !plain && state === 'done' ? colors.pass : plain ? colors.tintFill : colors.fill,
434
+ },
435
+ ], children: !plain && state === 'active' ? (_jsx(ActivityIndicator, { size: "small" })) : !plain && state === 'done' ? (_jsx(Icon, { name: "checkmark", size: 12, color: "#FFFFFF", weight: "bold" })) : (_jsx(Icon, { name: LINE_ICON[line.kind], size: 12, color: plain ? colors.tint : colors.secondary })) }), _jsx(Text, { style: [text.subhead, styles.rowText, { color: colors.label }], children: line.auto })] }));
436
+ }
437
+ function Section({ title, colors, children, }) {
438
+ return (_jsxs(View, { style: styles.sectionBlock, children: [_jsx(Text, { style: [text.footnote, styles.sectionTitle, { color: colors.secondary }], children: title }), children] }));
439
+ }
440
+ function Numbered({ items, colors, }) {
441
+ return (_jsx(View, { style: styles.numbered, children: items.map((item, i) => (
442
+ // biome-ignore lint/suspicious/noArrayIndexKey: a fixed list; the number is part of the content
443
+ _jsxs(View, { style: styles.numberedRow, children: [_jsx(Text, { style: [text.subhead, styles.number, { color: colors.secondary }], children: `${i + 1}.` }), _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { style: [text.subhead, { color: colors.label }], children: item.title }), item.detail ? (_jsx(Text, { style: [text.footnote, { color: colors.secondary }], children: item.detail })) : null] })] }, `${i}:${item.title}`))) }));
444
+ }
445
+ /** Who's reviewing: a slim line once known, a field until then. Shown on the PR. */
446
+ export function ReviewerName({ name, onChange, colors, }) {
447
+ const [editing, setEditing] = useState(name.trim() === '');
448
+ if (!editing) {
449
+ return (_jsxs(View, { style: styles.nameRow, children: [_jsxs(Text, { style: [text.footnote, { color: colors.secondary }], children: ["Reviewing as ", _jsx(Text, { style: { color: colors.label, fontWeight: '600' }, children: name.trim() })] }), _jsx(Pressable, { onPress: () => setEditing(true), accessibilityRole: "button", hitSlop: 12, children: _jsx(Text, { style: [text.footnote, styles.rowTitle, { color: colors.tint }], children: "Change" }) })] }));
450
+ }
451
+ return (_jsx(TextInput, { value: name, onChangeText: onChange, onBlur: () => setEditing(name.trim() === ''), placeholder: "Your name (shown on the PR)", placeholderTextColor: colors.secondary, autoCapitalize: "words", returnKeyType: "done", style: [
452
+ text.subhead,
453
+ styles.nameInput,
454
+ { color: colors.label, backgroundColor: colors.fill },
455
+ ], accessibilityLabel: "Your name" }));
456
+ }
457
+ const styles = StyleSheet.create({
458
+ frame: { position: 'absolute' },
459
+ surface: { padding: 8, flexShrink: 1 },
460
+ grow: { flex: 1 },
461
+ column: { flexShrink: 1 },
462
+ header: { flexDirection: 'row', alignItems: 'center', gap: 10, paddingBottom: 6 },
463
+ headerText: { flex: 1, minWidth: 0 },
464
+ headerTextAlone: { paddingLeft: 10 },
465
+ scroll: { flexGrow: 0, flexShrink: 1 },
466
+ body: { paddingHorizontal: 10, paddingTop: 4, paddingBottom: 10, gap: 10 },
467
+ footer: { gap: 2, paddingTop: 4 },
468
+ footerFull: { paddingBottom: 18 },
469
+ row: {
470
+ flexDirection: 'row',
471
+ alignItems: 'center',
472
+ gap: 12,
473
+ paddingVertical: 9,
474
+ paddingHorizontal: 10,
475
+ marginHorizontal: -10,
476
+ borderRadius: 22,
477
+ },
478
+ rowText: { flex: 1, minWidth: 0 },
479
+ rowTitle: { fontWeight: '600' },
480
+ section: { fontWeight: '600', marginTop: 8 },
481
+ sectionBlock: { gap: 8, marginTop: 4 },
482
+ sectionTitle: { fontWeight: '600' },
483
+ newer: {
484
+ flexDirection: 'row',
485
+ alignItems: 'center',
486
+ gap: 10,
487
+ padding: 10,
488
+ borderRadius: 22,
489
+ marginHorizontal: -4,
490
+ },
491
+ pill: { paddingVertical: 6, paddingHorizontal: 16, borderRadius: 999 },
492
+ pressed: { opacity: 0.7 },
493
+ mark: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },
494
+ ring: { borderWidth: 2, backgroundColor: 'transparent' },
495
+ ringText: { fontSize: 11, fontWeight: '700' },
496
+ setupRow: { flexDirection: 'row', alignItems: 'center', gap: 12 },
497
+ waiting: { opacity: 0.45 },
498
+ numbered: { gap: 8 },
499
+ numberedRow: { flexDirection: 'row', gap: 8 },
500
+ number: { width: 20, fontWeight: '600', fontVariant: ['tabular-nums'] },
501
+ pair: { flexDirection: 'row', gap: 8 },
502
+ half: { flex: 1 },
503
+ done: { alignItems: 'center', gap: 4, paddingVertical: 10 },
504
+ nameRow: {
505
+ flexDirection: 'row',
506
+ justifyContent: 'space-between',
507
+ alignItems: 'center',
508
+ marginTop: 6,
509
+ },
510
+ nameInput: { borderRadius: 14, paddingHorizontal: 12, paddingVertical: 10, marginTop: 6 },
511
+ question: { gap: 2 },
512
+ links: { flexDirection: 'row', flexWrap: 'wrap', gap: 14, marginTop: 4 },
513
+ timeline: { marginTop: 2 },
514
+ stage: { flexDirection: 'row', gap: 10 },
515
+ stageRail: { width: 20, alignItems: 'center' },
516
+ stageLine: { width: 2, flex: 1, marginVertical: 2, borderRadius: 1 },
517
+ stageGap: { paddingBottom: 12 },
518
+ stageHead: { flexDirection: 'row', justifyContent: 'space-between', gap: 8 },
519
+ stageDot: { width: 20, height: 20, alignItems: 'center', justifyContent: 'center' },
520
+ stageHollow: { borderWidth: 2, borderRadius: 10, width: 16, height: 16, margin: 2 },
521
+ attention: {
522
+ position: 'absolute',
523
+ top: -1,
524
+ right: -1,
525
+ width: 9,
526
+ height: 9,
527
+ borderRadius: 5,
528
+ backgroundColor: '#ecd88e',
529
+ },
530
+ });
531
+ //# sourceMappingURL=Popover.js.map