<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Walkthrough UI on Sven Ruppert</title><link>https://svenruppert.com/tags/walkthrough-ui/</link><description>Sven Ruppert — Java Veteran, Speaker, Trainer &amp; Bushcrafter. Articles, talks, workshops and videos on Core Java, Cybersecurity, Vaadin and Developer Relations.</description><generator>Hugo</generator><language>en</language><managingEditor>sven.ruppert@gmail.com (Sven Ruppert)</managingEditor><webMaster>sven.ruppert@gmail.com (Sven Ruppert)</webMaster><copyright>© 2026 Sven Ruppert</copyright><atom:link href="https://svenruppert.com/tags/walkthrough-ui/index.xml" rel="self" type="application/rss+xml"/><image><url>https://svenruppert.com/img/sven-ruppert.jpg</url><title>Sven Ruppert</title><link>https://svenruppert.com/tags/walkthrough-ui/</link></image><lastBuildDate>Fri, 26 Jun 2026 13:00:00 +0200</lastBuildDate><item><title>The Vaadin UI of the Recipe Assistant — Part 2: The Step-by-Step View</title><link>https://svenruppert.com/posts/recipe-assistant-vaadin-ui-part-2-the-step-by-step-view/</link><pubDate>Fri, 26 Jun 2026 13:00:00 +0200</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/recipe-assistant-vaadin-ui-part-2-the-step-by-step-view/</guid><description>Halting the automatic MCP pipeline into individually triggerable phases. Two views, one engine — the step-by-step walkthrough turns the fleeting course into a legible, temporally ordered object that the viewer can walk through at their own pace.</description><content:encoded>&lt;![CDATA[<h2 id="1-introduction-from-the-result-to-the-process">1. Introduction: From the Result to the Process</h2><p>The first part of this series dealt with the main view — the juxtaposition of two
answers as a didactic stage. It answers a yes-or-no question, and it answers it
convincingly: the attached server makes a visible difference. Yet it is silent
about how that difference comes about. Which tools the model chooses and in what
order, with which arguments it calls them, how their results flow back into the
ongoing dialogue, and at which point several runs finally become a coherent
recommendation — all of this runs hidden in automatic operation. The running
timeline of the main view hints at the traffic but does not halt it.</p><p>It is precisely here that the present second part begins. It deals with the
step-by-step view, a view of its own under the route<code>mcp-walkthrough</code> with the
title &ldquo;MCP Server — Step by Step&rdquo;. Where the main view sets the result of two
requests side by side and thereby conceals the mechanism, the step-by-step view
reverses the relationship. It halts the pipeline, makes each phase individually
triggerable, and turns the otherwise fleeting course into a legible, temporally
ordered object. The didactic gain lies in slowing down: what the main view reels
off in one go, the walkthrough view decomposes into comprehensible steps that the
viewer can walk through at his own pace.</p><p>The tour of this part therefore no longer follows the constituents of an interface,
but the hurdles that halting and making visible an inherently automatic process
brings with it. The first and load-bearing chapter treats the architectural
precondition: the decomposition of the automatic tool loop into individual,
resumable phases that both views share. There follow the processing track as a
timeline, the individual triggering of the phases, the data panel beside the track,
the presentation of parallel tool calls, and the streaming and rendering within a
single phase card. A concluding chapter brings both parts of the series together.</p><p>As in the first part, general familiarity with Vaadin is assumed; particular
elements are presented in detail as to their effect. The technical basis matches
that of the first part: JDK 26, Vaadin Flow 25.1.6 with embedded Jetty 12.1.10 and
Jackson for JSON processing, without Spring and without Jakarta EE. Both views rest
on the same protocol logic and differ solely in their drive and in their
presentation — a fact that the following chapter takes as its point of departure.</p><h2 id="2-from-the-automatic-loop-to-the-halted-pipeline">2. From the Automatic Loop to the Halted Pipeline</h2><p>The step-by-step view and the main view show the same process. This is not an
assertion but an architectural fact, and it is the precondition for everything else
this part treats. The main view drives the tool loop automatically: it calls the
model, executes the requested tools, adds their results to the dialogue and calls
the model again, until no further tools are requested. The walkthrough view must
halt precisely this loop and decompose it into individually triggerable steps —
without the protocol logic being duplicated. Were it duplicated, the two views
might show two different courses; yet the didactic value of the walkthrough rests
on its showing the same course, merely slowed down.</p><p>The solution lies in a dedicated bearer of the protocol logic, the<code>WalkthroughEngine</code>. Each of its methods runs exactly one phase, mutates the
supplied state and returns the result as a phase record that the interface can
render.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/** Phase 1. Build a fresh state seeded with the system + user message. */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">WalkthroughState</span><span class="w"/><span class="nf">startRun</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">systemPrompt</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">userMessage</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">WalkthroughState</span><span class="p">(</span><span class="n">systemPrompt</span><span class="p">,</span><span class="w"/><span class="n">userMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="cm">/** Phase 2. Fetch the MCP tool catalogue and attach it to the state. */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolCatalog</span><span class="w"/><span class="nf">fetchToolCatalogue</span><span class="p">(</span><span class="n">WalkthroughState</span><span class="w"/><span class="n">state</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ArrayNode</span><span class="w"/><span class="n">tools</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">toolBridge</span><span class="p">.</span><span class="na">toolsForOllama</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">state</span><span class="p">.</span><span class="na">setToolsArray</span><span class="p">(</span><span class="n">tools</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">names</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">tools</span><span class="p">.</span><span class="na">size</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">JsonNode</span><span class="w"/><span class="n">tool</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">tools</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">tool</span><span class="p">.</span><span class="na">path</span><span class="p">(</span><span class="s">"function"</span><span class="p">).</span><span class="na">path</span><span class="p">(</span><span class="s">"name"</span><span class="p">).</span><span class="na">asText</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="n">names</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">name</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolCatalog</span><span class="w"/><span class="n">phase</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolCatalog</span><span class="p">(</span><span class="n">names</span><span class="p">,</span><span class="w"/><span class="n">tools</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">state</span><span class="p">.</span><span class="na">setCurrentPhase</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">phase</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The decisive thought stands in the engine&rsquo;s class comment: the protocol logic
resides in a single place, and only the drive differs. The class comment puts it
thus: &ldquo;The engine is shared by the comparison view (driving the phases in an auto
loop) and the walkthrough view (driving them one user click at a time); the
protocol logic therefore lives in a single place.&rdquo; The five phases are realised as
methods —<code>startRun</code>,<code>fetchToolCatalogue</code>,<code>runModelQuery</code>,<code>executeTools</code> and<code>finalise</code> — and each is callable on its own. It is precisely because each phase is
a method that the walkthrough view can call them individually on a button press,
while the main view reels them off in a single loop on one virtual thread.</p><p>So that the interface can render each phase uniformly, the phase results are
modelled as a sealed interface. Each record carries the partial result the
walkthrough view is to show and at the same time names the action that advances to
the following phase.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">sealed</span><span class="w"/><span class="kd">interface</span><span class="nc">WalkthroughPhase</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">permits</span><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">Initial</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolCatalog</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ModelQuery</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolExecution</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">FinalAnswer</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/** Short title of the phase, shown as the card heading. */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">title</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/** Plain-English description of what just happened. */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">description</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/** Label of the button that advances to the next phase. */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">nextActionLabel</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/** True when this phase is the last one; the run is complete. */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">default</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">terminal</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The sealed interface finally names its five permitted forms. Each is realised as a<code>record</code> and knows three pieces of information: a title for the card heading, a
description of what has just happened, and the label of the button that leads to
the next phase. Each phase thus carries its own rendering instruction with it; the
interface need not know which phase it is in, but asks the record for title,
description and next action. The last phase reports through<code>terminal</code> that the run
is complete.</p><p>The course of the phases can be read as a state machine. The first two phases occur
once each; between the model query and the tool execution runs a loop that is
repeated until the model requests no further tools; then the run issues into the
final answer.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-diagram-01.png" alt="State machine of the five walkthrough phases" loading="lazy" decoding="async"/><p>It is on this very state machine that both views rest. The main view traverses it
of its own accord, without pause; the walkthrough view pauses at each transition
and leaves the next step to the viewer. Which drive is at work is a property of the
view, not of the engine.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-ch02.png" alt="One engine, two drivers: the comparison and walkthrough views share the same protocol logic; only the drive differs" loading="lazy" decoding="async"/><p>The decomposition into resumable phases is thus far more than a structural
convenience. It is the guarantee that the slowed-down presentation of the
walkthrough view does not show a reconstructed imitation of the course, but the
course itself — the same one that the main view reels off in a single go. On this
foundation the following chapter can show how the individual phases become a
growing track.</p><h2 id="3-the-processing-track-as-a-timeline">3. The Processing Track as a Timeline</h2><p>Where the main view shows only the final result, the walkthrough view preserves the
entire path to it. The central visual element in which this path becomes visible is
the processing track: a vertical sequence of cards that grows by one card with every
completed phase. The track is thus not merely a display element but the actual
record of the process — a record the viewer can read in retrospect, because it
persists rather than flowing away like the stream of the main view.</p><p>The bearer of the track is a plain container that takes up a card for each phase. It
arises when the view is assembled, beside the data panel.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="nf">WalkthroughView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addClassName</span><span class="p">(</span><span class="s">"recipe-shell"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">(</span><span class="n">buildTrack</span><span class="p">(),</span><span class="w"/><span class="n">dataPanel</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">body</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-body"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">body</span><span class="p">.</span><span class="na">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">configureControls</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The left half of the body forms the track, the right half the data panel, to which
a chapter of its own is devoted. Within the track a<code>trackContainer</code> collects the
cards. Each phase has its own build method that creates a card and appends it to the
container. The first phase, the initial situation, shows the pattern in its purest
form.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">renderInitialCard</span><span class="p">(</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">Initial</span><span class="w"/><span class="n">phase</span><span class="p">,</span><span class="w"/><span class="n">Runnable</span><span class="w"/><span class="n">onNext</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">card</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">newCard</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"/><span class="s">"recipe-card--initial"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">chip</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeChip</span><span class="p">(</span><span class="s">"recipe-chip--initial"</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">FLAG_O</span><span class="p">,</span><span class="w"/><span class="n">phase</span><span class="p">.</span><span class="na">title</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">header</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">headerRow</span><span class="p">(</span><span class="n">chip</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">subtitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeSubtitle</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Conversation context, ready to be sent. Nothing has hit the model yet."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-card__result"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">makeSection</span><span class="p">(</span><span class="s">"System prompt"</span><span class="p">,</span><span class="w"/><span class="n">phase</span><span class="p">.</span><span class="na">systemPrompt</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">makeSection</span><span class="p">(</span><span class="s">"User message"</span><span class="p">,</span><span class="w"/><span class="n">phase</span><span class="p">.</span><span class="na">userMessage</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">description</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeDescriptionBlock</span><span class="p">(</span><span class="n">phase</span><span class="p">.</span><span class="na">description</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">card</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">header</span><span class="p">,</span><span class="w"/><span class="n">subtitle</span><span class="p">,</span><span class="w"/><span class="n">result</span><span class="p">,</span><span class="w"/><span class="n">description</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">trackContainer</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">card</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addNextButton</span><span class="p">(</span><span class="n">card</span><span class="p">,</span><span class="w"/><span class="n">phase</span><span class="p">.</span><span class="na">nextActionLabel</span><span class="p">(),</span><span class="w"/><span class="n">onNext</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Every card follows the same construction: a header row with an icon belonging to the
phase and the title, a subtitle, a result area with the respective content of the
phase, and a description of what has happened. Title, description and the label of
the next action come directly from the phase record — those very pieces of
information that the previous chapter showed on the sealed interface. The card
therefore need not know which phase it renders; it asks the record. Finally,<code>trackContainer.add</code> appends the card to the track, and<code>addNextButton</code> furnishes it
with a button that leads to the next phase.</p><p>The order of the cards is not arbitrary but carries meaning. Cards stacked one above
another denote a sequential dependency: one phase follows from the previous. Between
consecutive cards a connector drawn purely via CSS is therefore shown — a line with
an arrowhead that makes the flow visible at a glance. These connectors are not part
of the Java source but are drawn in the stylesheet through the pseudo-elements<code>::before</code> and<code>::after</code>; the cards themselves thereby remain plain.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-diagram-02.png" alt="The processing track as a numbered sequence of phase cards" loading="lazy" decoding="async"/><p>The numbering of the phases reflects this temporal order. The initial situation
bears the one, the tool catalogue the two; the model queries bear the three with a
sub-number per run (3.1, 3.2 …), the tool executions the four with the same
sub-number (4.1, 4.2 …), and the concluding summary the five. Thus from the label of
each card one can read at which point of the run it stands and how often the loop
between model and tools has already been traversed.</p><p>The last card falls outside the pattern, for it shows no further phase of the
protocol but a summary of the run: a few figures — the total time, the number of
model runs, the number of tool calls and an estimated number of output tokens — and
a small bar chart that sets the duration of the individual phases in proportion. The
actual answer is not repeated by this card, for it was already rendered in the last
model-query card — a circumstance the seventh chapter pursues. With the processing
track the stage is thus set; the following chapter shows how the viewer brings the
individual cards into being.</p><h2 id="4-triggering-phases-individually">4. Triggering Phases Individually</h2><p>The processing track does not grow of its own accord. The viewer sets the pace by
triggering each phase individually — and this very interaction model is the heart of
the walkthrough view. The previous chapter showed that each card receives, at its
end, a button whose label names the next phase: &ldquo;Fetch tool catalogue from MCP&rdquo;,
&ldquo;Send initial model query&rdquo;, &ldquo;Execute the tool&rdquo;, &ldquo;Send follow-up model query&rdquo;. A
press on this button calls the corresponding method of the engine, whose result
appears as a new card, which in turn carries the button to the phase after next.
Thus the run proceeds step by step, each step a deliberate action.</p><p>The question of what &ldquo;resumable&rdquo; means in a server-side interface can be answered
precisely here. The state of the run rests, between two steps, in a field of the
view — the<code>WalkthroughState</code>, which the engine mutates and returns on every call. A
step takes up this state, runs its phase and deposits the result back into the
state. Between the steps nothing happens; the view remains in a clearly recognisable,
resting state and waits for the viewer&rsquo;s next action.</p><p>Since the individual phases contain blocking calls to the model and to the tools,
the update model familiar from the first part applies here too: execution takes
place on a background thread, and the result is channelled into the component tree
by way of<code>@Push</code> and<code>UI.access</code>. Two flags secure the orderly course:<code>phaseInFlight</code> indicates that a phase is currently running, and<code>autoRunning</code>
whether the viewer has chosen the automatic run.</p><p>For besides stepping forward, the view offers a second way: the &ldquo;Run to End&rdquo; button,
which traverses the remaining phases of its own accord. During such an automatic run
the buttons of the individual cards are suppressed; the cards still appear as soon as
their phase is resolved, but a brief pause is inserted between the phases so that the
progression remains legible. This pause amounts to 350 milliseconds and is inserted
by way of a dedicated virtual thread.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">scheduleAutoContinue</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="w"/><span class="n">ui</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Thread</span><span class="p">.</span><span class="na">startVirtualThread</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">AUTO_RUN_PAUSE_MS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">InterruptedException</span><span class="w"/><span class="n">ie</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">interrupt</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(</span><span class="k">this</span><span class="p">::</span><span class="n">continueAuto</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">UIDetachedException</span><span class="w"/><span class="n">ignored</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The background thread sleeps the fixed pause and then triggers the next step by way
of<code>UI.access</code>. The<code>try</code> block around<code>ui.access</code> catches the<code>UIDetachedException</code>
— the case in which the viewer has meanwhile left the view; the automatic run then
runs quietly into the void instead of throwing an error. Where the automatic and the
step-by-step drives part ways is shown by the conclusion of a phase. After the tool
execution, for instance, a single branch decides whether things continue
automatically or whether the next button is offered to the viewer.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="n">phaseInFlight</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">autoRunning</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">scheduleAutoContinue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addNextButton</span><span class="p">(</span><span class="n">card</span><span class="p">,</span><span class="w"/><span class="n">exec</span><span class="p">.</span><span class="na">nextActionLabel</span><span class="p">(),</span><span class="w"/><span class="k">this</span><span class="p">::</span><span class="n">advanceToModelQuery</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">updateRunToEndAvailability</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>If the automatic run is chosen, the phase schedules its own continuation; otherwise
the card receives a button whose label again stems from the phase record and whose
activation leads to the next phase. The difference from the automatic loop of the
main view is thus named precisely: it is not the mechanics of the transmission that
change — they are the same in both views — but solely the trigger. In place of the
self-acting continuation steps the viewer&rsquo;s deliberate action.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-diagram-03.png" alt="Auto-run vs step-by-step — when each card resolves" loading="lazy" decoding="async"/><p>A &ldquo;Reset&rdquo; button next to the input field clears the track and the recorded figures
again, so that a new prompt can be entered. The viewer thus has three operating
actions at his disposal: to begin a run, to carry it through step by step or of its
own accord, and to reset it. Which way is chosen changes nothing about the course
shown, only its pace — and precisely therein lies the didactic value: the viewer can
study the same process once at leisure and once let it run in context. The following
chapter turns to the second major constituent of the view, the data panel.</p><h2 id="5-the-data-panel">5. The Data Panel</h2><p>Beside the processing track stands a second area to which this chapter is devoted:
the data panel. While the track records the temporal succession of the phases, the
data panel shows the current state of the data the model accesses — the pantry and
the dietary profile. The relationship of the two areas is that of course and state:
the track answers the question of how the present state came about; the data panel
answers the question of what this state looks like at this moment.</p><p>Notable is what the data panel is built with. It is not a component of its own but
the same sidebar that the main view already uses — here merely carried under a
different name.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Sidebar</span><span class="w"/><span class="n">dataPanel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Sidebar</span><span class="p">(</span><span class="n">services</span><span class="p">.</span><span class="na">pantry</span><span class="p">(),</span><span class="w"/><span class="n">services</span><span class="p">.</span><span class="na">profile</span><span class="p">());</span></span></span></code></pre></div></div><p>The reuse is no accident but the expression of the same principle that ran through
the first part: the business state is maintained in exactly one place, and both
views access it through the same REST clients. What was said in the first part about
the writing and reloading of the sidebar holds here unchanged. The only difference
lies in the role: in the main view the sidebar is the operator&rsquo;s instrument; in the
walkthrough view it additionally appears as an object of illustration showing what
the model can read.</p><p>In the assembly of the view the data panel appears as the right half of the body,
set opposite the track.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Div</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">(</span><span class="n">buildTrack</span><span class="p">(),</span><span class="w"/><span class="n">dataPanel</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">body</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-body"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">body</span><span class="p">.</span><span class="na">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">body</span><span class="p">);</span></span></span></code></pre></div></div><p>The subtitle line of the view names the panel&rsquo;s task expressly: &ldquo;The data panel on
the right reflects what the model can read; the track below grows one card per
step.&rdquo; With that, the interplay of the two areas is brought to the point. On the
left the track grows card by card and shows the process; on the right stands the
data panel and shows the object to which the process refers.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-ch05.png" alt="Layout of the view: on the left the growing processing track, on the right the data panel with pantry and dietary profile" loading="lazy" decoding="async"/><p>The data panel is not rigid but is refreshed in the course of the run — and indeed
at the point at which the model may actually have changed the state. After the tool
execution, when a state-mutating tool such as consuming an ingredient or adjusting
the profile has run, the view reloads the panel.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="n">dataPanel</span><span class="p">.</span><span class="na">refreshGrid</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dataPanel</span><span class="p">.</span><span class="na">refreshProfileForm</span><span class="p">();</span></span></span></code></pre></div></div><p>It is here that the didactic gain of juxtaposing course and state shows itself. The
viewer sees in the track that a tool was called which changes the pantry; and he
sees, at the same moment, in the data panel how the pantry has thereby changed. The
one explains the other: the tool execution in the track is the cause, the changed
display in the data panel the effect. Where the main view performs the refresh only
at the end of a whole run, here it happens after each individual tool execution —
fitting to the slowed-down pace of the walkthrough, which wishes to make each effect
visible on its own.</p><p>Thus track and data panel come together as two perspectives on the same process.
The one shows the sequence of actions, the other their precipitation in the state.
The following chapter returns to the track and considers a case that demands a
presentation of its own: the simultaneous execution of several tools.</p><h2 id="6-presenting-parallel-tool-calls">6. Presenting Parallel Tool Calls</h2><p>If the model requests several tools at once in a single reply, a twofold task
arises: the calls are to be executed concurrently, and their simultaneity is at the
same time to be presented as such. The walkthrough view solves both sides of this
task in a way that directly reflects the technical reality of the engine.</p><p>The execution is taken on by the engine in its fourth phase. It opens an executor
with one virtual thread per task, submits a<code>CompletableFuture</code> for each tool call,
waits with<code>allOf</code> for their completion, and collects the results in the original
order of the request.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ExecutorService</span><span class="w"/><span class="n">executor</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Executors</span><span class="p">.</span><span class="na">newVirtualThreadPerTaskExecutor</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">List</span><span class="o">&lt;</span><span class="n">CompletableFuture</span><span class="o">&lt;</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolOutcome</span><span class="o">&gt;&gt;</span><span class="w"/><span class="n">futures</span><span class="w"/><span class="o">=</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">calls</span><span class="p">.</span><span class="na">size</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">PendingToolCall</span><span class="w"/><span class="n">call</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">calls</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">futures</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">CompletableFuture</span><span class="p">.</span><span class="na">supplyAsync</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">invokeSafely</span><span class="p">(</span><span class="n">call</span><span class="p">),</span><span class="w"/><span class="n">executor</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CompletableFuture</span><span class="p">.</span><span class="na">allOf</span><span class="p">(</span><span class="n">futures</span><span class="p">.</span><span class="na">toArray</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">CompletableFuture</span><span class="o">[</span><span class="n">0</span><span class="o">]</span><span class="p">)).</span><span class="na">join</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">List</span><span class="o">&lt;</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolOutcome</span><span class="o">&gt;</span><span class="w"/><span class="n">outcomes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">calls</span><span class="p">.</span><span class="na">size</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">CompletableFuture</span><span class="o">&lt;</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolOutcome</span><span class="o">&gt;</span><span class="w"/><span class="n">f</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">futures</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">outcomes</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="na">join</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>Three properties of this execution deserve attention. First, the calls are genuinely
conducted simultaneously, each on a virtual thread of its own. Second, the results
are collected in the order of the request, not in the order of their arrival; the
presentation thereby remains stable, whichever call answers first. Third, a failed
call throws no exception but is returned through<code>invokeSafely</code> as an error outcome,
so that the model can react to it on the next run.</p><p>The presentation of this simultaneity rests on a deliberate decision: several calls
requested at once are not shown as separate, vertically stacked cards, but as
side-by-side lanes within a single tool-execution card, furnished with a parallel
marker that names how many concurrent virtual threads are running. Vertically
stacked cards denote, as the third chapter showed, a sequential dependency;
side-by-side lanes denote simultaneity. The form of the presentation thus itself
carries a statement about the concurrency.</p><p>While a call is still running, its lane shows the name of the tool, the arguments and
a hint at the pending result.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="nf">buildLaneRunning</span><span class="p">(</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">PendingToolCall</span><span class="w"/><span class="n">c</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">lane</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lane</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__lane"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lane</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__lane--running"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="na">name</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"(...)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">name</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tool-name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Pre</span><span class="w"/><span class="n">args</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Pre</span><span class="p">(</span><span class="n">prettyJson</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="na">argumentsJson</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">args</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__json"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">pending</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="s">"Awaiting MCP response..."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pending</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__pending"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lane</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"/><span class="n">args</span><span class="p">,</span><span class="w"/><span class="n">pending</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">lane</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Once the execution is complete, the card replaces the running lanes with the
resolved ones and at the same time refreshes the data panel.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">finishToolExecutionCard</span><span class="p">(</span><span class="n">Div</span><span class="w"/><span class="n">card</span><span class="p">,</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">description</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">result</span><span class="p">,</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">lanes</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">progressWrap</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolExecution</span><span class="w"/><span class="n">exec</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">progressWrap</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">card</span><span class="p">.</span><span class="na">removeClassName</span><span class="p">(</span><span class="s">"running"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lanes</span><span class="p">.</span><span class="na">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ToolOutcome</span><span class="w"/><span class="n">oc</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">exec</span><span class="p">.</span><span class="na">outcomes</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lanes</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildLaneResolved</span><span class="p">(</span><span class="n">oc</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">description</span><span class="p">.</span><span class="na">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">description</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">makeDescriptionLabel</span><span class="p">(</span><span class="s">"Description"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">description</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">exec</span><span class="p">.</span><span class="na">description</span><span class="p">())));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dataPanel</span><span class="p">.</span><span class="na">refreshGrid</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dataPanel</span><span class="p">.</span><span class="na">refreshProfileForm</span><span class="p">();</span></span></span></code></pre></div></div><p>Each resolved lane shows the name of the tool, the arguments and the neatly
formatted result JSON; an erroneous call paints its lane red. Notably, the refresh
of the data panel happens precisely here — after the execution, when the tools may
have changed the state — which closes the arc to the previous chapter.</p><p>The statement of the presentation — sequential below one another, parallel beside
one another — corresponds to the reality of the engine. The description of the phase
record brings this to the point: &ldquo;The MCP server executed all N tools concurrently
(parallel virtual threads). Each lane shows arguments and result independently.&rdquo;</p><figure><img src="/images/2026/06/recipe-assistant-part-2-diagram-04.png" alt="Parallel tool execution — one card with side-by-side lanes" loading="lazy" decoding="async"/><p>Thus the walkthrough view makes the concurrency both effective and visible. It
conducts the calls genuinely simultaneously, and it shows them in a form that does
not veil this simultaneity but displays it — without thereby losing the temporal
placement of the calls within the overall course, for all lanes dwell in one and the
same card at its place in the track.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-ch06.png" alt="Parallel tool execution: one card, side-by-side lanes under a parallel marker" loading="lazy" decoding="async"/><p>The following chapter returns to a detail that dwells in the model-query card and
closes the arc to the first part: the streaming of the text and its final
presentation as Markdown within a single card.</p><h2 id="7-streaming-and-markdown-in-the-phase-card">7. Streaming and Markdown in the Phase Card</h2><p>The fifth chapter of the first part ended with a forward reference. There it was
shown that the main view renders the stream directly as Markdown, and at the same
time it was announced that the step-by-step view takes the opposite path: raw text
first, then rendered. This counter-design the present chapter makes good on. It
dwells in the model-query card, that card which represents a single call to the
model.</p><p>While the stream runs, the text grows in a plain area. On assembly the card is
furnished with such a<code>streamDiv</code>, an indeterminate progress bar and — as a
particularity of the walkthrough view — a presentation of the request that was sent.
Then, on a virtual thread, the call to the engine runs, whose callback appends each
fragment to the raw-text area.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">StringBuilder</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Div</span><span class="w"/><span class="n">header</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">headerRow</span><span class="p">(</span><span class="n">chip</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Div</span><span class="w"/><span class="n">description</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeDescription</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Div</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">(</span><span class="n">requestSection</span><span class="p">,</span><span class="w"/><span class="n">replyLabel</span><span class="p">,</span><span class="w"/><span class="n">progressWrap</span><span class="p">,</span><span class="w"/><span class="n">streamDiv</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">result</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-card__result"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">card</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">header</span><span class="p">,</span><span class="w"/><span class="n">subtitle</span><span class="p">,</span><span class="w"/><span class="n">result</span><span class="p">,</span><span class="w"/><span class="n">description</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">trackContainer</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">card</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">java</span><span class="p">.</span><span class="na">time</span><span class="p">.</span><span class="na">Instant</span><span class="w"/><span class="n">t0</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">java</span><span class="p">.</span><span class="na">time</span><span class="p">.</span><span class="na">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kt">int</span><span class="w"/><span class="n">iterationLabel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">nextIter</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Thread</span><span class="p">.</span><span class="na">startVirtualThread</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">ModelQuery</span><span class="w"/><span class="n">query</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">engine</span><span class="p">.</span><span class="na">runModelQuery</span><span class="p">(</span><span class="n">state</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">chunk</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">streamDiv</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}));</span></span></span></code></pre></div></div><p>The callback appends each fragment to the buffer and sets its content as plain text
on the<code>streamDiv</code>. It is<code>setText</code>, not the setting of Markdown content: during the
stream the answer deliberately appears as raw text. With that, the two-stage approach
announced in the first part is visible in its first stage. Notable, too, is the
presentation of the request that precedes the card: it shows exactly the messages
that were sent to the model — system and user message and, in later runs, the
preceding replies and tool results — so that the viewer recognises precisely what the
model received.</p><p>The second stage sets in as soon as the stream is complete. The card then decides,
on the basis of the requested tools, how it presents itself finally.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">query</span><span class="p">.</span><span class="na">toolCalls</span><span class="p">().</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">streamDiv</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Markdown</span><span class="w"/><span class="n">rendered</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Markdown</span><span class="p">(</span><span class="n">streamedText</span><span class="p">.</span><span class="na">isBlank</span><span class="p">()</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"_(no text)_"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">streamedText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">rendered</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__rendered"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">rendered</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">phaseInFlight</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// The streamed text is already rendered as Markdown here, so the</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// dedicated "final answer" card would be redundant. Skip straight</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// into the run-summary card with timing + token stats.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">autoRunning</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">scheduleAutoContinue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">scheduleAutoSummaryAdvance</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">streamedText</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">streamDiv</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"(no text emitted; the model went straight to tool calls)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">streamDiv</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__stream--empty"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>If the model requests no tools, this is the final answer. The card removes the
raw-text area and replaces it with a freshly created<code>Markdown</code> component — the second
stage of the announced two-stage approach. It is precisely because the final answer
is already rendered here that a dedicated card for the &ldquo;final answer&rdquo; would be
superfluous; the comment in the source says so expressly. The view therefore
proceeds directly to the summary card that the third chapter described as the last
card of the track. If, on the other hand, the model requests tools, the raw text
remains — with a hint, where applicable, should the model have gone straight to the
tool calls — and beneath it appears a listing of the requested calls.</p><p>The reason for this different choice from the main view lies, as already set out in
the first part, in the different purpose. The teaching view prefers a calm, stable
picture of each phase. During the stream it shows the raw text, which grows calmly
without incomplete markup rearranging itself beneath the viewer&rsquo;s gaze; only after
completion does the rendered document take its place. The phase description confirms
it: &ldquo;Streaming is complete and the text is rendered as Markdown.&rdquo;</p><figure><img src="/images/2026/06/recipe-assistant-part-2-diagram-05.png" alt="Streaming and rendering inside the model-query card" loading="lazy" decoding="async"/><p>With that, the arc that the first part spanned is closed. The two views answer the
same design question in two well-founded ways: the main view renders progressively,
to show the vividness of the emergence; the walkthrough view shows raw text first and
renders after completion, to preserve the calm of study. Both ways follow the same
principle, that an interface should honestly reflect the state of its content. The
following chapter returns to a card that the run passed through early and that
rewards a closer look: the tool-catalogue card.</p><h2 id="8-the-tool-catalogue-card">8. The Tool-Catalogue Card</h2><p>At its beginning the run passes through a card that the preceding narrative left
behind quickly: the tool-catalogue card, the second phase. It deserves a closer look,
for it is more than a mere list. It makes visible which tools the attached server
offers the model at all, and it does so in a form that surpasses mere enumeration:
with clickable badges and full JSON descriptors, between which a smooth scroll
mediates.</p><p>The card carries two areas: a row of badges and a sequence of entries. Each entry
presents a tool in detail — with name, description and the full JSON descriptor —
while the badges serve as concise, clickable jump marks. The construction of the
entries arises from the tool array that the engine procured in the second phase.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">java</span><span class="p">.</span><span class="na">util</span><span class="p">.</span><span class="na">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Div</span><span class="o">&gt;</span><span class="w"/><span class="n">entryByName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">java</span><span class="p">.</span><span class="na">util</span><span class="p">.</span><span class="na">LinkedHashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">JsonNode</span><span class="w"/><span class="n">tool</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">phase</span><span class="p">.</span><span class="na">toolsArray</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">toolName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">tool</span><span class="p">.</span><span class="na">path</span><span class="p">(</span><span class="s">"function"</span><span class="p">).</span><span class="na">path</span><span class="p">(</span><span class="s">"name"</span><span class="p">).</span><span class="na">asText</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">toolName</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">toolName</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="k">continue</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">entry</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tool-entry"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"data-tool-name"</span><span class="p">,</span><span class="w"/><span class="n">toolName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">entryTitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Icon</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CODE</span><span class="p">),</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">toolName</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entryTitle</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tool-entry-title"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">description</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">tool</span><span class="p">.</span><span class="na">path</span><span class="p">(</span><span class="s">"function"</span><span class="p">).</span><span class="na">path</span><span class="p">(</span><span class="s">"description"</span><span class="p">).</span><span class="na">asText</span><span class="p">(</span><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">description</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">entryDesc</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">description</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entryDesc</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tool-entry-desc"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">entryTitle</span><span class="p">,</span><span class="w"/><span class="n">entryDesc</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">entryTitle</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Pre</span><span class="w"/><span class="n">json</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Pre</span><span class="p">(</span><span class="n">prettyJson</span><span class="p">(</span><span class="n">tool</span><span class="p">.</span><span class="na">toString</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">json</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__json"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">json</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entries</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">entryByName</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">toolName</span><span class="p">,</span><span class="w"/><span class="n">entry</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Two things deserve attention. First, name and description are read directly from the
tool&rsquo;s JSON node — via<code>path</code> and<code>asText</code> — so that the card shows exactly what the
server actually offers, not a maintained second version. Second, a<code>LinkedHashMap</code>
holds the mapping of tool names to entry<code>Div</code> and thereby preserves the order; it is
this very mapping that allows the badges to jump to their entry. The full descriptor
is attached as a monospaced<code>Pre</code> block.</p><p>The badges arise in a second pass, this time over the phase&rsquo;s list of names. If an
entry is found for a name, the badge is made clickable and furnished with the
features necessary for operability and accessibility.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">phase</span><span class="p">.</span><span class="na">toolNames</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">tag</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">name</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tool-tag"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">target</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">entryByName</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">name</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">target</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tool-tag--clickable"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Jump to "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" descriptor"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"role"</span><span class="p">,</span><span class="w"/><span class="s">"button"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"tabindex"</span><span class="p">,</span><span class="w"/><span class="s">"0"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">addEventListener</span><span class="p">(</span><span class="s">"click"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">scrollToToolEntry</span><span class="p">(</span><span class="n">target</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tag</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">addEventListener</span><span class="p">(</span><span class="s">"keydown"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">scrollToToolEntry</span><span class="p">(</span><span class="n">target</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setFilter</span><span class="p">(</span><span class="s">"event.key === 'Enter' || event.key === ' '"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tags</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">tag</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here it shows how much can be regulated on the element itself with the platform&rsquo;s own
means in Vaadin Flow. The badge receives, through<code>setAttribute</code>, the role of a
button, a tooltip and a tab index, so that it is reachable not only with the mouse but
also with the keyboard. Two listeners are attached to the element — one for the mouse
click, one for the keyboard — whereby the keyboard listener is restricted through<code>setFilter</code> to the Enter and the space key. This filter is notable: it is a
client-side expression that decides already in the browser whether the event is
reported to the server at all; needless server round trips are thereby avoided.</p><p>The actual jumping is taken on by a small method that triggers client-side behaviour
through<code>executeJs</code>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">scrollToToolEntry</span><span class="p">(</span><span class="n">Div</span><span class="w"/><span class="n">target</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">target</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">executeJs</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"this.scrollIntoView({behavior:'smooth', block:'start'});"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"this.classList.add('recipe-walkthrough__tool-entry--flash');"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"setTimeout(() =&gt; "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"this.classList.remove('recipe-walkthrough__tool-entry--flash'),"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" 1500);"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The passed JavaScript expression lets the target entry scroll smoothly into the
visible area and then adds to it, for a second and a half, a style class that makes it
flash briefly before it is removed again. Thus, after a click on a badge, the viewer
not only finds the corresponding descriptor again, but his gaze is led directly to it
by the flash. That<code>this</code> within the expression refers to the target element is the
bridge between the server-side component and its client-side counterpart.</p><p>The catalogue card thus comes together from three simple means: the reading of the
real tool array, the linking of badge and entry through an ordered mapping, and a slim
piece of client-side behaviour for the jumping and flashing. A charting library or an
additional framework is not needed for this; the element and its methods suffice.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-ch08.png" alt="The tool-catalogue card: a row of clickable badges above a list of tool entries with description and JSON descriptor" loading="lazy" decoding="async"/><p>The following chapter turns to the last card of the run, which is likewise built by
hand and uses no bought-in chart: the summary card.</p><h2 id="9-the-summary-card">9. The Summary Card</h2><p>The last card of the run shows no further phase of the protocol but a balance: a few
figures and a bar chart that sets the duration of the individual phases in proportion.
The third chapter already mentioned it as the conclusion of the track; here let it be
considered in detail, for it is pure, hand-assembled Vaadin composition — without a
charting library, solely from elementary components and proportionally set widths.</p><p>The card itself is plainly built. It strings together a row of stat tiles and a bar
chart; the actual answer it does not repeat, since that was already rendered in the
last model-query card.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">renderFinalAnswerCard</span><span class="p">(</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">FinalAnswer</span><span class="w"/><span class="n">phase</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">card</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">newCard</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"/><span class="s">"recipe-card--final"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">chip</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeChip</span><span class="p">(</span><span class="s">"recipe-chip--final"</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CHART_LINE</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"5. Run summary"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">header</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">headerRow</span><span class="p">(</span><span class="n">chip</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">subtitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeSubtitle</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Timing and approximate token usage per phase. Token counts are "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"estimated as streamed-chars / 4."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-card__result"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildSummaryTiles</span><span class="p">(</span><span class="n">phase</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildPhaseChart</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">description</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeDescriptionBlock</span><span class="p">(</span><span class="n">phase</span><span class="p">.</span><span class="na">description</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">card</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">header</span><span class="p">,</span><span class="w"/><span class="n">subtitle</span><span class="p">,</span><span class="w"/><span class="n">result</span><span class="p">,</span><span class="w"/><span class="n">description</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">trackContainer</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">card</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The figures arise from the statistics captured phase by phase during the run. Each
completed phase has left behind a<code>PhaseStat</code> record — with label, colour class,
duration and a size figure together with a unit, for instance the number of tools or
the character count of the stream. From these values the card sums the total duration,
the number of tool calls and the total of streamed characters.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="nf">buildSummaryTiles</span><span class="p">(</span><span class="n">WalkthroughPhase</span><span class="p">.</span><span class="na">FinalAnswer</span><span class="w"/><span class="n">phase</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">totalChars</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">totalDurationMs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">toolCalls</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">PhaseStat</span><span class="w"/><span class="n">s</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">phaseStats</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">totalDurationMs</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">duration</span><span class="p">().</span><span class="na">toMillis</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="s">"chars"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">sizeLabel</span><span class="p">()))</span><span class="w"/><span class="n">totalChars</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">sizeValue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="s">"tool"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">sizeLabel</span><span class="p">())</span><span class="w"/><span class="o">||</span><span class="w"/><span class="s">"tools"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">sizeLabel</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toolCalls</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">sizeValue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">wallClockMs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">runStartedAt</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">totalDurationMs</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">:</span><span class="w"/><span class="n">java</span><span class="p">.</span><span class="na">time</span><span class="p">.</span><span class="na">Duration</span><span class="p">.</span><span class="na">between</span><span class="p">(</span><span class="n">runStartedAt</span><span class="p">,</span><span class="w"/><span class="n">java</span><span class="p">.</span><span class="na">time</span><span class="p">.</span><span class="na">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">()).</span><span class="na">toMillis</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">approxTokens</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">totalChars</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">4</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">tiles</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tiles</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__summary-tiles"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tiles</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildTile</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CLOCK</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">formatMillis</span><span class="p">(</span><span class="n">wallClockMs</span><span class="p">),</span><span class="w"/><span class="s">"Total time"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tiles</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildTile</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">REFRESH</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="n">phase</span><span class="p">.</span><span class="na">iterations</span><span class="p">()),</span><span class="w"/><span class="s">"Model iterations"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tiles</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildTile</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">COGS</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="n">toolCalls</span><span class="p">),</span><span class="w"/><span class="s">"Tool calls"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tiles</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildTile</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">FILE_TEXT_O</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"≈ "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">approxTokens</span><span class="p">,</span><span class="w"/><span class="s">"Output tokens (~chars/4)"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">tiles</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Two subtleties deserve attention. The total duration, when the start instant is known,
is measured as the actually elapsed wall-clock time between beginning and present, not
as a mere sum of the phase durations; pauses between the steps thus enter the reported
total time. And the number of output tokens is not measured but estimated from the
character count — as character count divided by four — which is why the tile carries
the approximately sign. It is a deliberately coarse approximation that gives the
viewer an order of magnitude without promising more than it can hold.</p><p>Each individual tile is a small, ever-identical building block of icon, value and
label.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="nf">buildTile</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="w"/><span class="n">icon</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">value</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">label</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">tile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tile</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tile"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Icon</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Icon</span><span class="p">(</span><span class="n">icon</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">i</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tile-icon"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">valueSpan</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">value</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">valueSpan</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tile-value"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">labelSpan</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">label</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">labelSpan</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__tile-label"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tile</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">i</span><span class="p">,</span><span class="w"/><span class="n">valueSpan</span><span class="p">,</span><span class="w"/><span class="n">labelSpan</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">tile</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The bar chart is the most instructive part of the card, for it shows how a usable
chart can be drawn without any charting library, solely with the means of the
platform. First the largest duration value is determined, which serves as the
reference magnitude for the scaling; then each phase receives a row.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="nf">buildPhaseChart</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">chart</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">chart</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">chartTitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">makeSectionLabel</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">BAR_CHART_H</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Per-phase duration"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">chart</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">chartTitle</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">maxMs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">PhaseStat</span><span class="w"/><span class="n">s</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">phaseStats</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">maxMs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">maxMs</span><span class="p">,</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">duration</span><span class="p">().</span><span class="na">toMillis</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">PhaseStat</span><span class="w"/><span class="n">s</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">phaseStats</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">chart</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">buildChartRow</span><span class="p">(</span><span class="n">s</span><span class="p">,</span><span class="w"/><span class="n">maxMs</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">chart</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The core lies in the individual row. From the duration of the phase and the previously
determined maximum a percentage is calculated which is set directly as the width of
the bar. A lower bound of two per cent ensures that even very short phases appear as a
visible bar and do not vanish entirely.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="nf">buildChartRow</span><span class="p">(</span><span class="n">PhaseStat</span><span class="w"/><span class="n">s</span><span class="p">,</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">maxMs</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">row</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">row</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-row"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">label</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">label</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">label</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-label"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">ms</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">duration</span><span class="p">().</span><span class="na">toMillis</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">double</span><span class="w"/><span class="n">pct</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">2</span><span class="p">.</span><span class="na">0</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="n">ms</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">100</span><span class="p">.</span><span class="na">0</span><span class="p">)</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">maxMs</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">barTrack</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">barTrack</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-track"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">bar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bar</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-bar"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bar</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-bar--"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">colorClass</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bar</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"width"</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="n">java</span><span class="p">.</span><span class="na">util</span><span class="p">.</span><span class="na">Locale</span><span class="p">.</span><span class="na">ROOT</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"%.1f%%"</span><span class="p">,</span><span class="w"/><span class="n">pct</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">barTrack</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">bar</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">duration</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">formatMillis</span><span class="p">(</span><span class="n">ms</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">duration</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-duration"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">sizeValue</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">sizeLabel</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">size</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"recipe-walkthrough__chart-size"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">row</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">label</span><span class="p">,</span><span class="w"/><span class="n">barTrack</span><span class="p">,</span><span class="w"/><span class="n">duration</span><span class="p">,</span><span class="w"/><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">row</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The width of the bar is set directly on the element through<code>getStyle().set("width", …)</code> as a percentage; the colour stems from a style class whose name is formed from the
colour class of the phase, so that the bars match the badges of the phases by colour.
Beside the bar the row shows the duration and the size figure. The duration is
formatted legibly: below a second in milliseconds, above it in seconds with two
decimal places.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">formatMillis</span><span class="p">(</span><span class="kt">long</span><span class="w"/><span class="n">ms</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">ms</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">1000</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">ms</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" ms"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="n">java</span><span class="p">.</span><span class="na">util</span><span class="p">.</span><span class="na">Locale</span><span class="p">.</span><span class="na">ROOT</span><span class="p">,</span><span class="w"/><span class="s">"%.2f s"</span><span class="p">,</span><span class="w"/><span class="n">ms</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">1000</span><span class="p">.</span><span class="na">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Thus from a few building blocks a complete little chart arises: a reference magnitude,
a row per phase, a bar set proportionally in its width and a readable label. Notable is
all that was not needed here — no charting library, no drawing on a canvas, no
additional framework. A<code>Div</code> given a percentage width is already a bar; no more is
required.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-ch09.png" alt="The summary card: four stat tiles above a horizontal bar chart of the phase durations" loading="lazy" decoding="async"/><p>With that, the two cards that the run passed through quickly or reached last have also
been considered. The following and final chapter draws the sum and brings both parts
of the series together.</p><h2 id="10-conclusion">10. Conclusion</h2><p>The first part of this series showed that the attached server makes a difference; the
second has shown how that difference comes about. With that, the arc spanned by the
introduction to the first part is closed — the passage from the what to the how. The
step-by-step view halts what the main view reels off in one go and turns the fleeting
course into a legible object.</p><p>The load-bearing insight of this part was architectural in nature: both views rest on
the same engine. The protocol logic lives in a single place, in the<code>WalkthroughEngine</code>, whose five phases are realised as individually callable methods.
It is precisely this decomposition into resumable phases that guarantees the
slowed-down walkthrough shows not an imitation but the same course that the main view
traverses of its own accord. On this foundation stood the remaining chapters: the
processing track, which preserves the process card by card as a timeline; the
individual triggering of the phases, which leaves the pace to the viewer; the data
panel, which sets the state beside the course; the presentation of parallel tool
calls, whose form itself displays the concurrency; the streaming with a final
Markdown rendering in the phase card, which made good on the counter-design announced
in the first part; the tool-catalogue card, which assigns clickable badges and full
JSON descriptors to the tools offered to the model; and finally the summary card,
which concludes the run in figures and a hand-drawn bar chart.</p><p>However different the two views are in their effect, they are connected by the same
principle that already ran through the first part. An interface for model-driven tool
use must not merely display results; it must keep the process of their emergence
comprehensible. The main view fulfils this principle through juxtaposition and
simultaneity; the walkthrough view fulfils it through halting and decomposition. Both
are different answers to the same demand, and precisely their difference shows that
the demand admits of more than one realisation.</p><figure><img src="/images/2026/06/recipe-assistant-part-2-ch10.png" alt="One engine, five phases, two views: the comparison and walkthrough views drive the same five phases — the one shows that, the other how" loading="lazy" decoding="async"/><p>There remains the retrospect on the series as a whole. It began with an MCP server
built using nothing but the platform&rsquo;s own means, without Spring and without Jakarta
EE. It continued with an interface that preserved the same renunciation of
heavyweight frameworks and confined itself to Vaadin Flow with embedded Jetty. And it
concluded with two views on the same process, which show that the value of an
attached server cannot merely be asserted but can be demonstrated — once as a result,
once as a process. Whoever has followed the series from beginning to end thus
possesses not only a runnable demonstrator but an understanding of why a carefully
conducted interface is indispensable for conveying model-driven tool use.</p>
]]></content:encoded><category>Java</category><category>Vaadin</category><category>MCP</category><media:content url="https://svenruppert.com/images/2026/06/recipe-assistant-vaadin-ui-part-2-hero.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/06/recipe-assistant-vaadin-ui-part-2-hero.jpg"/><enclosure url="https://svenruppert.com/images/2026/06/recipe-assistant-vaadin-ui-part-2-hero.jpg" type="image/jpeg" length="0"/></item></channel></rss>