SLOPSHOPPER

decision-tree

When Claude ends a reply with a question, explains the choice in a side pane and lets you explore any option, or your own, in a real forked session

newpanecommandtoastmodelprocess
★ 67v0.2.1MITupdated 2026-10-15ray-amjad/claude-code-mods/plugins/decision-tree
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · decision-tree
│ ┃ question ✕ › fix the failing auth test and add an audit log call │ ┃ Waiting for Claude to ask you something. │ ⏺ Read(src/auth.ts) │ ⎿ Read 6 lines │ ⏺ Update(src/auth.ts) │ ⎿ Added 2 lines, removed 1 line │ ⏺ Bash(bun test) │ ⎿ 3 pass, 1 fail │ │ ● Done. refresh now rejects expired claims and logs an audit event. │ │ ✻ Worked for 42s · done 4:20 PM │ │ › /explain │ ⎿ decision-tree: No question to explain yet. │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · question
Waiting for Claude to ask you something.
README

decision-tree

A Claude Code mod that turns the question at the end of Claude's reply into a side pane you can explore: every option with what it leads to, and a real forked session for any answer you want to try before you commit.

decision-tree in a session: Claude's reply lists three storage options and asks which one to use, and the Decision tree pane beside it shows the question, one box per option with what it leads to and its pros and cons, SQLite in green as recommended, and an Other box for your own answer, above the four steps from Claude asking to you exploring

After every turn:

  1. A regex looks at the end of the reply. A question mark in the last paragraph, or two or more numbered or lettered options. Most turns stop here, with no model call.
  2. Haiku confirms it. It answers YES if you have to decide something before work can go on, NO for "anything else?" and other pleasantries.
  3. A fork explains the choice. $.model.fork replays the session's own transcript (served from the prompt cache), so it knows everything Claude knew. It has no tools and replies with JSON: the question, the context you need, each option with what it leads to and its pros and cons, and a recommendation.
  4. The pane draws it. One box per option, the recommended one in green, and an Explore button on each. The last box is Other: type your own answer and press Enter.

You answer Claude in the prompt as usual. Nothing in the pane goes back to Claude.

Exploring

The pane on a branch: "If you pick SQLite" with the steps the forked session would take and its session id, under it the map of every question and option explored, and below the window a graph of the main session with two forks off its question, one for SQLite and one for your own "use Redis" answer, each reaching its own next question

Explore forks the real session at the message that asked the question:

claude -p --resume <session> --resume-session-at <that message> --fork-session

The fork is told you picked that option and carries on with read-only tools (Read, Grep, Glob). In 12 to 20 seconds the pane shows what it would do, its steps, and the next question it would ask you. Explore again from there to go deeper: each level forks the branch above it.

  • Your own answer: the Other box, or /explain other <answer>, adds it as an option and explores it like the rest.
  • Map: every question and option you explored as one tree. Press a row to jump to it.
  • Continue branch: opens the branch as a live session with claude --resume <branch> --fork-session, in a new Herdr pane when you're inside Herdr, or copied to your clipboard otherwise.

Every branch is its own session file, so the main conversation never sees any of it.

Commands

CommandWhat it does
/explainOpen the pane for the latest question
/explain bExplore option B (A to H)
/explain other <answer>Explore your own answer
/explain mapShow the map
/explain back, /explain topMove up the tree
/explain continueOpen the current branch as a live session

Install

/plugin install decision-tree --marketplace ray-amjad/claude-code-mods

Answer y to add the marketplace, then pick a scope.

Settings

OptionDefaultWhat it does
gateModelhaikuThe model that answers YES or NO

Limits

  • Claude Code only opens a pane nobody asked for in a terminal at least 144 columns wide. In a narrower one you get a toast; run /explain and the pane opens above the prompt.
  • The tree lives in the mod's memory. A new question, a restart or a reload clears it; the branch sessions stay on disk and can still be resumed by id.
  • --resume-session-at is an undocumented Claude Code flag (tested on 2.1.296). If the mod can't find the message to cut at, it forks from the latest point and says so.
  • Each branch is a full claude -p run with a turn's cost, and its session file stays in the project's history.

Develop

claude --plugin-dir ./decision-tree
claude plugin validate .

In claude -p, set DECISION_TREE_WAIT=1 so the run waits for the fork before it exits.

Source 1 files
hooks/register.jsx 425 lines
1// decision-tree: when Claude's reply ends by asking you something, a fork of the
2// conversation explains the choice in a side pane. Then you can explore
3// any option: the mod forks the real session at that question, plays the answer out in
4// the fork, and shows the next question. Each branch is its own session, so nothing you
5// explore reaches the main conversation.
6
7const PANE = 'question'
8
9// The answer every explanation is asked for, so the pane can draw it
10const SHAPE = '{"question": "the question in one plain line", ' +
11  '"context": "one or two sentences: what I need to know to decide", ' +
12  '"options": [{"label": "2 to 5 words", "detail": "what it means", ' +
13  '"leadsTo": "what happens next if I pick it", "pros": ["short"], "cons": ["short"]}], ' +
14  '"recommendation": {"label": "the label of one option", "why": "one sentence"}}'
15
16// The tree for the latest question. A node is { sid, msgId, answer, status, data, children }:
17// sid and msgId say which session, cut at which message, a branch below it forks from
18let root = null
19// Option indexes from the root to the node the pane shows
20let path = []
21// What the pane shows: 'node' (one question) or 'map' (the whole tree)
22let view = 'node'
23// Where the session transcripts live, from the Stop hook
24let transcriptPath = ''
25
26// Cheap check, no model: does the end of the reply ask something?
27// A question mark in the last paragraph, or a numbered / lettered / bulleted list of options.
28function looksLikeQuestion(answer) {
29  const tail = answer.trim().slice(-1500)
30  const paragraphs = tail.split(/\n\s*\n/)
31  const last = paragraphs[paragraphs.length - 1] || ''
32  if (/\?\s*(\*\*|_|`)?\s*$/.test(last) || /\?\s*$/m.test(last)) return true
33  const options = tail.match(/^\s*(\d+[.)]|[A-D][.):]|[-*]\s+\*\*Option|Option [A-D1-9])/gm) || []
34  return options.length >= 2 && /\b(which|prefer|choose|pick|want|should I|let me know)\b/i.test(tail)
35}
36
37// The gate: a small model confirms the question needs a real decision from the user
38async function needsDecision($, answer, model) {
39  const r = await $.model.complete({
40    model,
41    system: 'You read the end of an AI coding assistant reply. Answer YES if it ends by asking ' +
42      'the user to decide, choose, or supply something before work can go on. Answer NO for ' +
43      'rhetorical questions, pleasantries such as "anything else?", or a reply that just reports. ' +
44      'Reply with YES or NO only.',
45    prompt: answer.trim().slice(-2000),
46    maxTokens: 5,
47    timeoutMs: 20000,
48  })
49  return r.isAnswered && /^\s*yes/i.test(r.text)
50}
51
52// The first explanation: a model fork replays the conversation and returns the choice as JSON
53async function explainRoot($, answer) {
54  const node = { sid: await $.session.id(), answer, status: 'thinking', children: [] }
55  root = node
56  path = []
57  view = 'node'
58  await openPane($, false)
59  $.ui.invalidate('ui.render')
60
61  const r = await $.model.fork({
62    prompt: 'Your last reply ended by asking me a question. Do not answer it and do not use tools. ' +
63      'Help me answer it. Reply with JSON only, no code fence, in this shape: ' + SHAPE + '. ' +
64      'Use the options you offered; if you offered none, give the 2 to 4 answers I would most ' +
65      'likely pick. At most 2 pros and 2 cons each. No em or en dashes.',
66  })
67  settle(node, r.isAnswered ? r.text : '', r.isAnswered ? 'the fork did not return JSON' : r.reason)
68  if (root !== node) return
69  $.ui.invalidate('ui.render')
70}
71
72// Explore option i of the node the pane shows: fork the real session at that node's
73// question, tell the fork we picked i, and let it play that out with read-only tools.
74// With own, the answer is yours: it joins the question's options first, then is explored
75async function explore($, i, own) {
76  const tree = root
77  const node = nodeAt(path)
78  if (own && node && node.data && node.data.options) {
79    const options = node.data.options
80    i = options.findIndex(o => o.label.toLowerCase() === own.toLowerCase())
81    if (i < 0) i = options.push({ label: own, mine: true }) - 1
82  }
83  const option = node && node.data && node.data.options && node.data.options[i]
84  if (!option) return $.ui.toast('decision-tree: no option ' + String.fromCharCode(65 + i) + ' here')
85  path = [...path, i]
86  view = 'node'
87  $.ui.invalidate('ui.render')
88  if (node.children[i]) return
89
90  const child = { label: option.label, status: 'exploring', children: [], startedAt: Date.now() }
91  node.children[i] = child
92
93  // The message to cut the parent session at: the reply that asked this question
94  if (!node.msgId) node.msgId = await lastAssistantId($, node.sid, node.answer)
95  if (!node.msgId) $.ui.log('decision-tree: message id not found, forking from the latest point')
96  const args = ['claude', '-p', whatIf(option),
97    '--resume', node.sid, '--fork-session', '--output-format', 'json',
98    '--permission-mode', 'default', '--allowedTools=Read,Grep,Glob',
99    '--disallowedTools=Bash,Edit,Write,NotebookEdit']
100  if (node.msgId) args.push('--resume-session-at', node.msgId)
101
102  let out = null
103  try {
104    const run = await $.process.run(args, {
105      cwd: await $.session.cwd(),
106      env: { DECISION_TREE_CHILD: '1' },
107      timeoutMs: 300000,
108    })
109    out = resultOf(run.stdout)
110    if (!out) child.reason = (run.stderr || run.stdout || 'no output').slice(0, 300)
111  } catch (err) {
112    child.reason = String(err)
113  }
114  if (root !== tree) return
115  if (out) {
116    child.sid = out.session_id
117    child.answer = out.result
118    settle(child, out.result || '', 'the branch did not return JSON')
119  } else {
120    child.status = 'failed'
121  }
122  child.ms = Date.now() - child.startedAt
123  $.ui.invalidate('ui.render')
124}
125
126// The prompt a branch gets: our answer, then the same JSON as the root plus what happened
127function whatIf(option) {
128  return 'WHAT-IF BRANCH: this is a forked copy of the session for exploring one answer. ' +
129    'The real session is not affected. My answer to your question: "' + option.label + '"' +
130    (option.detail ? ' (' + option.detail + ')' : '') + '. Carry on as if I picked it, but change no files and run no ' +
131    'commands; you may Read, Grep and Glob to make the plan concrete. Then reply with JSON only, ' +
132    'no code fence, in this shape, plus two more fields: "outcome" (two to four sentences: what ' +
133    'you would do and how things stand after) and "steps" (up to 4 short concrete steps). ' +
134    'The question and options are the NEXT decision you would need from me after this; if there ' +
135    'is none, give an empty question and options []. Shape: ' + SHAPE + '. No em or en dashes.'
136}
137
138// Turn a reply into the node's data, or mark the node failed
139function settle(node, text, reason) {
140  const data = parseJson(text)
141  if (data && Array.isArray(data.options)) {
142    node.data = data
143    node.status = 'ready'
144  } else {
145    node.status = 'failed'
146    node.reason = node.reason || reason
147  }
148}
149
150// claude -p --output-format json prints the result object, or the whole event list
151function resultOf(stdout) {
152  try {
153    const out = JSON.parse(stdout)
154    const res = Array.isArray(out) ? out.find(x => x.type === 'result') : out
155    return res && res.session_id ? res : null
156  } catch {
157    return null
158  }
159}
160
161// Find the id of the assistant message that asked the question, in the session's transcript
162async function lastAssistantId($, sid, answer) {
163  // The Stop hook's transcript path names the folder; without it, rebuild it from the cwd
164  // (a Windows path uses \, and a native Windows terminal sets USERPROFILE, not HOME)
165  const home = (await $.env.get('HOME')) || (await $.env.get('USERPROFILE'))
166  const folder = transcriptPath ? transcriptPath.replace(/[\\/][^\\/]+$/, '') :
167    home + '/.claude/projects/' + (await $.session.cwd()).replace(/[^a-zA-Z0-9]/g, '-')
168  const file = folder + '/' + sid + '.jsonl'
169  if (!(await $.fs.exists(file))) return undefined
170  // A transcript past $.fs.read's 4 MiB cannot be searched: fork from the latest point
171  const lines = (await $.fs.read(file).catch(() => '')).split('\n')
172  const want = (answer || '').trim().slice(-60)
173  for (let n = lines.length - 1; n >= 0; n--) {
174    let row
175    try { row = JSON.parse(lines[n]) } catch { continue }
176    if (row.type !== 'assistant' || row.isSidechain) continue
177    const text = (row.message.content || []).filter(b => b.type === 'text').map(b => b.text).join('')
178    if (text && text.trim().endsWith(want)) return row.uuid
179  }
180  return undefined
181}
182
183// The map: one row per question and per option, drawn as a tree with the path to each row
184// Question: Which storage?
185// ├ A JSON file on disk ▸
186// │ └ Q: How should I build it?
187// └ B SQLite ★
188function mapRows(node, p, prefix, rows) {
189  const data = node.data
190  if (!data || !data.question) return rows
191  rows.push({ text: prefix + 'Q: ' + data.question, path: p, kind: 'question' })
192  const pick = (data.recommendation && data.recommendation.label) || ''
193  data.options.forEach((o, i) => {
194    const last = i === data.options.length - 1
195    const child = node.children[i]
196    const mark = !child ? '' : child.status === 'exploring' ? '  ...' : child.status === 'failed' ? '  ✗' : '  ▸'
197    rows.push({
198      text: prefix + (last ? '└ ' : '├ ') + String.fromCharCode(65 + i) + ' ' + o.label + (o.label === pick ? ' ★' : '') + (o.mine ? ' ✎' : '') + mark,
199      path: child ? [...p, i] : null,
200      kind: child ? 'explored' : 'option',
201    })
202    if (child && child.status === 'ready') mapRows(child, [...p, i], prefix + (last ? '  ' : '│ '), rows)
203  })
204  return rows
205}
206
207function nodeAt(p) {
208  let node = root
209  for (const i of p) node = node && node.children[i]
210  return node
211}
212
213// Continue a branch for real: a new Claude session forked from it, in a Herdr pane if there is one
214async function continueBranch($, node) {
215  if (!node || !node.sid) return
216  const command = 'claude --resume ' + node.sid + ' --fork-session'
217  if ((await $.env.get('HERDR_ENV')) === '1') {
218    try {
219      const split = await $.process.run(['herdr', 'pane', 'split', '--current', '--direction', 'down',
220        '--cwd', await $.session.cwd(), '--no-focus'])
221      const id = JSON.parse(split.stdout).result.pane.pane_id
222      await $.process.run(['herdr', 'pane', 'run', id, command])
223      return $.ui.toast('decision-tree: branch opened in pane ' + id)
224    } catch (err) {
225      $.ui.log('could not open a Herdr pane: ' + err)
226    }
227  }
228  await $.ui.copy({ text: command })
229  $.ui.toast('decision-tree: copied ' + command)
230}
231
232// Open the side pane. Unasked, Claude Code only places it on a wide terminal.
233async function openPane($, asked) {
234  // focus takes only true, so leave it out for an unasked open
235  const pane = { id: PANE, title: 'Decision tree' }
236  // Asked from /explain: take the keys, Escape closes it, and ask for room above the prompt
237  const opened = await $.ui.open(asked ? { ...pane, focus: true, closeOnEscape: true, rows: 30 } : pane)
238  if (!opened.isPlaced) $.ui.toast('decision-tree: run /explain to see the pane')
239}
240
241// The fork may wrap its JSON in prose or a fence; take the outermost braces
242function parseJson(text) {
243  const start = text.indexOf('{')
244  const end = text.lastIndexOf('}')
245  if (start < 0 || end <= start) return null
246  try {
247    return JSON.parse(text.slice(start, end + 1))
248  } catch {
249    return null
250  }
251}
252
253export function register(on, options) {
254  on('session.start', async ($, e, next) => {
255    await $.command.register({
256      name: 'explain',
257      description: 'Show the decision tree; A to H explores an option, other <answer> explores your own',
258      argumentHint: '[A-H | other <answer> | map | back | top | continue]',
259      immediate: true,
260    })
261    return next(e)
262  })
263
264  // Remember where transcripts are written, to find message ids to fork at
265  on('classic.Stop', async ($, e, next) => {
266    if (e.transcript_path) transcriptPath = e.transcript_path
267    return next(e)
268  }).catch(($, e, next) => (next.called ? undefined : next(e)))
269
270  // After each of Claude's replies: regex check, then the gate, then the fork
271  on('turn.complete', async ($, e, next) => {
272    const result = await next(e)
273    // A branch's own claude -p run never explains its own replies
274    if ((await $.env.get('DECISION_TREE_CHILD')) === '1') return result
275    if (e.agentId || e.reason !== 'answer' || !looksLikeQuestion(e.answer)) return result
276    if (!(await needsDecision($, e.answer, options.gateModel || 'haiku'))) return result
277    // Run the fork without holding up the prompt; in a -p run, wait so the process lives on
278    const work = explainRoot($, e.answer).catch(err => $.ui.log('decision-tree failed: ' + err))
279    if ((await $.env.get('DECISION_TREE_WAIT')) === '1') await work
280    return result
281  })
282
283  // /explain opens the pane; /explain B explores option B; back, top and continue move around
284  on('command.run', { command: 'explain' }, async ($, e) => {
285    if (!root) return { text: 'No question to explain yet.' }
286    const raw = (e.args || '').trim()
287    const arg = raw.toLowerCase()
288    view = arg === 'map' ? 'map' : 'node'
289    if (arg === 'back') path = path.slice(0, -1)
290    else if (arg === 'top') path = []
291    else if (arg === 'continue') await continueBranch($, nodeAt(path))
292    else if (/^[a-h]$/.test(arg) || /^other\s+\S/.test(arg)) {
293      const own = /^other\s/.test(arg) ? raw.replace(/^other\s+/i, '') : undefined
294      const work = explore($, own ? -1 : arg.charCodeAt(0) - 97, own).catch(err => $.ui.log('explore failed: ' + err))
295      if ((await $.env.get('DECISION_TREE_WAIT')) === '1') await work
296    }
297    $.ui.invalidate('ui.render')
298    await openPane($, true)
299    return {}
300  })
301
302  // The pane: where you are in the tree, what the branch did, then the question and its options
303  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
304    const { Box, Text, Button, Input } = $.ui.resolve(e)
305    const width = Math.max(30, (e.props.bodyColumns || 60) - 2)
306    const node = nodeAt(path)
307
308    if (!root || !node) return <Text dimColor>Waiting for Claude to ask you something.</Text>
309
310    // The breadcrumb: Question › B SQLite › A WAL mode
311    const crumbs = ['Question']
312    let walk = root
313    for (const i of path) {
314      crumbs.push(String.fromCharCode(65 + i) + ' ' + walk.children[i].label)
315      walk = walk.children[i]
316    }
317    const nav = (
318      <Box gap={2} marginTop={1}>
319        {path.length ? <Button key="back" label="Back" onPress={() => { path = path.slice(0, -1); $.ui.invalidate('ui.render') }} /> : null}
320        {path.length > 1 ? <Button key="top" label="Top" onPress={() => { path = []; $.ui.invalidate('ui.render') }} /> : null}
321        {node.sid && path.length ? <Button key="cont" label="Continue branch" onPress={() => continueBranch($, node)} /> : null}
322        <Button key="map" label="Map" onPress={() => { view = 'map'; $.ui.invalidate('ui.render') }} />
323        <Button key="close" label="Close" onPress={() => $.ui.close({ id: PANE })} />
324      </Box>
325    )
326
327    if (node.status === 'thinking') return <Text color="yellow">Reading the conversation to explain the question...</Text>
328
329    // The map: the whole tree at once. Press a question or an explored option to open it.
330    if (view === 'map') {
331      const here = path.join('.')
332      return (
333        <Box flexDirection="column" width={width}>
334          <Text bold>Map · ★ recommended · ▸ explored · ✎ yours</Text>
335          <Text>{' '}</Text>
336          {mapRows(root, [], '', []).map((row, n) => {
337            // Mark the option you are on, or the first question when you are at the top
338            const isHere = row.path && row.path.join('.') === here && (row.kind !== 'question' || !row.path.length)
339            const text = (isHere ? '● ' : '  ') + row.text
340            if (!row.path) return <Text key={'r' + n} dimColor>{text}</Text>
341            return <Button key={'r' + n} plain label={text}
342              onPress={() => { path = row.path; view = 'node'; $.ui.invalidate('ui.render') }} />
343          })}
344          <Box gap={2} marginTop={1}>
345            <Button key="node" label="Back to question" onPress={() => { view = 'node'; $.ui.invalidate('ui.render') }} />
346            <Button key="close" label="Close" onPress={() => $.ui.close({ id: PANE })} />
347          </Box>
348        </Box>
349      )
350    }
351    if (node.status === 'exploring') {
352      return (
353        <Box flexDirection="column" width={width}>
354          <Text dimColor>{crumbs.join(' › ')}</Text>
355          <Text color="yellow">{'Forking the session and playing out "' + node.label + '"... (read-only, about 20 to 60s)'}</Text>
356          {nav}
357        </Box>
358      )
359    }
360    if (node.status === 'failed') {
361      return (
362        <Box flexDirection="column" width={width}>
363          <Text dimColor>{crumbs.join(' › ')}</Text>
364          <Text color="red">Could not explain it: {String(node.reason)}</Text>
365          {nav}
366        </Box>
367      )
368    }
369
370    const data = node.data
371    const pick = (data.recommendation && data.recommendation.label) || ''
372    return (
373      <Box flexDirection="column" width={width}>
374        <Text dimColor>{crumbs.join(' › ')}</Text>
375        {path.length ? (
376          <Box borderStyle="quote" borderColor="magenta" paddingLeft={1} flexDirection="column" marginBottom={1}>
377            <Text bold color="magenta">{'If you pick ' + node.label}</Text>
378            {data.outcome ? <Text>{data.outcome}</Text> : null}
379            {(data.steps || []).map((s, j) => <Text key={'s' + j} color="blue">{(j + 1) + '. ' + s}</Text>)}
380            <Text dimColor>{'branch session ' + String(node.sid).slice(0, 8) + (node.ms ? ' · ' + Math.round(node.ms / 1000) + 's' : '')}</Text>
381          </Box>
382        ) : null}
383        {data.question ? (
384          <Box borderStyle="double" borderColor="cyan" paddingX={1} flexDirection="column">
385            <Text bold color="cyan">{data.question}</Text>
386            {data.context ? <Text dimColor>{data.context}</Text> : null}
387          </Box>
388        ) : <Text dimColor>No further decision on this branch.</Text>}
389        {data.options.map((o, i) => {
390          const isPick = o.label === pick && !o.mine
391          const child = node.children[i]
392          const color = isPick ? 'green' : o.mine ? 'yellow' : 'gray'
393          return (
394            <Box key={'opt' + i} flexDirection="column">
395              <Text dimColor>{'  │'}</Text>
396              <Box borderStyle={isPick ? 'bold' : 'round'} borderColor={color} paddingX={1} flexDirection="column">
397                <Text bold color={color === 'gray' ? 'white' : color}>{String.fromCharCode(65 + i) + '  ' + o.label + (isPick ? '   ★ recommended' : '') + (o.mine ? '   ✎ your answer' : '')}</Text>
398                {o.detail ? <Text>{o.detail}</Text> : null}
399                {o.leadsTo ? <Text color="blue">{'→ ' + o.leadsTo}</Text> : null}
400                {(o.pros || []).map((p, j) => <Text key={'p' + j} color="green">{'+ ' + p}</Text>)}
401                {(o.cons || []).map((c, j) => <Text key={'c' + j} color="red">{'- ' + c}</Text>)}
402                <Button key={'x' + i} label={child ? (child.status === 'exploring' ? 'Exploring...' : 'Open branch ▸') : 'Explore ▸'}
403                  onPress={() => explore($, i)} />
404              </Box>
405            </Box>
406          )
407        })}
408        {data.question ? (
409          <Box flexDirection="column">
410            <Text dimColor>{'  │'}</Text>
411            <Box borderStyle="round" borderColor="gray" borderDimColor paddingX={1} flexDirection="column">
412              <Text dimColor bold>{String.fromCharCode(65 + data.options.length) + '  Other'}</Text>
413              <Input key={'other.' + path.join('.')} placeholder="type your own answer" submitLabel="explore"
414                onSubmit={value => { if (value.trim()) explore($, -1, value.trim()) }} />
415            </Box>
416          </Box>
417        ) : null}
418        {pick ? <Text>{' '}</Text> : null}
419        {pick ? <Text color="green">{'★ ' + pick + ': ' + data.recommendation.why}</Text> : null}
420        {nav}
421      </Box>
422    )
423  })
424}
425