<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <feedpress:newsletterId>telerik-blogs</feedpress:newsletterId>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs-5aafd3c47efc3.jpg</logo>
  <title type="text">Telerik Blogs</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:a779cba9-1932-46d1-8d86-62a7f79c6f2a;id=37</id>
  <updated>2026-08-27T13:35:57Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs"/>
  <entry>
    <id>urn:uuid:1f4c932a-340d-4c45-9fcd-07c4c63123b5</id>
    <title type="text">AI UX Patterns to Show Meaningful User Benefit</title>
    <summary type="text">All the interesting technology and cool functionality in the world is meaningless if what we build doesn’t solve the problems our users need solved.</summary>
    <published>2026-08-26T20:33:33Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/ai-ux-patterns-show-meaningful-user-benefit"/>
    <content type="text"><![CDATA[<p><span class="featured">To get user buy-in, we need be adding AI features that actually work for users and help them in a significant, recognizable way.</span></p><p>All the interesting technology and cool functionality in the world is meaningless if what we build doesn&rsquo;t solve the problems our users need solved. To do that, we need to create experiences that make it as simple as possible for users get the output they need from our AI features and leverage it in their work. </p><h2 id="ux-pattern-usage-guidance">UX Pattern: Usage Guidance</h2><p>One of the biggest mistakes we can make when developing AI features is assuming that users know how to use them. As mentioned in the <a href="https://www.telerik.com/blogs/ux-challenge-designing-ai-powered-user-interfaces" target="_blank">first article</a> of this series: <em>we</em> know how to do things like provide context, specify formatting requirements, break large tasks into smaller ones, and iterate toward better results when using AI&mdash;but our users often won&rsquo;t.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-29-32-am.png?sfvrsn=b7d2d8ec_2" height="582" style="max-width:100%;height:auto;" title="Screenshot 2026-07-23 at 10.29.32 AM" width="962" alt="Chat prompts in Claude " sf-size="69820" /></p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-29-44-am.png?sfvrsn=b55696cc_2" height="538" style="max-width:100%;height:auto;" title="Screenshot 2026-07-23 at 10.29.44 AM" width="918" alt="Agent templates in ChatGPT" sf-size="129647" /></p><p>When we place a blank text box in front of a user and tell them to &ldquo;Ask AI,&rdquo; we're actually asking them to do a <em>lot</em> of prework before they even get to their main task. So much, in fact, that it may actually create more work for them than just completing the task themselves. </p><p>They need to understand what kinds of problems the tool can solve, how specific they should be, what information the AI needs to succeed and how to recognize when a response needs refinement. </p><p>Rather than expecting this level of AI-literacy from our users, we can help them by providing examples, templates, suggested prompts and guided workflows that demonstrate what success looks like. And if there&rsquo;s existing contextual information we can leverage without making the user input it all themselves, all the better. </p><h2 id="ux-pattern-action-buttons">UX Pattern: Action Buttons</h2><p>What will your users be doing with the content your AI feature generates? If they run a search or analyze a spreadsheet, what do they do with the results? If they create an image, who are they showing it to? The action itself is just the beginning of the flow for our users, who then need to <em>do something</em> with what they&rsquo;ve received. </p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-30-01-am.png?sfvrsn=24c2772c_2" title="Screenshot 2026-07-23 at 10.30.01 AM" width="450" alt="Quick Create buttons in Microsoft 365 Copilot" /></p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-30-06-am.png?sfvrsn=597b5a92_2" title="Screenshot 2026-07-23 at 10.30.06 AM" width="350" alt="In-document contextual action prompts in Microsoft 365 Office Suite " /></p><p>By including action buttons, we can make it as easy as possible to them to leverage the output of our tools in their own work. If they&rsquo;re not able to act on the content that was created, it&rsquo;s never going to be of much realistic use to them. And if it&rsquo;s not <em>immediately</em> clear what they can do with what we&rsquo;ve created for them, then we need to show them with real-world examples. </p><p>Once they have an idea of what they want to do, action buttons can guide them to suggested steps that help make the most of what was generated. But an application that creates content a user isn&rsquo;t sure how to use is nothing more than a novelty to be used once or twice and then ignored. </p><h2 id="ux-pattern-external-integrations">UX Pattern: External Integrations</h2><p>If we want to take this one step further, we can build in direct integrations with their other most-used tools. For example, generating a new document in their word processing software from an AI writing draft, pushing new code to their linked repo, opening a new ticket in their tracking system with findings from a test. The simpler we make it to get information from our tool into the rest of their workflow, the more useful it will be to them.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-29-51-am.png?sfvrsn=d9b0e6b0_2" title="Screenshot 2026-07-23 at 10.29.51 AM" width="350" alt="" /></p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-29-56-am.png?sfvrsn=c736b69e_2" title="Screenshot 2026-07-23 at 10.29.56 AM" width="450" alt="Agentic integrations in ChatGPT " /></p><h2 id="can-users-trust-the-ai-features-in-your-software-">Can Users Trust the AI Features in Your Software?</h2><p>AI can generate content, write code, analyze data, automate tasks and empower our users to do incredible work&mdash;but only if they&rsquo;re willing to try it. When everything our AI tools are doing is hidden behind the curtain, it makes users feel like things are happening without their input&mdash;which is hard, especially when many of them are already feeling some level of AI skepticism and hesitation. </p><p>If we create AI experiences that take away our users&rsquo; power, understanding and autonomy, then they&rsquo;ll never be interested in using what we build because they&rsquo;ll never feel truly comfortable engaging with it. By focusing on patterns that reinforce those UX priorities of <a href="https://www.telerik.com/blogs/ai-ux-patterns-user-trust" target="_blank">trust</a>, <a href="https://www.telerik.com/blogs/ai-ux-patterns-user-clarity" target="_blank">clarity</a>, <a href="https://www.telerik.com/blogs/ai-ux-patterns-user-control" target="_blank">control</a>, <a href="https://www.telerik.com/blogs/ai-ux-patterns-user-transparency" target="_blank">transparency</a> and meaningful benefit, we can set guardrails around our AI features that help make our users feel safe. </p><aside class="pquote"><style>.pquote {
float: right;
width: 200px;
font-size: 20px;
line-height: 1;
font-style: italic;
padding: 14px;
}
</style>
<blockquote><p>The technology may be incredible, but for it to be truly successful, <strong>users</strong> still need to be at the center of everything we build.</p></blockquote></aside><p>The technology is here. The models are already good and they just keep getting better, faster and more efficient. That means that the differentiator for AI-powered software isn&rsquo;t performance, anymore. It&rsquo;s the <em>quality</em> of the experiences that we can build around them. </p><p>The line between design and development is blurring a little more every day: how users interact with a system, how much information they see, how much control they have and how we earn their trust are now questions that developers <em>have</em> to consider when building AI-powered software&mdash;whether your title includes &ldquo;designer&rdquo; or not. The technology may be incredible, but for it to be truly successful, <em>users</em> still need to be at the center of everything we build.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Turn AI coding agents into a repeatable development workflow</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Progress Agent Harness is a CLI tool that orchestrates your AI coding agents through structured, repeatable workflows&mdash;bringing planning, execution and governance to AI-assisted software development. <a href="https://www.telerik.com/agent-harness-early-access" target="_blank">Join the Agent Harness Early Access Program.</a></p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ffa78558-3dce-4d01-865f-647f8f54aabd</id>
    <title type="text">Learning LangGraph with TypeScript—Part 1: Understanding Nodes, State and Edges</title>
    <summary type="text">LangGraph is a framework for building AI agents and workflows as stateful graphs. Learn about nodes, edges and state, plus how to build a LangGraph app in TypeScript.</summary>
    <published>2026-08-26T18:36:09Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Dhananjay Kumar </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/learning-langgraph-typescript-1-understanding-nodes-state-edges"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn about nodes, edges and state in LangGraph, plus how to build a LangGraph app in TypeScript.</span></p><p>LangGraph is a framework for building AI agents and workflows as stateful graphs. Each step in the workflow is represented as a node. Edges define how execution flows between nodes, and state acts as shared memory throughout the graph.</p><p>Built on top of LangChain, LangGraph provides explicit and debuggable control over multi-step LLM applications such as chatbots, tool-calling agents and automated pipelines.</p><p>In this article, we will use TypeScript to build a simple LangGraph application from scratch and explore the core concepts, APIs and execution model that form the foundation of more advanced agentic workflows.</p><p>LangGraph has three building blocks.</p><ol><li>State</li><li>Node</li><li>Edges</li></ol><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/langgraph-node-edge-state.png?sfvrsn=3a30e9c4_2" alt="Simple diagram showing LangGraph is composed of node, edge, state as building blocks." /></p><p>State represents data that is shared by every node. A node represents a function that takes a state as input and returns an updated value for the state. Edges are control flow, which can be serial, parallel or conditional.</p><p>To understand how graphs work, we&rsquo;ll start with a simple counter application that does not require a model. This example will introduce the core concepts of <strong>state</strong> and <strong>node</strong>, and by the end of the section, you&rsquo;ll understand how they form the foundation of a graph.</p><p>At the end of the article, we will create a FIFA chatbot using an OpenAI model and LangGraph using nodes, states and edges.</p><h2 id="what-is-a-state">What Is a State?</h2><p>In LangGraph, a state is a data structure that serves as a shared memory layer that connects the graphs. Nodes consume state, produce incremental updates and rely on it for conditional routing.</p><p>Every node in the graph depends on the state. Nodes read data from the state and write partial updates back to it. And conditional edges use the state to determine the next path in the workflow.</p><p><strong>StateSchema</strong> is the contract that defines the structure and behavior of a state in LangGraph. More than just a TypeScript type, it serves as a runtime specification that determines:</p><ul><li>Which fields exist in the state</li><li>Their types and default values (defined with Zod)</li><li>How updates are merged (reducers vs. last-write-wins)</li><li>Which data nodes can read from and write to</li></ul><p>You can create a state using the <strong>StateSchema</strong> as shown below:</p><p><strong>counter-state.schema.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> ReducedValue<span class="token punctuation">,</span> StateSchema <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/langgraph"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> z <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"zod"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> CounterStateSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">ReducedValue</span><span class="token punctuation">(</span>z<span class="token punctuation">.</span><span class="token keyword">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">default</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    inputSchema<span class="token punctuation">:</span> z<span class="token punctuation">.</span><span class="token keyword">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    reducer<span class="token punctuation">:</span> <span class="token punctuation">(</span>current<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> current <span class="token operator">+</span> next<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Every key in a <strong>StateSchema</strong> can be one of the following:</p><ol><li><strong>Zod Field</strong> &ndash; A standard state field defined with Zod. Updates follow a <em>last-write-wins</em> strategy, meaning the most recent value replaces the previous one.</li><li><strong>ReducedValue</strong> &ndash; A field that updates by combining the new value with the existing value instead of replacing it. In the example above, <code>count</code> is a <code>ReducedValue</code>, so each update is merged with the current count rather than overwriting it.</li><li><strong>MessageValue</strong> &ndash; A specialized form of <code>ReducedValue</code> designed for chat applications. Instead of replacing existing messages, new messages are automatically appended to the conversation history.</li></ol><p>Once state is defined using the <code>StateSchema</code>, you can work with the three derived types:</p><ol><li>State &ndash; Full object during the execution</li><li>Update &ndash; Partial object a node may return</li><li>Node &ndash; Function signature</li></ol><p><strong>counter-state.types.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> CounterState <span class="token operator">=</span> <span class="token keyword">typeof</span> CounterStateSchema<span class="token punctuation">.</span>State<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> CounterUpdate <span class="token operator">=</span> <span class="token keyword">typeof</span> CounterStateSchema<span class="token punctuation">.</span>Update<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> CounterNode <span class="token operator">=</span> <span class="token keyword">typeof</span> CounterStateSchema<span class="token punctuation">.</span>Node<span class="token punctuation">;</span>
</code></pre><p>For this example, we will mostly work with the <code>CounterNode</code>. Also, the <code>CounterStateSchema</code> has:</p><ol><li>Initial value set to 0</li><li>Each nodes return the updated value</li><li>Reducer adds current value to the previous value</li></ol><h2 id="what-is-a-reducer">What Is a Reducer?</h2><p>Before we move further, let&rsquo;s discuss the reducer function. It is a simple function that decides how a state field changes when a node returns an update. Multiple nodes can update the same field during a graph execution. Instead of overwriting the existing value, the reducer merges each partial update into the current state.</p><p>As nodes returns <strong>partial updates</strong>, LangGraph would need a rule for conflicting updates. That merge rule is written inside the the <code>reducer</code> function.</p><pre class=" language-ts"><code class="prism  language-ts">reducer<span class="token punctuation">:</span> <span class="token punctuation">(</span>current<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> current <span class="token operator">+</span> next<span class="token punctuation">,</span>
</code></pre><p>The signature of the reducer:</p><ul><li><code>current</code> &ndash; value in state before this update</li><li><code>next</code> &ndash; what the node returned for this field</li><li><code>return value</code> &ndash; new stored value</li></ul><p>Use a reducer only when values need to be combined or accumulated. If each update should simply replace the previous value, a standard field is all you need. As shown in below state schema, each time state will be updated with the last returned value from the node.</p><p><strong>lastcount-state.schema.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> LastCountStateSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> z<span class="token punctuation">.</span><span class="token keyword">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">default</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>For <code>LastCountState</code>, derived states can be exported as shown below:</p><p><strong>lastcount-state.types.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> LastCountState <span class="token operator">=</span> <span class="token keyword">typeof</span> LastCountStateSchema<span class="token punctuation">.</span>State<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> LastCountUpdate <span class="token operator">=</span> <span class="token keyword">typeof</span> LastCountStateSchema<span class="token punctuation">.</span>Update<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> LastCountNode <span class="token operator">=</span> <span class="token keyword">typeof</span> LastCountStateSchema<span class="token punctuation">.</span>Node<span class="token punctuation">;</span>
</code></pre><p>So far, we have defined two state schemas and exported their corresponding TypeScript types. The <code>CounterState</code> uses a reducer to accumulate new values with the existing value, while the <code>LastCountState</code> follows a <em>last-write-wins</em> approach and always stores the most recent value. Next, let&rsquo;s create the nodes that will operate on these states.</p><h2 id="creating-nodes">Creating Nodes</h2><p>In LangGraph, a <strong>node</strong> is the fundamental building block of a graph. It is usually implemented as a function that takes the current state as input, performs some computation or action, and returns a partial update to the state.</p><p>Depending on how the state schema is defined, LangGraph either merges the returned update into the existing state using the configured reducer or replaces the current value with the latest one before passing the updated state to the next step in the workflow.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/current-state-node-function-partial-state-update.png?sfvrsn=5341cd68_2" alt="Current State – Node Function – Partial State Update" /></p><p>You can create nodes to increment a counter state by 1 and 2 like below:</p><p><strong>counter.node.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> incrementNode<span class="token punctuation">:</span> <span class="token function-variable function">CounterNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> incrementTwiceNode<span class="token punctuation">:</span> <span class="token function-variable function">CounterNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>After each node runs, LangGraph:</p><ul><li>Receives the partial state update returned by the node</li><li>Merges the update into the current state using the rules defined in the <code>StateSchema</code></li><li>Passes the resulting state to the next node, or terminates the graph if execution is complete</li></ul><p>For <code>LastCountState</code>, a node can be created as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> setOneNode<span class="token punctuation">:</span> <span class="token function-variable function">LastCountNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> setFiveNode<span class="token punctuation">:</span> <span class="token function-variable function">LastCountNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> setNineNode<span class="token punctuation">:</span> <span class="token function-variable function">LastCountNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">9</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Because <code>LastCountState</code> stores only the latest value, every node overwrites the previous value, and the next node receives the most recent state update.</p><p>As we discussed earlier, a node only returns a partial state update. How that update is applied to the state is determined by the <code>StateSchema</code> through its merge strategy (reducers or last-write-wins), not by the node&rsquo;s implementation.</p><p>Also, nodes do not decide where execution goes next; <strong>edges</strong> do. Nodes only read state and write updates.</p><p>There are four types of nodes:</p><ol><li>Synchronous node &ndash; Returns an object immediately</li><li>Asynchronous node &ndash; Returns a promise</li><li>Stateless node &ndash; Ignores the state value and always returns the same value</li><li>Stateful node &ndash; Reads the fields from the state and computes the new value from them</li></ol><p>All the nodes we have created so far for both <code>CounterState</code> and <code>LastCountState</code> are synchronous and stateless.</p><p>We can create a stateful node on <code>CounterState</code> as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> doubleCountNode<span class="token punctuation">:</span> <span class="token function-variable function">CounterNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> state<span class="token punctuation">.</span>count<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We have created nodes; now see how they are connected using edges.</p><h2 id="creating-an-edge">Creating an Edge</h2><p>An edge connects two nodes and determines the control flow by specifying which node executes after the current node completes.</p><p>An edge can be of two types:</p><ol><li>Fixed edge &ndash; Always goes to the same next node</li><li>Conditional edge &ndash; Next node depends on state or routing function</li></ol><p>To work with nodes and edges, LangGraph provides two special nodes:</p><ol><li>START &ndash; Entry node, and it contains no custom code</li><li>END &ndash; Exit node, and it also contains no custom code</li></ol><h2 id="building-the-graph">Building the Graph</h2><p>The LangGraph graph is where you assemble everything. A graph brings together the state schema, nodes and edges into an executable workflow. You create and run a graph following the steps:</p><ul><li>Create a <strong>StateGraph</strong> using your state schema.</li><li>Add nodes with <strong>addNode()</strong> to define the tasks in the workflow.</li><li>Connect nodes with <strong>addEdge()</strong> to specify the execution order.</li><li>Compile the graph using <strong>compile()</strong> to generate a runnable workflow.</li><li>Invoke the graph with <strong>invoke()</strong>, which starts execution at START.</li><li>Execute nodes in the order defined by the graph&rsquo;s edges.</li><li>Apply reducers after each node runs to update the shared state.</li><li>Continue execution until the graph reaches the END node.</li><li>Return the final state as the workflow&rsquo;s output.</li></ul><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/create-graph-langgraph.png?sfvrsn=7964f8a5_2" alt="Create state graph, add nodes, add edges, compile, invoke, execute state (reducers), reach END, return final state" /></p><p>Think of the graph as the orchestrator, the nodes as workers performing tasks, and the state as the shared memory that connects them.</p><p>There are three steps to build the graph:</p><ol><li>Create a <code>StateGraph</code> with your schema</li><li>Register nodes and edges</li><li>Call <code>.compile()</code> to get a runnable graph</li></ol><p>We can create a graph for <code>CounterState</code> as shown below:</p><p><strong>counter.graph.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">buildCounterGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>CounterStateSchema<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"increment"</span><span class="token punctuation">,</span> incrementNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"incrementTwice"</span><span class="token punctuation">,</span> incrementTwiceNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"doubleCount"</span><span class="token punctuation">,</span> doubleCountNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"increment"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"increment"</span><span class="token punctuation">,</span> <span class="token string">"incrementTwice"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"incrementTwice"</span><span class="token punctuation">,</span> <span class="token string">"doubleCount"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"doubleCount"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>As you can see:</p><ul><li><code>addNode()</code> adds a node to the graph. Each node is a step.</li><li><code>addEdge()</code> is used to add an edge to the graph. Edges know the step by the string name, not the node function name.</li><li><code>new StateGraph()</code> creates a graph bound to the state schema definition.</li><li><code>.compile()</code> validates the graph. It checks for:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a. Is there any path from START to END<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b. Do node name edge exist<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c. Is the schema wired correctly<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d. Then returns a compiled graph</li></ul><p>In the same way, you can create a graph for <code>LastCountState</code>:</p><p><strong>lastcount.graph.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">buildLastCountGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>LastCountStateSchema<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"setOne"</span><span class="token punctuation">,</span> setOneNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"setFive"</span><span class="token punctuation">,</span> setFiveNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"setNine"</span><span class="token punctuation">,</span> setNineNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"setOne"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"setOne"</span><span class="token punctuation">,</span> <span class="token string">"setFive"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"setFive"</span><span class="token punctuation">,</span> <span class="token string">"setNine"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"setNine"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>We use <code>addEdge()</code> to connect nodes and define the execution flow within the graph. Key characteristics of <code>addEdge()</code> are:</p><ul><li>The <code>addEdge(startKey, endKey)</code> creates an unconditional edge.</li><li>Once the <code>startKey</code> node completes, execution always proceeds to the <code>endKey</code> node.</li><li>Possible values of <code>startKey</code> are START or node name or node name [].</li><li>Possible values of <code>endKey</code> are END or node name.</li></ul><p>In the count graph, we start at the increment node and create a linear chain to the <code>doubleCount</code> node.</p><pre class=" language-ts"><code class="prism  language-ts">    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"increment"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"increment"</span><span class="token punctuation">,</span> <span class="token string">"incrementTwice"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"incrementTwice"</span><span class="token punctuation">,</span> <span class="token string">"doubleCount"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"doubleCount"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
</code></pre><p>This is a linear chain.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/langgraph-chain.png?sfvrsn=eb236f6e_2" alt="Start, increment, increment twice, double count, end" /></p><h2 id="graph-rules-to-remember">Graph Rules to Remember</h2><p>When building a LangGraph workflow, keep the following rules in mind:</p><ol><li><strong>Node names must be valid.</strong> <br />Every node referenced in an edge must either be a registered node name or one of the special nodes: START or END.</li><br /><li><strong>Use the graph node name, not the function name.</strong> <br />When creating edges, refer to the string name assigned in <code>addNode()</code>, not the underlying function name.</li><br /><li><strong>A graph must have a starting path.</strong> <br />Every runnable graph must contain at least one edge originating from START.</li><br /><li><strong>A graph must have a termination path.</strong> <br />Every runnable graph must contain at least one edge leading to END.</li><br /><li><strong>Unreachable nodes are dead code.</strong> <br />If a node cannot be reached from START, it will never execute and is effectively dead code within the graph.</li><br /><li><strong>addEdge() creates unconditional transitions.</strong> <br /><code>addEdge()</code> does not evaluate the graph state or any conditions. Execution always follows the defined path. If you need conditional routing (for example, &ldquo;if count &gt; 5 go here, otherwise go there&rdquo;), use <code>addConditionalEdges()</code>. We&rsquo;ll explore conditional routing in the next article.</li></ol><h2 id="invoking-the-graph">Invoking the Graph</h2><p>We can invoke the graph using the <code>invoke()</code> method.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> graph <span class="token operator">=</span> <span class="token function">buildCounterGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> graph<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The invoke method returns the final state, and you should see output <code>{count: 6}</code>. There is another method, <code>stream()</code>, to read the state after each node&rsquo;s execution, and we will cover it in the next article.</p><p>In the same way, you can invoke <code>LastCountGraph</code> as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> graph1 <span class="token operator">=</span> <span class="token function">buildLastCountGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> result1 <span class="token operator">=</span> <span class="token keyword">await</span> graph1<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result1<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You should get the result <code>{count: 9}</code>.</p><p>In this way, you can create a basic LangGraph graph using state, state schema, edges and nodes.</p><p>The following diagram brings together all the concepts we have learned so far and illustrates the main building blocks of a LangGraph.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/langgraph-state-nodes-edges-recap.png?sfvrsn=886776fd_2" alt="1. State: shared memory. State schema shared across all nodes. Nodes read full state. Nodes return partial updates. 2. Nodes: units of work. LLM calls, tools, logic. Compoable and testable. 3. Edges: control flow. Fixed edges: A always to B. Conditional edges via route logic. Enables loops. Enables branches." /></p><h2 id="fifa-chat-example">FIFA Chat Example</h2><p>Now that we&rsquo;ve covered the core LangGraph concepts, let&rsquo;s put them into practice by building a chat application that answers questions about the FIFA World Cup.</p><p>Let us start by defining the state with <code>MessageValue</code>.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> schema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span> messages<span class="token punctuation">:</span> MessagesValue <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Next, create the language model for our FIFA World Cup chatbot using OpenAI&rsquo;s GPT. Before initializing the model, add your OpenAI API key to the .env file.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> model <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatOpenAI</span><span class="token punctuation">(</span><span class="token punctuation">{</span> model<span class="token punctuation">:</span> <span class="token string">"gpt-4o-mini"</span><span class="token punctuation">,</span> temperature<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Next, set the system prompt such that the model only answers about FIFA and does not answer on other topics.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> SYSTEM <span class="token operator">=</span> <span class="token template-string"><span class="token string">`You are a FIFA World Cup expert. ONLY answer questions about the FIFA World Cup (1930&ndash;present): winners, hosts, matches, records, players in World Cup context.
For anything else, reply exactly: "I can only help with FIFA World Cup questions."`</span></span><span class="token punctuation">;</span>
</code></pre><p>Next, create the node that acts as an agent. It will read messages, call the models and return the model&rsquo;s new reply. The <code>MessageValue</code> reducer will append the message to the message history.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> agent<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>Node <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">await</span> model<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Next, let&rsquo;s build the graph. In this example, the graph consists of a single node with an incoming START edge and an outgoing END edge.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> graph <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>schema<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> agent<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"agent"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Then, we seed the state with the <code>SystemMessage</code>:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">let</span> state<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>State <span class="token operator">=</span> <span class="token punctuation">{</span> messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">SystemMessage</span><span class="token punctuation">(</span>SYSTEM<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>Finally, inside the loop, we invoke the graph as below:</p><pre class=" language-ts"><code class="prism  language-ts">  state <span class="token operator">=</span> <span class="token keyword">await</span> graph<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token operator">...</span>state<span class="token punctuation">,</span>
    messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">HumanMessage</span><span class="token punctuation">(</span>question<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Here we are using schema.state, which is the full state graph read and write. Putting everything together, FIFA Chat Bot should look like below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> stdin <span class="token keyword">as</span> input<span class="token punctuation">,</span> stdout <span class="token keyword">as</span> output <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"node:process"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> readline <span class="token keyword">from</span> <span class="token string">"node:readline/promises"</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> HumanMessage<span class="token punctuation">,</span> SystemMessage <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/core/messages"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ChatOpenAI <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/openai"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> END<span class="token punctuation">,</span> MessagesValue<span class="token punctuation">,</span> START<span class="token punctuation">,</span> StateGraph<span class="token punctuation">,</span> StateSchema <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/langgraph"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">"dotenv/config"</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> schema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span> messages<span class="token punctuation">:</span> MessagesValue <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> model <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatOpenAI</span><span class="token punctuation">(</span><span class="token punctuation">{</span> model<span class="token punctuation">:</span> <span class="token string">"gpt-4o-mini"</span><span class="token punctuation">,</span> temperature<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> SYSTEM <span class="token operator">=</span> <span class="token template-string"><span class="token string">`You are a FIFA World Cup expert. ONLY answer questions about the FIFA World Cup (1930&ndash;present): winners, hosts, matches, records, players in World Cup context.
For anything else, reply exactly: "I can only help with FIFA World Cup questions."`</span></span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> agent<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>Node <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">await</span> model<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> graph <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>schema<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> agent<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"agent"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">let</span> state<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>State <span class="token operator">=</span> <span class="token punctuation">{</span> messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">SystemMessage</span><span class="token punctuation">(</span>SYSTEM<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> rl <span class="token operator">=</span> readline<span class="token punctuation">.</span><span class="token function">createInterface</span><span class="token punctuation">(</span><span class="token punctuation">{</span> input<span class="token punctuation">,</span> output <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"⚽ FIFA World Cup Chat &mdash; type 'exit' to quit\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> question <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">await</span> rl<span class="token punctuation">.</span><span class="token function">question</span><span class="token punctuation">(</span><span class="token string">"You: "</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>question<span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>question <span class="token operator">===</span> <span class="token string">"exit"</span> <span class="token operator">||</span> question <span class="token operator">===</span> <span class="token string">"quit"</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>

  state <span class="token operator">=</span> <span class="token keyword">await</span> graph<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token operator">...</span>state<span class="token punctuation">,</span>
    messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">HumanMessage</span><span class="token punctuation">(</span>question<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`\nAssistant: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span>content<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

rl<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You have created a FIFA chatbot using Graph, which uses nodes, state and edges. In further articles, we go deeper into creating agents using LangGraph. I hope you find this article useful. Thanks for reading.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">How to Build Your First LLM Application Using LangChain and TypeScript</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Learn the quick and easy steps to building your first <a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript">LLM-powered TypeScript application using LangChain and the OpenAI model.</a></p><a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript"></a></div><a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript"></a></div><a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript"></a></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:8882a414-c4af-4732-bc5f-78a9c3a0c4a2</id>
    <title type="text">Understanding the Modern AI Stack</title>
    <summary type="text">Grasp AI terminology with these helpful parallels from the full-stack development world.</summary>
    <published>2026-08-25T15:19:29Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/understanding-modern-ai-stack"/>
    <content type="text"><![CDATA[<p><span class="featured">Grasp AI terminology with these helpful parallels from the full-stack development world.</span></p><p>AI engineering comes with a vocabulary hurdle. LLMs, embeddings, vector databases, RAG, agents, memory, orchestration, evaluation, observability: the terms tend to arrive all at once, and most explanations treat them as a glossary of disconnected definitions. That makes the field feel larger and stranger than it is.</p><p>In the <a target="_blank" href="https://www.telerik.com/blogs/new-full-stack-developer-building-applications-humans-ai-assistants-autonomous-agents">first article</a> of this <a target="_blank" href="https://www.telerik.com/blogs/tag/ai-engineering-basics">AI Engineering Basics series</a>, we made the case that the full-stack role is expanding rather than being replaced, and that the developers who adapt will treat AI as a new layer of the stack to master. This article builds the mental model for that layer. Every term above has a job; the jobs relate to each other the same way the pieces of a web application do, and once we see the structure, the terms fall into place.</p><p>We&rsquo;ll organize the stack into five layers, and each one maps onto an instinct we already have:</p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th>Layer</th><th>Components</th><th>Closest full-stack analogy</th></tr></thead><tbody><tr><td>Model</td><td>LLMs</td><td>A runtime we call over an API</td></tr><tr><td>Knowledge</td><td>Embeddings, vector databases, RAG</td><td>The database and query layer</td></tr><tr><td>Action</td><td>Agents, tools, memory</td><td>Business logic and session state</td></tr><tr><td>Coordination</td><td>Orchestration</td><td>Backend workflows and job queues</td></tr><tr><td>Trust</td><td>Evaluation, observability</td><td>Testing and monitoring</td></tr></tbody></table><br /><p>To keep things concrete, we&rsquo;ll follow a single example throughout: a customer support assistant for TaskFlow, a fictional project management product. By the end, we&rsquo;ll trace one user request through all five layers.</p><h2 id="the-model-layer-llms">The Model Layer: LLMs</h2><p>At the base of the stack sits the <strong>large language model (LLM)</strong>, the component that understands and generates language. Models from providers like <a target="_blank" href="https://platform.openai.com">OpenAI</a>, <a target="_blank" href="https://anthropic.com">Anthropic</a> and <a target="_blank" href="https://ai.google.dev">Google</a> are available behind ordinary APIs, and calling one looks like calling any other web service. A minimal request for our TaskFlow assistant:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> openai<span class="token punctuation">.</span>responses<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  model<span class="token punctuation">:</span> <span class="token string">"gpt-5"</span><span class="token punctuation">,</span>
  instructions<span class="token punctuation">:</span> <span class="token string">"You are a support assistant for TaskFlow."</span><span class="token punctuation">,</span>
  input<span class="token punctuation">:</span> <span class="token string">"How do I export my project data?"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>output_text<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We send the user&rsquo;s input along with instructions that set the assistant&rsquo;s role, and we get generated text back. There&rsquo;s no infrastructure to manage and no machine learning background required.</p><p>Two properties of this call shape everything else in the stack.</p><p>First, the model itself is <strong>stateless</strong>. It retains nothing between requests, so anything it should know (the conversation so far, the user&rsquo;s plan, the relevant documentation) must be assembled into each request. Provider APIs can do some of that bookkeeping for us; the OpenAI Responses API, for example, can <a target="_blank" href="https://developers.openai.com">store conversation state server-side</a> and chain turns with a <code>previous_response_id</code> parameter. The convenience leaves the underlying principle intact, since the full history still gets fed to the model (and billed as input tokens) on each call.</p><p>Second, the request has a size limit called the <strong>context window</strong>, measured in tokens, which caps how much we can include. A model&rsquo;s knowledge is otherwise frozen at training time, which means our TaskFlow assistant, out of the box, knows nothing about TaskFlow.</p><p>The model, in other words, is a powerful runtime with no memory and no knowledge of our product, and the rest of the stack exists to work around those two gaps.</p><h2 id="the-knowledge-layer-embeddings-vector-databases-and-rag">The Knowledge Layer: Embeddings, Vector Databases and RAG</h2><p>Our assistant needs to answer questions about TaskFlow&rsquo;s actual documentation, and stuffing every doc into each request would blow through the context window (and the budget). The knowledge layer solves this by finding the few passages that matter for a given question and supplying only those.</p><p>The mechanism underneath is the <strong>embedding</strong>, a representation of text as a vector of numbers. Think of it as a coordinate system for meaning: just as GPS coordinates position a location on a map, an embedding positions a piece of text in a high-dimensional space where distance reflects similarity of meaning. The embeddings for &ldquo;How do I export my project data?&rdquo; and &ldquo;Downloading your projects as CSV&rdquo; land close together in that space despite sharing almost no words, while a passage about billing lands far away.</p><p>Those vectors need a home, which is what a <strong>vector database</strong> provides. It stores the embeddings for every chunk of our documentation and can answer the question &ldquo;which stored chunks are closest to this query?&rdquo; across millions of entries in milliseconds.</p><p><strong>Retrieval-Augmented Generation (RAG)</strong> is the pattern that ties these pieces to the model. When a user asks a question, the system retrieves the most relevant chunks from the vector database, augments the prompt with them and asks the model to generate an answer grounded in that material. The model still does the writing, but it works from our documentation rather than its training data, which keeps answers current and lets them cite sources. Platforms like <a target="_blank" href="https://www.progress.com/agentic-rag">Progress Agentic RAG</a> package this whole layer (chunking, embeddings, indexing, retrieval) as a service, and we&rsquo;ll dig into RAG in a dedicated article later in this series.</p><h2 id="the-action-layer-agents-tools-and-memory">The Action Layer: Agents, Tools and Memory</h2><p>Answering questions only gets our assistant so far. A user who asks &ldquo;Can you move all my overdue tasks to next sprint?&rdquo; wants something done, and doing things is the action layer&rsquo;s job.</p><p>We covered the assistant-versus-agent distinction in the first article, so a short recap suffices here. An <strong>agent</strong> receives a goal rather than a question, plans the steps to reach it, acts, checks the results and adjusts until the task is done.</p><p><strong>Tool use</strong> is what makes that possible. A tool is an operation we expose to the model with a name, a description, and typed parameters, like <code>move_task(task_id, sprint_id)</code> or <code>get_overdue_tasks(project_id)</code>. The model doesn&rsquo;t execute anything itself; it outputs which tool it wants to call and with what arguments, our code runs the call, and the result goes back into the conversation for the model&rsquo;s next decision. The <a target="_blank" href="https://modelcontextprotocol.io">Model Context Protocol (MCP)</a> is the emerging standard for wiring these connections consistently.</p><p><strong>Memory</strong> rounds out the layer, and it splits into two kinds. Short-term memory is the running conversation, resent with each request so the model can follow references like &ldquo;move those ones too.&rdquo; Long-term memory persists across sessions in ordinary storage we control: when our assistant remembers that a user prefers two-week sprints, our application saved that fact and retrieved it into the context at the right moment. Memory patterns get their own article later in the series.</p><h2 id="the-coordination-layer-orchestration">The Coordination Layer: Orchestration</h2><p>A real support interaction rarely fits in one model call. Consider a TaskFlow user reporting a duplicate charge. A production-quality handling of that request might start by classifying the message as a billing issue, retrieving the refund policy and looking up the account&rsquo;s payment history through a tool. From there, it drafts a response and routes the case to a human when the refund amount exceeds a threshold. Each step might involve a different model call, a different tool or no model at all.</p><p><strong>Orchestration</strong> is the layer that coordinates these multi-step flows. It carries state from step to step and decides which branch to take based on intermediate results. It also handles the operational details: retrying transient failures, enforcing timeouts, and managing handoffs between models, tools and humans.</p><p>If the model layer is a runtime, orchestration is the backend workflow engine wrapped around it, and the discipline it demands is the same one we apply to any distributed system: clear steps, explicit state and predictable failure handling. Frameworks exist to help (<a target="_blank" href="https://langchain.com">LangGraph</a> is a prominent example), though plenty of production systems orchestrate with plain application code.</p><h2 id="the-trust-layer-evaluation-and-observability">The Trust Layer: Evaluation and Observability</h2><p>Everything up to this point gets a system working. The trust layer is what lets us ship it and keep it running, and it&rsquo;s the part of the stack that teams most often discover too late.</p><p><strong>Evaluation</strong> answers the question &ldquo;is this working?&rdquo; for systems whose outputs aren&rsquo;t deterministic. A traditional assertion like <code>expect(output).toBe(expected)</code> falls apart when the same input can produce different, equally valid responses. Instead, we build evaluation sets: collections of representative inputs paired with criteria for judging the outputs, whether that judgment comes from string checks, from rules or from another model acting as a grader. Evaluations run whenever we change a prompt, swap a model or adjust retrieval, serving the same role a regression suite serves in conventional software.</p><p><strong>Observability</strong> answers &ldquo;what is it doing in production?&rdquo; For our TaskFlow assistant, that means tracing each request through every layer it touched: the documents retrieval returned, the tools the agent called and what each model call consumed and cost.</p><p>Token usage deserves particular attention because it&rsquo;s both the cost model and an early warning signal. A prompt change that doubles token consumption shows up in the bill before it shows up anywhere else.</p><p>Traces also turn debugging from guesswork into inspection, since we can see when retrieval surfaced the wrong document or when a tool returned an error that the model papered over.</p><p>This layer is also where governance lives: knowing what our AI systems are doing, proving it and enforcing limits on agent behavior. The AI operations articles later in this series spend most of their time here.</p><h2 id="one-request-through-the-stack">One Request Through the Stack</h2><p>To pull the layers together, we can follow a single message end to end. A TaskFlow user writes: &ldquo;I was charged twice for my Pro subscription this month. Can I get a refund?&rdquo;</p><ol><li><strong>Orchestration</strong> receives the message and classifies it as a billing issue with a refund request.</li><li>The <strong>knowledge layer</strong> retrieves TaskFlow&rsquo;s refund policy and the duplicate-charge troubleshooting guide from the vector database.</li><li>The <strong>action layer</strong> calls the <code>get_payment_history</code> tool, confirms the duplicate charge and checks <strong>memory</strong> for prior billing issues on the account.</li><li>The <strong>model</strong> receives the policy, the payment data and the conversation, and drafts a response with a proposed refund.</li><li>The refund amount falls under the auto-approval threshold, so orchestration executes the <code>create_refund</code> tool call; had it been larger, the flow would have paused for human approval.</li><li>The <strong>trust layer</strong> records the full trace: documents retrieved, tools called, tokens spent and the outcome, ready for review and for the next evaluation run.</li></ol><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/one-request-through-the-stack.webp?sfvrsn=903ad029_1" height="2880" style="max-width:100%;height:auto;" title="one-request-through-the-stack" width="2880" alt="One Request Through the Stack diagram: Orchestration, Knowledge, Action, Model, Trust" sf-size="314628" /></p><p>Notice that no single layer did anything magical. The model wrote text, retrieval found documents and tools touched real systems. Orchestration kept the steps in order while the trust layer recorded it all. The modern AI stack amounts to familiar engineering arranged around one unfamiliar component.</p><h2 id="wrap-up">Wrap-up</h2><p>The vocabulary of AI engineering describes a stack with real structure. Models generate, the knowledge layer grounds them in our data, the action layer lets them do things, orchestration coordinates the steps, and evaluation and observability make the whole thing trustworthy enough to operate.</p><p>Each layer maps onto skills full-stack developers already practice, and each one gets a deeper treatment as this series continues, starting with how these pieces are architected into real applications.</p><p>For more on the platforms and standards mentioned in this article, check out the following resources:</p><ul><li><a href="https://www.telerik.com/ai-engineering" target="_blank">Progress AI Engineering Solutions</a></li><li><a target="_blank" href="https://www.progress.com/agentic-rag">Progress Agentic RAG</a></li><li><a target="_blank" href="https://modelcontextprotocol.io">Model Context Protocol Documentation</a></li></ul>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:a5f31a91-17e7-48b5-92b9-4ddf5d9050e5</id>
    <title type="text">When to Use ListView vs. DataGrid in Real-World Projects</title>
    <summary type="text">Depending on the type of user, the number of fields and the tasks being performed, does your Blazor app need a DataGrid or a ListView?</summary>
    <published>2026-08-25T12:59:09Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/when-use-listview-vs-datagrid-real-world-projects"/>
    <content type="text"><![CDATA[<p><span class="featured">Depending on the type of user, the number of fields and the tasks being performed, does your Blazor app need a DataGrid or a ListView?</span></p><p>Displaying data to users is common in web applications. One question you may ask, and which doesn&rsquo;t always have the same answer, is how to choose the right component to do it.</p><p>Would it be appropriate to show a table with all the columns? Or would a card gallery like those used in mobile apps be better?</p><p>For that reason, in this article we will analyze the components <a target="_blank" href="https://www.telerik.com/blazor-ui/listview">Blazor ListView</a> and <a target="_blank" href="https://www.telerik.com/blazor-ui/grid">Blazor DataGrid</a> from the Progress Telerik UI library, comparing their features. Let&rsquo;s go!</p><h2 id="the-problem-of-choosing-the-wrong-component">The Problem of Choosing the Wrong Component</h2><p>How we choose to display information in our web application depends on several variables:</p><ul><li><strong>The type of user:</strong> A user who requires detailed information about an item is not the same as someone who only needs to see general information.</li><li><strong>The amount of fields to display:</strong> Quickly viewing information is not the same as analyzing and comparing data.</li><li><strong>The task to perform with the data:</strong> You may need to perform complex operations like grouping, applying filters or making comparisons between them, or you may not.</li></ul><p>Not being clear about the purpose of using the information could lead to poor user experiences or make it difficult to work with the data.</p><h2 id="getting-to-know-the-blazor-datagrid-component">Getting to Know the Blazor DataGrid Component</h2><p>The Blazor DataGrid component allows viewing data in a tabular form. The idea of the component is to display many records in a table format with all the necessary tools for a user to analyze them.</p><p>It is a quite robust and flexible component, which in its latest versions incorporates AI features for better data understanding. Among its main capabilities we can find:</p><ul><li>Sorting by one or multiple columns</li><li>Filtering on each column</li><li>Grouping by dragging headers</li><li>Paging with traditional pagination or virtual scrolling</li><li>Inline or popup editing of records</li><li>Resizable, reorderable and lockable columns</li><li>Exporting to Excel and PDF</li><li>Single or multiple selection</li><li>Customization using templates</li><li>Drag &amp; drop support</li><li>Smart AI features that work with natural language queries</li></ul><p>From the characteristics described, you can notice that we are talking about a component that should be used when users want to see many rows and fields at the same time and be able to manipulate them.</p><p>Some ideal use cases for this component are administrative panels, ERPs, support dashboards, internal tools or any interface where data takes priority over aesthetics.</p><h2 id="the-blazor-listview-component">The Blazor ListView Component</h2><p>The Blazor ListView component can be considered a freer component than the Blazor Grid. Instead of a tabular structure, the data is provided to you as a context that you can render as you wish. This means you can customize the UI to create cards, timelines, custom rows or any Razor structure you want.</p><p>Some of the component&rsquo;s main features include:</p><ul><li>Customizable built-in paging</li><li>On-demand loading</li><li>Handling of CUD operations through built-in commands</li><li>Custom data source operations</li><li>Fully customizable templates</li></ul><p>This component is ideal when the data is not tabular or when the layout needs to adapt to multiple screen styles. Some examples of when to use this component are news feeds, visual catalogs, item galleries, among others.</p><h2 id="when-to-use-a-grid-vs.-a-listview">When to Use a Grid vs. a ListView?</h2><p>In addition to the analysis we&rsquo;ve done for each component, I&rsquo;d like to share some comparative points about when to use a Grid and when to use a ListView.</p><p>It&rsquo;s advisable to use a Grid when:</p><ul><li>You need the user to be able to compare values across rows</li><li>Each record has many fields to be analyzed</li><li>Screen space is not an issue</li><li>You require complex operations such as sorting, filtering or grouping</li><li>You need to edit data in bulk</li><li>You need to export table data</li></ul><p>On the other hand, it&rsquo;s advisable to use a ListView when:</p><ul><li>Records have few fields or are ideal for visually rich interfaces</li><li>User interaction involves viewing items one at a time, not analyzing them</li><li>You need full control over the layout of each item</li><li>You are building some type of app that includes a card-like component or similar</li></ul><p>Let&rsquo;s see how both views look in a real example.</p><h2 id="creating-a-blazor-project-with-listview-and-grid">Creating a Blazor Project with ListView and Grid</h2><p>To demonstrate the difference between the two components and see when it&rsquo;s convenient to use one over the other, let&rsquo;s create a component that loads 50 orders and allows switching the view between a ListView-style and a Grid-style.</p><p>To achieve this, start by creating a project with the <strong>Blazor Web App</strong> template, selecting <strong>Interactive render mode</strong> in <code>Server</code> and <strong>Interactivity location</strong> in <code>Global</code>. Then, you can follow the <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/getting-started/web-app">official installation guide for Telerik UI for Blazor</a> to install the Telerik components in your project.</p><h3 id="defining-the-app-data-model">Defining the App Data Model</h3><p>Once the Telerik components are installed in the project, the next step will be to create the data model that represents a sales order in the system. For this, we will use a <code>record</code> type as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> record <span class="token function">SalesOrder</span><span class="token punctuation">(</span>
    <span class="token keyword">int</span> OrderId<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Customer<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Product<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Category<span class="token punctuation">,</span>
    <span class="token keyword">int</span> Quantity<span class="token punctuation">,</span>
    <span class="token keyword">decimal</span> UnitPrice<span class="token punctuation">,</span>
    DateTime OrderDate<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Status<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token operator">=</span><span class="token operator">&gt;</span> Quantity <span class="token operator">*</span> UnitPrice<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s create a service that will generate the fictitious orders.</p><h3 id="creating-a-data-service">Creating a Data Service</h3><p>Let&rsquo;s create a service class that will be used to generate the fictitious orders. For this, let&rsquo;s add an interface and its corresponding implementation:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ISalesService</span>
<span class="token punctuation">{</span>
    IReadOnlyList<span class="token operator">&lt;</span>SalesOrder<span class="token operator">&gt;</span> <span class="token function">GetOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SalesService</span> <span class="token punctuation">:</span> ISalesService
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Customers <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token string">"Contoso"</span><span class="token punctuation">,</span> <span class="token string">"Fabrikam"</span><span class="token punctuation">,</span> <span class="token string">"Adventure Works"</span><span class="token punctuation">,</span>
        <span class="token string">"Northwind Traders"</span><span class="token punctuation">,</span> <span class="token string">"Telerik"</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token punctuation">(</span><span class="token keyword">string</span> Product<span class="token punctuation">,</span> <span class="token keyword">string</span> Category<span class="token punctuation">,</span> <span class="token keyword">decimal</span> Price<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Catalog <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token punctuation">(</span><span class="token string">"Laptop Pro 15"</span><span class="token punctuation">,</span>      <span class="token string">"Electronics"</span><span class="token punctuation">,</span> <span class="token number">1299</span><span class="token punctuation">.</span>99m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Wireless Mouse"</span><span class="token punctuation">,</span>     <span class="token string">"Electronics"</span><span class="token punctuation">,</span>   <span class="token number">45</span><span class="token punctuation">.</span>00m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"4K Monitor"</span><span class="token punctuation">,</span>         <span class="token string">"Electronics"</span><span class="token punctuation">,</span>  <span class="token number">399</span><span class="token punctuation">.</span>50m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Mechanical Keyboard"</span><span class="token punctuation">,</span><span class="token string">"Electronics"</span><span class="token punctuation">,</span>  <span class="token number">129</span><span class="token punctuation">.</span>99m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Running Shoes"</span><span class="token punctuation">,</span>      <span class="token string">"Clothing"</span><span class="token punctuation">,</span>      <span class="token number">89</span><span class="token punctuation">.</span>95m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Winter Jacket"</span><span class="token punctuation">,</span>      <span class="token string">"Clothing"</span><span class="token punctuation">,</span>     <span class="token number">159</span><span class="token punctuation">.</span>99m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Organic Coffee"</span><span class="token punctuation">,</span>     <span class="token string">"Food"</span><span class="token punctuation">,</span>          <span class="token number">24</span><span class="token punctuation">.</span>50m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Premium Tea Box"</span><span class="token punctuation">,</span>    <span class="token string">"Food"</span><span class="token punctuation">,</span>          <span class="token number">18</span><span class="token punctuation">.</span>75m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Office Chair"</span><span class="token punctuation">,</span>       <span class="token string">"Furniture"</span><span class="token punctuation">,</span>    <span class="token number">249</span><span class="token punctuation">.</span>00m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Standing Desk"</span><span class="token punctuation">,</span>      <span class="token string">"Furniture"</span><span class="token punctuation">,</span>    <span class="token number">599</span><span class="token punctuation">.</span>00m<span class="token punctuation">)</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Statuses <span class="token operator">=</span>
        <span class="token punctuation">[</span><span class="token string">"Pending"</span><span class="token punctuation">,</span> <span class="token string">"Shipped"</span><span class="token punctuation">,</span> <span class="token string">"Delivered"</span><span class="token punctuation">,</span> <span class="token string">"Cancelled"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> IReadOnlyList<span class="token operator">&lt;</span>SalesOrder<span class="token operator">&gt;</span> <span class="token function">GetOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> random <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>SalesOrder<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">50</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">var</span> item <span class="token operator">=</span> Catalog<span class="token punctuation">[</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Catalog<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
            orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">SalesOrder</span><span class="token punctuation">(</span>
                OrderId<span class="token punctuation">:</span> <span class="token number">1000</span> <span class="token operator">+</span> i<span class="token punctuation">,</span>
                Customer<span class="token punctuation">:</span> Customers<span class="token punctuation">[</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Customers<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
                Product<span class="token punctuation">:</span> item<span class="token punctuation">.</span>Product<span class="token punctuation">,</span>
                Category<span class="token punctuation">:</span> item<span class="token punctuation">.</span>Category<span class="token punctuation">,</span>
                Quantity<span class="token punctuation">:</span> random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                UnitPrice<span class="token punctuation">:</span> item<span class="token punctuation">.</span>Price<span class="token punctuation">,</span>
                OrderDate<span class="token punctuation">:</span> DateTime<span class="token punctuation">.</span>Today<span class="token punctuation">.</span><span class="token function">AddDays</span><span class="token punctuation">(</span><span class="token operator">-</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                Status<span class="token punctuation">:</span> Statuses<span class="token punctuation">[</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Statuses<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">return</span> orders<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The code is simple, defining arrays with information that will simulate a business dataset, as well as generating 50 orders by randomly combining the arrays.</p><p>To be able to inject the service, we will go to <code>Program.cs</code>, where we will add it as <code>Singleton</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation">&lt;</span>ISalesService<span class="token punctuation">,</span> SalesService<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With the service ready, we can start with the visual tests.</p><h3 id="preparing-the-orders-page">Preparing the Orders Page</h3><p>In the <code>Components\Pages</code> folder we will create a new component called <code>SalesOrders.razor</code>, which will look as follows:</p><pre class=" language-xml"><code class="prism  language-xml">@page "/sales-orders"
@using SalesOrderListGridDemo.Services
@using SalesOrderListGridDemo.Models
@rendermode InteractiveServer
@inject ISalesService SalesService

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PageTitle</span><span class="token punctuation">&gt;</span></span>Sales Orders<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>PageTitle</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Sales Orders<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    Switch between the <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>ListView<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> and the
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>DataGrid<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> using the toggle below.
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sales-toolbar<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikButtonGroup</span> <span class="token attr-name">SelectionMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ButtonGroupSelectionMode.Single<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ButtonGroupToggleButton</span> <span class="token attr-name">Selected</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(currentView == ViewMode.ListView)<span class="token punctuation">"</span></span>
                                 <span class="token attr-name">SelectedChanged</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(_ =&gt; SetView(ViewMode.ListView))<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            ListView
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ButtonGroupToggleButton</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ButtonGroupToggleButton</span> <span class="token attr-name">Selected</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(currentView == ViewMode.Grid)<span class="token punctuation">"</span></span>
                                 <span class="token attr-name">SelectedChanged</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(_ =&gt; SetView(ViewMode.Grid))<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            DataGrid
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ButtonGroupToggleButton</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikButtonGroup</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@orders.Count orders loaded<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

@code {
    private enum ViewMode { ListView, Grid }

    private List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>SalesOrder</span><span class="token punctuation">&gt;</span></span> orders = new();
    private ViewMode currentView = ViewMode.ListView;

    protected override void OnInitialized()
    {
        orders = SalesService.GetOrders().ToList();
    }

    private void SetView(ViewMode view) =&gt; currentView = view;
}
</code></pre><p>In the code of the previous page, you can notice a few things:</p><ul><li>We have created a <code>enum</code> with the options <code>ListView</code> and <code>Grid</code>, which will allow us to switch between views.</li><li>We load the orders into <code>OnInitialized</code>.</li><li>The method <code>SetView</code> allows changing the view.</li><li>We use a <a target="_blank" href="https://www.telerik.com/blazor-ui/buttongroup">Blazor Button Group</a> component, ideal for handling multiple grouped buttons. In this case, we implement it to activate one view or the other through the assignment of a state with <code>Selected</code>. Also, when a button is clicked, <code>SelectedChanged</code> is fired, which invokes the method <code>SetView</code>.</li></ul><p>With the options section ready, let&rsquo;s render the ListView component.</p><h3 id="rendering-a-blazor-listview">Rendering a Blazor ListView</h3><p>The next step will be to render the ListView. To do this, we will add a conditional block and use the component <code>TelerikListView</code>:</p><pre class=" language-xml"><code class="prism  language-xml">@if (currentView == ViewMode.ListView)
{
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikListView</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@orders<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Pageable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                     <span class="token attr-name">PageSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>9<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Template</span> <span class="token attr-name">Context</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-header<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-id<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>#@order.OrderId<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status-badge status-@order.Status<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Status<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-product<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-meta<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Customer   @order.Category<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-meta<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.OrderDate.ToString("MMM dd, yyyy")<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-footer<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Quantity   @order.UnitPrice.ToString("C")<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-total<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Total.ToString("C")<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Template</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikListView</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>In the previous code, the property <code>Data</code> binds to the service&rsquo;s list. We configure some additional properties such as paging (<code>Pageable</code>) and number of items per page (<code>PageSize</code>). Also, we use a <code>Template</code> to define a custom view, highlighting only the most important data such as product, customer, category, date, total, etc.</p><p>To make the layout look correct, we will add some visual styles in <code>wwwroot/app.css</code>:</p><pre class=" language-css"><code class="prism  language-css"><span class="token selector"><span class="token class">.sales-toolbar</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
    <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">margin-bottom</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card</span> </span><span class="token punctuation">{</span>
    <span class="token property">border</span><span class="token punctuation">:</span> <span class="token number">1</span>px solid <span class="token hexcode">#e2e8f0</span><span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">0.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#fff</span><span class="token punctuation">;</span>
    <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">1</span>px <span class="token number">2</span>px <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token number">0</span>,<span class="token number">0</span>,<span class="token number">0</span>,<span class="token number">0.04</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
    <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">0.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">100%</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card-header</span>,
<span class="token class">.order-card-footer</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>
    <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card-product</span> </span><span class="token punctuation">{</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">1.1</span>rem<span class="token punctuation">;</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token number">600</span><span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#0f172a</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card-meta</span> </span><span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#64748b</span><span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">0.9</span>rem<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-total</span> </span><span class="token punctuation">{</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token number">700</span><span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#0f172a</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.status-badge</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
    <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">0.15</span>rem <span class="token number">0.6</span>rem<span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">999</span>px<span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">0.75</span>rem<span class="token punctuation">;</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token number">600</span><span class="token punctuation">;</span>
    <span class="token property">text-transform</span><span class="token punctuation">:</span> uppercase<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.status-Pending</span>   </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#fef3c7</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#92400e</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.status-Shipped</span>   </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#dbeafe</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#1e40af</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.status-Delivered</span> </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#dcfce7</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#166534</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.status-Cancelled</span> </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#fee2e2</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#991b1b</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

</code></pre><p>When running the application, we can see the layout we created using the ListView:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/listview-sales-orders-sample.png?sfvrsn=ffdd2cd6_2" alt="Blazor ListView displaying sample sales orders" /></p><p>In the image above, you can notice that there is no comparison between orders; rather, there is navigation between the list items. If users wanted to group items, filter them or perform complex operations, this would not be the right component. Let&rsquo;s now see how to implement a Grid.</p><h3 id="adding-a-grid-component-to-the-app">Adding a Grid Component to the App</h3><p>Let&rsquo;s see how a Grid component looks, completing the <code>else</code> branch of the conditional in the code to add a <code>TelerikGrid</code>:</p><pre class=" language-xml"><code class="prism  language-xml">else
{
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikGrid</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@orders<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Pageable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">PageSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>15<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Sortable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">FilterMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@GridFilterMode.FilterRow<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Groupable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Resizable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Reorderable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumns</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.OrderId)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Order #<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Customer)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Customer<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Product)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Product<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Category)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Category<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>140px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Quantity)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Qty<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>90px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.UnitPrice)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Unit Price<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130px<span class="token punctuation">"</span></span> <span class="token attr-name">DisplayFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:C}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Total)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Total<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130px<span class="token punctuation">"</span></span> <span class="token attr-name">DisplayFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:C}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.OrderDate)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Date<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>140px<span class="token punctuation">"</span></span> <span class="token attr-name">DisplayFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:d}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Status)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Status<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>GridColumns</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikGrid</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>In the previous code we can see the notable difference between the two components:</p><ol><li>It has parameters like <code>Sortable</code>, <code>Groupable</code>, <code>Resizable</code>, <code>Reorderable</code>, etc., which enable capabilities a user expects in a table-like format.</li><li>The number of items shown per page is set to 15, because each row takes up less space.</li><li>If needed, we could customize the columns to display custom content.</li></ol><p>When running the application, we will have a result like the following:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/sales-orders-grid-example.gif?sfvrsn=5d6f72e4_2" alt="Blazor DataGrid view displaying sales orders with columns" /></p><p>In the image above, you can see that we perform operations such as grouping, sorting and filtering rows.</p><h2 id="final-comparison-between-components">Final Comparison Between Components</h2><p>Once we have the application assembled and have seen how each component looks, we can reach the following conclusion:</p><ul><li>Use a ListView when you want to treat each record as an individual entity, customizing its visual hierarchy. It is a component intended to be consumed by users.</li><li>Use a DataGrid when you want a spreadsheet-like experience, with operations such as sorting, grouping, filtering, etc. It is a component intended to be operated by users.</li></ul><p>Each of the views has a different purpose, although it is possible to combine their use to create mixed experiences.</p><h2 id="conclusion">Conclusion</h2><p>Throughout this article we have examined the ListView and Grid components from Progress Telerik UI for Blazor. We have discussed the best scenarios for using each of them, as well as the implementation code to use them.</p><p>We can conclude that you should use a ListView when the information is intended for end users, requires a high degree of customization and offers a unique exploratory experience.</p><p>On the other hand, a DataGrid can be used to display multiple records in a tabular form when operations that enable analysis are needed, such as grouping, sorting, filtering, etc.</p><p>Now I invite you to create spectacular experiences using both components. The whole Telerik UI for Blazor library is available in the free 30-day trial, including the ListView and the DataGrid.</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-blazor" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e60e98a0-0744-48f5-8739-b3a848fd124b</id>
    <title type="text">DataGrids Are For... Financial Analysts</title>
    <summary type="text">As a financial analyst, you need the ability to explore your organization’s data—the DataGrid from Progress Telerik and Kendo UI gives you that ability.</summary>
    <published>2026-08-24T15:39:48Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/datagrids-are-for-financial-analysts"/>
    <content type="text"><![CDATA[<p><span class="featured">As a financial analyst, you need the ability to explore your organization&rsquo;s data&mdash;the DataGrid from Progress Telerik and Kendo UI gives you that ability.</span></p><p>If you are a financial analyst, your responsibility is to &ldquo;understand the numbers&rdquo;&mdash;not just in terms of what &ldquo;the numbers&rdquo; tell you about the past but what those numbers can tell you about the future. That understanding drives your ability both to make informed decisions yourself and to support others in making informed decisions.</p><p>As a financial analyst, you work with a variety of data from multiple sources, some of them external (market information, financial forecasts) and many of them internal (budgets, financial statements and other organization data). Essentially, you turn data into information so that information can drive decisions by becoming business intelligence.</p><p>Each of your organization&rsquo;s applications manages part of your organization&rsquo;s data and, as a result, can provide data that you need for that process. A data exploration tool, embedded in an application&rsquo;s user interface or as a standalone tool, can automatically bring together the information for any area of business. That commitment to a set of data can eliminate the time needed to extract, transform and load the data that a more general-purpose financial analysis tool (e.g., <a target="_blank" href="https://www.cubesoftware.com/">Cube</a>) requires. You just open the application and there&rsquo;s your data, ready for you to work with.</p><p>That&rsquo;s only useful to you, however, if the application&rsquo;s user interface goes beyond simple transaction management&mdash;if the application includes a tool that enables you to work with and explore the data embedded in the application. You need a tool that, among other things, can:</p><ul><li>Work with data from a wide variety of sources</li><li>Handle large amounts of data but remain responsive (you don&rsquo;t want to have to wait on your data being displayed so that you can work on it)</li><li>Format the data to highlight key issues</li><li>Give you the ability to change the data display as you&rsquo;re working with the data to support whatever analysis you need</li><li>Integrate support for Large Language Models to let you leverage AI analytics</li><li>Export the data so that you can use it in other tools</li><li>Support looking at the &ldquo;big picture&rdquo; while also letting you drill down into the essential detail</li></ul><p>And, critically, you need a tool that works &ldquo;the way you expect&rdquo; so you don&rsquo;t have to figure out how to use the tool and can concentrate on the data the grid delivers.</p><p>The Progress Telerik and Kendo UI DataGrid does all those things. (<a href="https://demos.telerik.com/blazor-ui/grid/overview" target="_blank">See the Blazor DataGrid Demo</a> for example.) Some of those features are available in any implementation of the grid (&ldquo;works the way you expect,&rdquo; for instance). Some features will require you to specify to the application&rsquo;s developers how you want the grid to work.</p><p>If, for example, you want the DataGrid to work with a data source that includes lot of rows, you&rsquo;ll want developers to enable the DataGrid&rsquo;s &ldquo;load on demand&rdquo; feature. Similarly, if you&rsquo;re going to be working with data that others will be updating, you&rsquo;ll want your grid to be implemented using observables that let you see those changes as early as possible.</p><h2 id="the-initial-display">The Initial Display</h2><p>Typically, for example, you&rsquo;ll want to have the DataGrid configured so that, as you open the application, the grid retrieves the data that you normally expect to work with. But if you&rsquo;ll need to look at several different data sources as part of analyzing the data, then you&rsquo;ll also want to have the DataGrid configured to allow you to dynamically switch to that other data.</p><p>For that initial display, you can also have your grid act as a dashboard that highlights cells (or whole rows) that signal key issues. If there are specific values that you want highlighted in the grid, you can have those values built in. But, if you want, you can also have the ability to change the values/thresholds used to highlight the data you&rsquo;re interested in.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-01---formatting.png?sfvrsn=33194d2f_2" alt="A Telerik DataGrid with rows and individual cells formatted in a variety of colors. Some columns are highlighted using a bar chart format by levering the cell’s background fill color; Other columns use icons to flag key numbers or changes." /></p><p>You can, of course, sort the grid&rsquo;s rows into the order you need. But you can also, at any time, reorder the grid&rsquo;s columns (or suppress columns) to pull together the data you&rsquo;re interested right now.</p><p>And, also by default, you can filter the grid to show just the rows you need for a particular analysis. You can filter by arbitrary values (e.g., &ldquo;variances greater than 5%&rdquo;) or have a checkbox option for specific columns that makes it easier to select the rows you want. The DataGrid&rsquo;s toolbar also includes a search textbox that you can use to find specific rows.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-02---filter-sort.png?sfvrsn=debc87a1_2" alt="A Telerik DataGrid showing the sort options for a column (ascending and descending) plus the checkbox option for filtering data by value" /></p><h2 id="supporting-multiple-configurations">Supporting Multiple Configurations</h2><p>That&rsquo;s already multiple options and, as a result, a lot of opportunities for you to dynamically reconfigure the DataGrid as you&rsquo;re working with it. Fortunately, you can also have the DataGrid save and restore your configuration settings so that you can quickly switch between different grid configurations as you need them.</p><h2 id="ai-support">AI Support</h2><p>In the modern world, you&rsquo;ll also want to decide how much of the grid&rsquo;s AI support you&rsquo;ll want to have enabled. The DataGrid gives you multiple ways to access AI processors:</p><ul><li>The AI Toolbar for Data Operations lets you use plain language commands to explore your data through filtering, grouping and sorting</li><li>The AI Toolbar for Highlighting lets you flag rows that meet complicated conditions</li><li>The AI Assistant adds a column to your table with a button that opens an AI prompt window to let you ask any question that makes sense</li></ul><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-03---artificial-intelligence.gif?sfvrsn=6086b35_2" alt="An animated GIF of the Telerik DataGrid showing a user opening the grid’s AI Assistant from. The user then selects a “Sort by Salary” quick action to resort the grid" /></p><h2 id="grouping-and-aggregating-data">Grouping and Aggregating Data</h2><p>The DataGrid also lets you organize rows into meaningful groups. You can, as you&rsquo;re viewing the data, select the rows that you want to group or drag/drop columns or rows to create the groups you need. (See <a href="https://www.telerik.com/kendo-react-ui/components/grid/grouping" target="_blank">Grouping options for KendoReact Grid</a> as an example.)</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-04---grouping.png?sfvrsn=416bbcd3_2" alt="An animated GIF of the Telerik DataGrid showing how columns can be dynamically dragged to form groups based on the data in columns" /></p><p>Once you create groups, you can include displays of aggregate values for those groups. But you don&rsquo;t have to create groups to get aggregate values. You can, as you&rsquo;re working with the grid, select the rows and columns you want to be included in your aggregated values.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-05---grouping-aggregrates.gif?sfvrsn=c22705fb_2" alt="An animated GIF of the Telerik DataGrid showing the user selecting rows and a set of aggregate values at the bottom of the grid" /></p><p>While the DataGrid supports the typical aggregate functions &ldquo;out of the box&rdquo; (e.g., count, sum, average, etc.), you can have more sophisticated aggregates integrated into your grids.</p><h2 id="drilling-down">Drilling Down</h2><p>You can also have the DataGrid configured as a hierarchy of high-level sections that you can drill down into to reveal nested, detailed data. You can have your data nested as many levels deep as you need, and each level can have its own, distinct set of columns and aggregates. Effectively, you can expand your high-level rows into a different grid of detail data that reflects your needs at each level.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-06---hierarchy-aggregate.png?sfvrsn=ebd98085_2" alt="The Telerik DataGrid organized as a hierarchy. As the top level of the hierarchy is expanded, it opens a new grid with its own aggregate values" /></p><h2 id="exporting-data">Exporting Data</h2><p>The DataGrid also supports exporting data in a variety of formats so you can integrate other tools into your analysis (or you can just copy grid data so you can paste into some other tool).</p><p>You&rsquo;re not limited to copying or exporting all the data in the grid. While you&rsquo;re working with the grid, you can select the rows and columns you want you want to export into another tool (or just share with others&mdash;the grid supports multiple export formats, including PDF). If your application is using the Telerik Chart control you can export your data straight into the chart for visualization.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-07---export.png?sfvrsn=bb05b579_2" alt="The Telerik DataGrid with two separate sets of rows selected and the export menu opened showing the Copy/Export options: (Copy, Copy with Headers, Export, Export with Headers, and Export to Chart)" /></p><p>And, if you are using the DataGrid to facilitate selecting the data to export into some other tool, the grid supports a &ldquo;high density&rdquo; mode that packs more data on the screen. (See the Kendo UI for <a href="https://www.telerik.com/kendo-angular-ui/components/grid/grouping/modes" target="_blank">Angular Grid Grouping Display Modes</a> page for example.)</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-08---high-density.gif?sfvrsn=496c1720_2" alt="An animated GIF of the Telerik DataGrid control showing the grid shifting from a more readable view to a view with narrower columns and shorter rows to fit more data into less space" /></p><p>As an analyst, you can&rsquo;t really have too much data&mdash;provided, of course, your tools let you review and explore that data effectively. The Telerik DataGrid gives you a customizable window into the data for any application, turning the application itself into a tool for analyzing its own data. That enables you to create the business intelligence that drives better decisions, which is, after all, the purpose of being a financial analyst.</p><hr /><p>Check out the <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> suite of products to learn about the DataGrid in your favorite .NET or JavaScript flavor. And you can try out the whole suite free for 30 days.</p><p><a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank" class="Btn">Try Telerik DevCraft</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:11e0f437-8d92-4dc9-a2b6-a4030526b9ad</id>
    <title type="text">Skeleton in .NET MAUI: What It Is and How to Use It</title>
    <summary type="text">A skeleton allows us to indicate to users that an app or page is loading. This improves the user experience, providing feedback that the app isn’t broken.</summary>
    <published>2026-08-24T15:23:04Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Leomaris Reyes </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/skeleton-net-maui-what-and-how-to-use-it"/>
    <content type="text"><![CDATA[<p><span class="featured">A skeleton allows us to indicate to users that an app or page is loading. This improves the user experience, providing feedback that the app isn&rsquo;t broken.</span></p><p>Let&rsquo;s face it: as users, we tend to be impatient when an application doesn&rsquo;t respond quickly. It&rsquo;s very easy to assume that the app has frozen or isn&rsquo;t working when it doesn&rsquo;t give us any kind of feedback.</p><p>Imagine you have an application with a menu that displays a list connected to a backend. If the backend takes time to respond and we don&rsquo;t indicate to the user that data is loading, they will most likely think: &ldquo;This app doesn&rsquo;t work.&rdquo;</p><p>As developers, it&rsquo;s important to prevent this from happening. There are different ways to visually communicate that data is loading, and one of my favorites is using skeletons. These allow us to gently indicate to the user that the app is performing an action, even if the content isn&rsquo;t ready yet.</p><p>In this article, we&rsquo;ll learn what a Skeleton is, in which scenarios it is most useful, and how to implement the Skeleton component designed by Progress Telerik UI for .NET MAUI.</p><h2 id="what-is-a-skeleton">What Is a Skeleton?</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/00_skeleton_sample.png?sfvrsn=b89cf7f6_2" alt="Skeleton sample" /><br /><span style="font-size:11px;">Image generated using AI for illustrative purposes</span></p><p>A skeleton is a visual element used to indicate that content is loading. Basically, it appears on the screen as blocks (usually gray) placed in the same positions where the actual elements will be displayed once the data is available and the effect disappears.</p><p>Its purpose is to give users context while the information is loading by showing the structure that the content will have. It also typically includes a subtle animation that communicates the app is still working, even if all that&rsquo;s visible are gray blocks or rectangles.</p><p>This way, instead of showing empty spaces, a traditional loader or, even worse, nothing at all, a skeleton keeps the interface clear and lets the user know that the application is working fine.</p><h2 id="✅-when-should-you-use-skeletons">✅ When Should You Use Skeletons?</h2><p>It&rsquo;s important to see skeletons not just as a visual element to &ldquo;entertain&rdquo; users, but as a tool that allows your app to handle loading states in a smoother and more intentional way.</p><p>Let&rsquo;s look at some scenarios where skeletons are especially useful:</p><h3 id="when-waiting-for-api-responses">When Waiting for API Responses</h3><p>No matter how optimized your app is, it still depends on backend response times. Skeletons help bridge that gap by showing structure while data is loading. Ideally, you should display the skeleton only while the request is in progress and replace it immediately once the data is available.</p><h3 id="screens-with-heavy-images">Screens with Heavy Images</h3><p>When loading images that may take time, skeletons are extremely useful to indicate that content is coming, instead of leaving empty or broken-looking spaces.</p><h2 id="❌-when-are-skeletons-not-recommended">❌ When Are Skeletons NOT Recommended?</h2><h3 id="when-data-is-not-actually-loading">When Data Is Not Actually Loading</h3><p>Avoid using skeletons just for visual purposes. If there&rsquo;s no real loading happening, then you don&rsquo;t need them.</p><h3 id="for-instant-loads">For Instant Loads</h3><p>If the content appears immediately, adding a skeleton only adds unnecessary complexity and can even hurt the experience.</p><h3 id="very-simple-screens">Very Simple Screens</h3><p>On screens with minimal content, a skeleton might be overkill. A simple layout or even a simple loading indicator could be enough.</p><h2 id="meet-the-telerik-skeleton-">Meet the Telerik Skeleton </h2><p>Did you know that Progress Telerik provides a <strong><a href="https://www.telerik.com/maui-ui/skeleton">Skeleton component for .NET MAUI</a></strong>? Oh yeahh! </p><p>Now that you&rsquo;ve seen how powerful using one can be, let&rsquo;s quickly go through how to implement it and highlight some of its most important benefits.</p><p>Before we dive in, let&rsquo;s make sure your environment is ready. Just keep these two key points in mind:</p><ul><li><strong>Create a Telerik account</strong>: If you don&rsquo;t have a <a href="https://www.telerik.com/account/">Telerik account</a> yet, you can create one for free. You&rsquo;ll get access to a trial license that allows you to explore not only the Skeleton component, but the full set of UI controls available for .NET MAUI.</li><li><strong>Install Telerik UI for .NET MAUI</strong>: This is a one-time setup, so you won&rsquo;t need to repeat it for each component. If you haven&rsquo;t installed it yet, I&rsquo;ve included an article that walks you through the process step by step for both <a href="https://www.telerik.com/maui-ui/documentation/get-started/first-steps-vs">Visual Studio</a> and <a href="https://www.telerik.com/maui-ui/documentation/get-started/first-steps-vs-code">Visual Studio Code</a>.</li></ul><h3 id="adding-the-namespace">Adding the Namespace</h3><pre><code>xmlns:telerik="http://schemas.telerik.com/2022/xaml/maui"
</code></pre><h3 id="registering-the-component">Registering the Component</h3><p>Go to your <strong>MauiProgram.cs</strong> file and, inside the CreateMauiApp method, register the Telerik controls using the <strong>UseTelerik()</strong> extension method.</p><pre class=" language-csharp"><code class="prism  language-csharp">using Telerik.Maui.Controls.Compatibility;
public static class MauiProgram
{

    public static MauiApp CreateMauiApp()
    {
    var builder = MauiApp.CreateBuilder();
    builder
    .UseTelerik()
    .UseMauiApp&lt;App&gt;()
    .ConfigureFonts(fonts =&gt;
    {
    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
    });
    return builder.Build();
    
    }
}
</code></pre><h3 id="how-to-use-it">How to Use It?</h3><p>Using it is very simple. You just have to add a tag like the following:</p><pre class=" language-xml"><code class="prism  language-xml">&lt;telerik:RadSkeleton x:Name="skeleton"/&gt;
</code></pre><h3 id="skeleton-types-in-the-telerik-ui-for-.net-maui-library">Skeleton Types in the Telerik UI for .NET MAUI Library</h3><p>Typically, when creating a skeleton, you need to build the structure from scratch. However, the Telerik UI for .NET MAUI Skeleton component comes with predefined structures (anatomies), which helps you save both time and code. (Honestly, this is gold. A huge win!)</p><p>This control offers several built-in structures that you can use to represent different types of content:</p><p>➖ <strong>Article:</strong> Displays a layout with a small image placeholder on the left and multiple text lines on the right, simulating a title and description.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/01_article_skeleton_type.png?sfvrsn=62c04b72_2" alt="Article skeleton sample" /></p><p>➖ <strong>Text:</strong> A simple text structure composed of multiple lines, typically used to represent titles and paragraphs.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/02_text_skeleton_type.png?sfvrsn=844034a1_2" alt="Text skeleton sample" /></p><p>➖ <strong>PersonalCircle:</strong> Shows a circular avatar placeholder with text lines next to it, commonly used for user profiles or list items with avatars.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/03_personalcircle_skeleton_type.png?sfvrsn=72d71c48_2" alt="Personal circle skeleton sample" /></p><p>➖ <strong>PersonaSquare:</strong> Similar to PersonaCircle, but uses a square (with rounded corners) instead of a circular avatar.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/04_personasquare_skeleton_type.png?sfvrsn=7d7748ae_2" alt="Personal square skeleton sample" /></p><p>➖ <strong>Image:</strong> Represents a large media placeholder with a single text line below, ideal for image-focused layouts.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/05_image_skeleton_type.png?sfvrsn=2bd148cc_2" alt="Personal square skeleton sample" /></p><p>➖ <strong>Video:</strong> Simulates a video layout with a small avatar and text at the top, followed by a large media area and additional text lines below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/06_video_skeleton_type.png?sfvrsn=dae0aadc_2" alt="Video skeleton sample" /></p><p>➖ <strong>Card:</strong> Displays a card-style layout with a title at the top, a large image placeholder and text lines at the bottom.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/07_card_skeleton_type.png?sfvrsn=5d4d50e4_2" alt="Card skeleton sample" /></p><p>➖ <strong>ContentFeed:</strong> Mimics a feed-style layout with a header (avatar + one line), a large content block and text lines below, similar to social media posts.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/08_contentfeed_skeleton_type.png?sfvrsn=222eb4d7_2" alt="Content feed sample" /><br /><span style="font-size:11px;">The images of the Skeleton types were generated using AI for better understanding</span></p><p>To use any of these types, you just need to use the SkeletonType property as shown below:</p><pre class=" language-xml"><code class="prism  language-xml">&lt;telerik:RadSkeleton SkeletonType="Article" /&gt;
</code></pre><h2 id="-custom-skeleton-view"> Custom Skeleton View</h2><p>But &hellip; what if you want to create your own skeleton anatomy? </p><p>You can do that too!  Although the predefined types help you save time and effort, they don&rsquo;t always fit your design. That&rsquo;s why Progress Telerik allows you to create a fully custom one.</p><p>The idea is very simple: you define your own layout (just like your real UI, but without data) and assign it to the <strong>RadSkeleton</strong> using the LoadingViewTemplate property.</p><h3 id="️-implementation">️ Implementation</h3><p>First, define your RadSkeleton and assign a custom template to it:</p><pre class=" language-xml"><code class="prism  language-xml">    &lt;telerik:RadSkeleton LoadingViewTemplate="{StaticResource CustomSkeletonView}"  
    Grid.Row="1" 
    x:Name="skeleton"&gt; 
    
    &lt;!-- Your real content goes here --&gt;
    
    &lt;/telerik:RadSkeleton&gt;
</code></pre><p>Then, create the DataTemplate that will represent your skeleton:</p><pre class=" language-xml"><code class="prism  language-xml">&lt;DataTemplate x:Key="CustomSkeletonView"&gt;
&lt;Grid RowDefinitions="40, 100, 200"
RowSpacing="10"
Padding="10"&gt;

    &lt;HorizontalStackLayout Spacing="10"&gt;
    &lt;BoxView WidthRequest="40" HeightRequest="40" CornerRadius="20"/&gt;
    &lt;BoxView WidthRequest="40" HeightRequest="40" CornerRadius="20"/&gt;
    &lt;BoxView WidthRequest="40" HeightRequest="40" CornerRadius="20"/&gt;
    &lt;BoxView WidthRequest="40" HeightRequest="40" CornerRadius="20"/&gt;
    &lt;/HorizontalStackLayout&gt;
    &lt;BoxView WidthRequest="200"
    HeightRequest="100"
    HorizontalOptions="Start"
    Grid.Row="1"
    CornerRadius="12"/&gt;
    
    &lt;Grid Grid.Row="2"
    ColumnDefinitions="40, *"
    ColumnSpacing="10"&gt;
    &lt;BoxView HeightRequest="40"
    WidthRequest="40"
    CornerRadius="20"
    Grid.Column="0"/&gt;
    
    &lt;VerticalStackLayout Spacing="10" Grid.Column="1"&gt;
    &lt;BoxView HeightRequest="20" WidthRequest="120" CornerRadius="6"/&gt;
    &lt;BoxView HeightRequest="20" WidthRequest="180" CornerRadius="6"/&gt;
    &lt;/VerticalStackLayout&gt;
    &lt;/Grid&gt;
&lt;/Grid&gt;
&lt;/DataTemplate&gt;
</code></pre><p>Done!!! We made it!  Did you like it?</p><h2 id="conclusion">Conclusion</h2><p>I hope this article helped you understand what a skeleton is and how it can improve the user experience in your .NET MAUI applications. </p><p>From understanding when to use it, to exploring the different types provided by Progress Telerik and finally implementing it in your UI, you&rsquo;re now ready to apply this knowledge in your own apps. I invite you to explore more about the Telerik Skeleton for .NET MAUI in the following articles:</p><ul><li><a href="https://www.telerik.com/maui-ui/skeleton">https://www.telerik.com/maui-ui/skeleton</a></li><li><a href="https://www.telerik.com/maui-ui/documentation/controls/skeleton/overview">https://www.telerik.com/maui-ui/documentation/controls/skeleton/overview</a></li></ul><p>If you have any questions or would like me to dive deeper into a specific part, feel free to leave a comment, I&rsquo;ll be happy to help!</p><p>See you in the next article! &zwj;♀️</p><h3>Ready to Try Telerik UI for .NET MAUI?</h3><p>Give the Skeleton component plus <a target="_blank" href="https://www.telerik.com/maui-ui">70 other .NET MAUI components</a> a try, free for 30 days.</p><p><a href="https://www.telerik.com/try/ui-for-maui" target="_blank" class="Btn">Try Telerik UI for .NET MAUI</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:18a8c83a-19ac-4a74-b424-ebdf4033bd47</id>
    <title type="text">Create a Kendo UI for Angular Dinner Picker</title>
    <summary type="text">Sick of picking where to go for dinner? Build this little Angular app to help you choose!</summary>
    <published>2026-08-20T17:09:35Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/create-kendo-ui-angular-dinner-picker"/>
    <content type="text"><![CDATA[<p><span class="featured">Sick of picking where to go for dinner? Build this little Angular app to help you choose!</span></p><p>Do you ever argue about where to eat? If you&rsquo;re like me, you can&rsquo;t think of places half the time, and you just wish someone would pick for you. Well, here you go!</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/chrome_v90f4i218o.gif?sfvrsn=5c1916fc_2" height="717" style="max-width:100%;height:auto;" title="Angular Restaurant Picker" width="501" alt="Angular Restaurant Picker with options for dine-in or take-out" sf-size="382850" /></p><h2 id="tldr">TL;DR</h2><p>This app will create a random dinner picker from JSON data in Angular using Progress Kendo UI. You can choose from Takeout or Dine-in.</p><h2 id="setup">Setup</h2><p>Create a new <a target="_blank" href="https://angular.dev/">Angular</a>&nbsp;app:</p><pre class=" language-html"><code class="prism  language-html">ng new angular-dinner-picker
</code></pre><h3 id="get-a-kendo-ui-license">Get a Kendo UI License</h3><p>Log in to Progress and purchase a Kendo UI for Angular license, or try it for free.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/image.png?sfvrsn=bece1600_2" height="840" style="max-width:100%;height:auto;" title="Kendo UI License Key" width="1195" alt="Kendo UI License Key" sf-size="107716" /></p><p>Download the actual key, as you will need it later for deployment.</p><h3 id="install-kendo-ui-licensing">Install Kendo UI Licensing</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i -S @progress/kendo-licensing
</code></pre><p>Then run:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>This will set up Kendo UI on your local machine for this project. Make sure you have a working license at this point, and to <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/licensing">follow the correct setup</a>.</p><h3 id="install-packages">Install Packages</h3><pre class=" language-bash"><code class="prism  language-bash">ng add @progress/kendo-angular-buttons
ng add @progress/kendo-angular-dropdowns
ng add @progress/kendo-angular-layout
</code></pre><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i -S <span class="token function">npm</span> <span class="token function">install</span> @angular/forms @progress/kendo-svg-icons @progress/kendo-theme-default
</code></pre><p> You may need to also install <code>@angular/localize</code>.</p><h3 id="install-tailwind">Install Tailwind</h3><p>Make sure Angular is configured for Tailwind. Follow the <a target="_blank" href="https://tailwindcss.com/docs/installation/framework-guides/angular">Tailwind Guide</a>.</p><h3 id="configure-styles">Configure Styles</h3><p>Make sure your styles are shown in <code>styles.css</code> correctly.</p><pre class=" language-bash"><code class="prism  language-bash">@import <span class="token string">'@progress/kendo-theme-default/dist/default-main.css'</span><span class="token punctuation">;</span>
@import <span class="token string">'tailwindcss'</span><span class="token punctuation">;</span>
</code></pre><h2 id="picker-component">Picker Component</h2><p>Generate a new picker component.</p><pre class=" language-bash"><code class="prism  language-bash">ng g c picker
</code></pre><h3 id="model">Model</h3><p>Create a model at <code>picker.model.ts</code>.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">export</span> <span class="token function">type</span> RestaurantMode <span class="token operator">=</span> <span class="token string">'dine-in'</span> <span class="token operator">|</span> <span class="token string">'takeout'</span><span class="token punctuation">;</span>

<span class="token function">export</span> <span class="token function">type</span> Restaurant <span class="token operator">=</span> <span class="token punctuation">{</span>
  id: number<span class="token punctuation">;</span>
  name: string<span class="token punctuation">;</span>
  cuisine: string<span class="token punctuation">;</span>
  mode: RestaurantMode<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">export</span> <span class="token function">type</span> RestaurantModeFilter <span class="token operator">=</span> <span class="token string">'all'</span> <span class="token operator">|</span> RestaurantMode<span class="token punctuation">;</span>
</code></pre><h3 id="data">Data</h3><p>You can declare your data in the <code>restaurants.ts</code>. You could obviously get more sophisticated and import data from a database if you like.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> Restaurant <span class="token punctuation">}</span> from <span class="token string">"./picker.model"</span><span class="token punctuation">;</span>

<span class="token function">export</span> const restaurants: Restaurant<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 1,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Monjunis"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"Italian"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 2,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Strawn's Eat Shop"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"Southern Diner"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 3,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Country Tavern"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"BBQ"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  
  <span class="token punctuation">..</span>.
  
<span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><p> I used my city&rsquo;s info so I can actually use the app! You can customize this and deploy it separately for different situations or cities!</p><h3 id="picker-class">Picker Class</h3><p>Create the picker classes at <code>picker.ts</code>.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> ChangeDetectionStrategy, Component <span class="token punctuation">}</span> from <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> FormsModule <span class="token punctuation">}</span> from <span class="token string">'@angular/forms'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> ButtonsModule <span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-angular-buttons'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> DropDownsModule <span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-angular-dropdowns'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> restaurants <span class="token punctuation">}</span> from <span class="token string">'./restaurants'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> Restaurant, RestaurantModeFilter <span class="token punctuation">}</span> from <span class="token string">'./picker.model'</span><span class="token punctuation">;</span>

<span class="token function">type</span> ModeOption <span class="token operator">=</span> <span class="token punctuation">{</span>
  label: string<span class="token punctuation">;</span>
  value: RestaurantModeFilter<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

@Component<span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector: <span class="token string">'app-picker'</span>,
  standalone: true,
  imports: <span class="token punctuation">[</span>
    FormsModule,
    ButtonsModule,
    DropDownsModule
  <span class="token punctuation">]</span>,
  templateUrl: <span class="token string">'./picker.html'</span>,
  changeDetection: ChangeDetectionStrategy.OnPush
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token function">export</span> class Picker <span class="token punctuation">{</span>
  <span class="token function">readonly</span> restaurants <span class="token operator">=</span> restaurants<span class="token punctuation">;</span>

  <span class="token function">readonly</span> modeOptions: ModeOption<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      label: <span class="token string">'Any'</span>,
      value: <span class="token string">'all'</span>
    <span class="token punctuation">}</span>,
    <span class="token punctuation">{</span>
      label: <span class="token string">'Dine-in'</span>,
      value: <span class="token string">'dine-in'</span>
    <span class="token punctuation">}</span>,
    <span class="token punctuation">{</span>
      label: <span class="token string">'Takeout'</span>,
      value: <span class="token string">'takeout'</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  selectedMode: RestaurantModeFilter <span class="token operator">=</span> <span class="token string">'all'</span><span class="token punctuation">;</span>
  selectedRestaurant: Restaurant <span class="token operator">|</span> null <span class="token operator">=</span> null<span class="token punctuation">;</span>

  get filteredRestaurants<span class="token punctuation">(</span><span class="token punctuation">)</span>: Restaurant<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>this.selectedMode <span class="token operator">==</span><span class="token operator">=</span> <span class="token string">'all'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> this.restaurants<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> this.restaurants.filter<span class="token punctuation">((</span>restaurant<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> restaurant.mode <span class="token operator">==</span><span class="token operator">=</span> this.selectedMode<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  pickRestaurant<span class="token punctuation">(</span><span class="token punctuation">)</span>: void <span class="token punctuation">{</span>
    const choices <span class="token operator">=</span> this.filteredRestaurants<span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>choices.length <span class="token operator">==</span><span class="token operator">=</span> 0<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      this.selectedRestaurant <span class="token operator">=</span> null<span class="token punctuation">;</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    const pickableChoices <span class="token operator">=</span>
      this.selectedRestaurant <span class="token operator">&amp;&amp;</span> choices.length <span class="token operator">&gt;</span> 1
        ? choices.filter<span class="token punctuation">((</span>restaurant<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> restaurant.id <span class="token operator">!=</span><span class="token operator">=</span> this.selectedRestaurant?.id<span class="token punctuation">)</span>
        <span class="token keyword">:</span> choices<span class="token punctuation">;</span>

    const index <span class="token operator">=</span> Math.floor<span class="token punctuation">(</span>Math.random<span class="token punctuation">(</span><span class="token punctuation">)</span> * pickableChoices.length<span class="token punctuation">)</span><span class="token punctuation">;</span>

    this.selectedRestaurant <span class="token operator">=</span> pickableChoices<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><ul><li>Pick the restaurant with <code>pickResaturant()</code> function choosing a random JSON entry from the filtered results.</li><li>We have <code>any</code>, <code>dine-in</code> and <code>takeout</code>.</li><li>We will use the filtered results in the html template from <code>filteredResautrants()</code>.</li></ul><h3 id="picker-template">Picker Template</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token operator">&lt;</span>main
  class<span class="token operator">=</span><span class="token string">"grid min-h-screen place-items-center bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.95),rgba(255,255,255,0)_28%),linear-gradient(180deg,#f7efe7_0%,#eef2f7_100%)] p-4 sm:p-6"</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>section
    class<span class="token operator">=</span><span class="token string">"w-full max-w-sm rounded-4xl border border-slate-300/40 bg-white/92 px-6 py-7 shadow-[0_24px_60px_rgba(15,23,42,0.12)] backdrop-blur-[14px] sm:px-8 sm:py-8"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>header class<span class="token operator">=</span><span class="token string">"space-y-3 text-center"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>h1 class<span class="token operator">=</span><span class="token string">"m-0 text-[2.05rem] font-semibold leading-[0.98] tracking-tight text-slate-800 sm:text-[2.3rem]"</span><span class="token operator">&gt;</span>
        Restaurant Picker
      <span class="token operator">&lt;</span>/h1<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>p class<span class="token operator">=</span><span class="token string">"mx-auto max-w-64 text-[1rem] leading-7 text-slate-500"</span><span class="token operator">&gt;</span>
        Pick where to eat without thinking about it.
      <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/header<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"my-6 h-px bg-slate-200/80"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"space-y-6"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"space-y-3 text-center"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>label for<span class="token operator">=</span><span class="token string">"mode"</span> class<span class="token operator">=</span><span class="token string">"block text-[1.15rem] font-semibold leading-tight text-slate-800"</span><span class="token operator">&gt;</span>
          What kind?
        <span class="token operator">&lt;</span>/label<span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>kendo-dropdownlist id<span class="token operator">=</span><span class="token string">"mode"</span> class<span class="token operator">=</span><span class="token string">"w-full text-left"</span> <span class="token punctuation">[</span>data<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"modeOptions"</span> textField<span class="token operator">=</span><span class="token string">"label"</span> valueField<span class="token operator">=</span><span class="token string">"value"</span>
          <span class="token punctuation">[</span>valuePrimitive<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"true"</span> <span class="token punctuation">[</span><span class="token punctuation">(</span>ngModel<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"selectedMode"</span> /<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>button kendoButton class<span class="token operator">=</span><span class="token string">"min-h-14 w-full text-base font-semibold"</span> themeColor<span class="token operator">=</span><span class="token string">"primary"</span> size<span class="token operator">=</span><span class="token string">"large"</span>
        rounded<span class="token operator">=</span><span class="token string">"large"</span> <span class="token punctuation">[</span>disabled<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"filteredRestaurants.length === 0"</span> <span class="token punctuation">(</span>click<span class="token punctuation">)</span><span class="token operator">=</span><span class="token string">"pickRestaurant()"</span><span class="token operator">&gt;</span>
        Pick Restaurant
      <span class="token operator">&lt;</span>/button<span class="token operator">&gt;</span>

      @if <span class="token punctuation">(</span>selectedRestaurant<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token operator">&lt;</span>section
        class<span class="token operator">=</span><span class="token string">"rounded-[1.75rem] border border-rose-200/80 bg-[linear-gradient(135deg,rgba(255,241,242,0.92),rgba(255,255,255,0.98))] px-6 py-6 shadow-[inset_0_1px_0_rgba(255,255,255,0.7)]"</span>
        aria-live<span class="token operator">=</span><span class="token string">"polite"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"grid justify-items-center gap-4 text-center"</span><span class="token operator">&gt;</span>
          <span class="token operator">&lt;</span>p class<span class="token operator">=</span><span class="token string">"m-0 text-[0.78rem] font-bold uppercase tracking-[0.2em] text-rose-700"</span><span class="token operator">&gt;</span>Tonight<span class="token string">'s pick&lt;/p&gt;

          &lt;h2
            class="m-0 max-w-[11ch] text-[clamp(1.7rem,4vw,2rem)] font-semibold leading-[1.08] tracking-tight text-slate-900 text-balance"&gt;
            {{ selectedRestaurant.name }}
          &lt;/h2&gt;

          &lt;div class="grid justify-items-center gap-3"&gt;
            &lt;p class="m-0 text-[1rem] leading-6 text-slate-700"&gt;
              {{ selectedRestaurant.cuisine }}
            &lt;/p&gt;

            &lt;span
              class="inline-flex min-h-10 items-center justify-center rounded-full border border-slate-300/40 bg-white/90 px-5 text-sm font-bold text-slate-900 shadow-[0_6px_18px_rgba(15,23,42,0.06)]"&gt;
              {{ selectedRestaurant.mode === '</span>dine-in<span class="token string">' ? '</span>Dine-in<span class="token string">' : '</span>Takeout<span class="token string">' }}
            &lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/section&gt;
      } @else {
      &lt;div
        class="rounded-[1.75rem] border border-dashed border-slate-400/60 bg-slate-50/75 px-5 py-6 text-center text-[0.95rem] leading-6 text-slate-500"&gt;
        @if (filteredRestaurants.length === 0) {
        No restaurants found for this option.
        } @else {
        Click the button to pick a restaurant.
        }
      &lt;/div&gt;
      }

      &lt;p class="text-center text-[0.82rem] font-semibold text-slate-500"&gt;
        {{ filteredRestaurants.length }}
        restaurant{{ filteredRestaurants.length === 1 ? '</span><span class="token string">' : '</span>s' <span class="token punctuation">}</span><span class="token punctuation">}</span> available
      <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/section<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>/main<span class="token operator">&gt;</span>
</code></pre><p>Here&rsquo;s what&rsquo;s going on above:</p><ul><li>When we run <code>pickRestaurant()</code>, the app displays the filtered items as a signal.</li><li>The <code>kendo-dropdownlist</code> component uses <code>data</code> field for options with <code>label</code> and <code>value</code> matching the drop down.</li><li>We just add <code>kendoButton</code> to our <code>button</code> component to get the look we want. We can use Tailwind with the look!</li></ul><p>And it&rsquo;s that simple!</p><p><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/angular-dinner-picker">GitHub</a><br /><strong>Demo:</strong> <a target="_blank" href="https://angular-dinner-picker.vercel.app/">Vercel Serverless</a></p><p>You can try all of this yourself with the Kendo UI for Angular trial, free for 30 days.</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" target="_blank" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:a5f0d0a2-cf99-48c9-99af-0fcb347a85c8</id>
    <title type="text">AI UX Patterns for User Transparency</title>
    <summary type="text">Most users won’t integrate what they can’t see and understand. If the AI applications we build are going to become part of their daily lives, users need transparency.</summary>
    <published>2026-08-20T16:31:50Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/ai-ux-patterns-user-transparency"/>
    <content type="text"><![CDATA[<p><span class="featured">Many users won&rsquo;t allow AI to integrate into their workflow if they don&rsquo;t know what that access really means. To get user buy-in, we need to provide transparency.</span></p><p>AI tools are strongest when they integrate into the rest of a user&rsquo;s workflow&mdash;referencing internal documents, sending emails, making calendar appointments, drafting and sharing content with coworkers, so on and so forth. </p><p>However, most users won&rsquo;t integrate what they can&rsquo;t see and understand. If the AI applications we build are going to become part of their daily lives, users need transparency into exactly what they do, what data they have access to and what it will cost them (in both time and money). </p><p>The more transparent we can make these features, the less hesitation our users will have adopting them. </p><h2 id="ux-pattern-permissions">UX Pattern: Permissions</h2><p>The concept of asking for user permissions is certainly not AI-specific, but the stakes can feel a lot higher when you&rsquo;re asking users for permission to allow AI to act on their behalf. </p><p>We need to make it easy for users to see which applications we&rsquo;ve allowed our tool to access and in what ways, keeping in mind that permissions are often more than just &ldquo;yes&rdquo; or &ldquo;no.&rdquo; </p><p>Can your agent just reference data from this database, or is it allowed to delete tables? Can it only read a user&rsquo;s emails, or is it allowed to send from their address as well? Can it run scripts? Search local files? Each one of these actions requires direct user sign-off. </p><p>Permissions are also not a one-and-done situation. A user might feel comfortable allowing an action to happen once under their direct supervision, but don&rsquo;t want to allow it permanently. They might give access to a specific folder, but not every folder. They might approve an action, but want to be notified each time it&rsquo;s taken. Consider what gray zones might exist in your permission structure, and try to accommodate as many options as you can. </p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-28-41-am.png?sfvrsn=997a5784_2" title="Screenshot 2026-07-23 at 10.28.41 AM" width="450" alt="Extensive permission options in VS Code Copilot " /></p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-28-46-am.png?sfvrsn=6db9970a_2" title="Screenshot 2026-07-23 at 10.28.46 AM" width="450" alt="Auto-approval permissions in VS Code Copilot " /></p><p>Another common sticking point with AI is data collection. Often, it can be helpful to save information from past interactions, but storing this kind of data requires real attention to user permission and data management. </p><p>If you want to create a system that can &ldquo;remember&rdquo; things, you also need to make sure it can &ldquo;forget&rdquo; as well&mdash;and that the user can not only see but has final say in what exactly what gets remembered. An ideal permissions flow will not only ask for a user&rsquo;s approval in the moment, but also create a space where they can see the history of what they&rsquo;ve given access to and when&mdash;and allow them to revoke that access or permission at any time.<span style="background-color:transparent;color:inherit;font-family:inherit;font-size:inherit;text-align:inherit;text-transform:inherit;word-spacing:normal;caret-color:auto;white-space:inherit;"></span></p><h2 id="ux-pattern-estimates-and-confirmation">UX Pattern: Estimates and Confirmation</h2><p>This one is pretty simple, so we won&rsquo;t spend too much time on it, but another crucial aspect of transparency is making sure users know exactly what they&rsquo;re committing to when they approve an action request. In addition to approving the actual steps and plan (as we discussed earlier in the <a href="https://www.telerik.com/blogs/ai-ux-patterns-user-trust" target="_blank">Trust post</a>), they also need to know how much time it&rsquo;s going to take and what it will cost them in money or tokens. </p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-28-55-am.png?sfvrsn=cdede2a2_2" title="Screenshot 2026-07-23 at 10.28.55 AM" width="550" alt="Figma&#39;s AI Balance token tracking meter" /></p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-07-23-at-10-29-02-am.png?sfvrsn=e3d1bac5_2" title="Screenshot 2026-07-23 at 10.29.02 AM" width="300" alt="Model and token usage reports in VS Code Copilot " sf-size="13947" /></p><p>Even if we can&rsquo;t specify an exact amount, providing rough estimates generally gives users enough information to work with&mdash;and to potentially revise their request if it&rsquo;s going to exceed what they&rsquo;re comfortable with. </p><h2 id="ux-pattern-solo-execution-indicator">UX Pattern: Solo Execution Indicator</h2><p>Similarly to the marking that denotes which content was AI-generated, it&rsquo;s also a good idea to have some kind of visual signal for when an AI tool is acting independently. If, for example, you&rsquo;re going to allow an agent to take control of the user&rsquo;s browser, then there needs to be a banner, sidebar, outline or some other kind of indication that the user is no longer driving the interaction. </p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/jul-23-2026-10-29-18.gif?sfvrsn=ff53fc19_2" title="Jul-23-2026 10-29-18" alt="The glowing orange border denotes agent control of the browser in Claude&#39;s browser extension " /></p><p>Not only is this a good thing to do just for transparency&mdash;so the user understands the current state of the system&mdash;but also so they don&rsquo;t unintentionally interrupt or confuse an ongoing process. It&rsquo;s an especially important consideration for processes that you know will take an extended period of time, where the user may step away and come back without keeping track of everything that&rsquo;s happened while they were gone.</p><p><strong>Read next:</strong> <a target="_blank" href="https://www.telerik.com/blogs/ai-ux-patterns-show-meaningful-user-benefit">AI UX Patterns to Show Meaningful User Benefit</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:4696baea-52cd-4e5f-b14b-8051d7ccbea9</id>
    <title type="text">Build, Automate and Observe: What You Can Do with Telerik, Kendo UI and AI Engineering This August</title>
    <summary type="text">See what you can do with this August’s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and AI Observability.</summary>
    <published>2026-08-19T17:28:37Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Iva Borisova </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/build-automate-observe-august"/>
    <content type="text"><![CDATA[<p><span class="featured">See what you can do with this August&rsquo;s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and AI Observability.</span></p><p>AI stopped being &ldquo;the thing that writes your code&rdquo; a while ago. At this point, it&rsquo;s showing up at nearly every stage of the job: helping you build the app, automate the parts nobody wants to do by hand and keep an eye on how AI-powered features actually behave once real users touch them.</p><p>This month&rsquo;s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and Progress AI Observability are a good snapshot of that shift. Here&rsquo;s what&rsquo;s new and, more importantly, what it actually lets you do.</p><p>But before that &hellip; We&rsquo;re walking through everything below on <strong>Progress AI Monthly: Telerik, Kendo UI &amp; More livestream on August 28, 9 a.m. ET.</strong> No sign-up, just show up.</p><p><a target="_blank" href="https://www.youtube.com/watch?v=yAdEAoDZTAc">YouTube</a> &middot; <a target="_blank" href="https://www.linkedin.com/events/7495480741330386945/">LinkedIn</a> &middot; <a target="_blank" href="https://www.twitch.tv/codeitlive">Twitch</a></p><h2 id="build">Build</h2><h3 id="more-ai-fewer-tokens">More AI, Fewer Tokens</h3><p>Before getting to what&rsquo;s new, a word on what&rsquo;s gotten leaner: token consumption is down by up to 45% across our .NET products, with better output quality on top of that, so the same prompt now costs you less and gets you more. Alongside that, the Agentic UI Generator for Telerik UI for Blazor, KendoReact and Kendo UI for Angular is now available as an agent-native skill instead of an externally configured tool, which makes it noticeably easier to deploy in air-gapped and regulated environments.</p><p><a target="_blank" href="https://www.telerik.com/mcp-servers">See the MCP servers page.</a></p><h3 id="build-ui-with-a-prompt">Build UI with a Prompt</h3><p>The Agentic UI Generator now supports Telerik UI for ASP.NET Core. Describe what you need in plain language, and it generates real, framework-aware code that follows the patterns you&rsquo;d already be using: grids, forms, navigation, layout, the works. That means less time assembling scaffolding and more time on the parts of the app that actually need your judgment.</p><p>A few things people are already using it for: business applications, admin portals, customer-facing dashboards, analytics views and data-driven workflows.</p><p><a target="_blank" href="https://www.telerik.com/aspnet-core-ui/documentation/ai-core/agentic-ui-generator/overview">See how the ASP.NET Core UI Generator works.</a></p><h3 id="build-reports-with-natural-language">Build Reports with Natural Language</h3><p>The Telerik Reporting AI Report Generator has already been generating charts and gauges from a plain description of what you want to see. Now that same idea extends to structured report content: tables, lists, crosstabs, summary rows, headers and footers, and grouped structures.</p><p>Describe the table you need. For instance, grouped by region, with subtotals. And the Generator builds it, then hands you a preview to check before anything actually lands in the report.</p><p><a target="_blank" href="https://demos.telerik.com/reporting/ai-report-generator?mode=ai-table-generator">Explore AI-Generated Tables, Lists and Crosstabs.</a></p><h3 id="build-faster-with-new-components">Build Faster with New Components</h3><p>Not every update this month is AI-flavored. Some of it is just useful, new UI:</p><ul><li>Telerik UI for Blazor: <a target="_blank" href="https://demos.telerik.com/blazor-ui/expansionpanel/overview">Expansion Panel</a></li><li>KendoReact: <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/inputs/otpinput">OTP Input</a></li><li>Kendo UI for Vue: <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/barcodes">Barcode</a>, <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/pdfviewer">PDF Viewer</a></li><li>Telerik UI for .NET MAUI: <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/charts/overview">Charts (Preview)</a>, <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/circularslider/overview">Circular Slider</a></li><li>Telerik UI for WPF: <a target="_blank" href="https://www.telerik.com/products/wpf/documentation/controls/radbuttons/features/smart-paste-button">Smart Paste Button</a>, <a target="_blank" href="https://www.telerik.com/products/wpf/documentation/controls/radinlineaiassistant/overview">Inline AI Assistant</a></li><li>Telerik UI for WinForms: <a target="_blank" href="https://www.telerik.com/products/winforms/documentation/controls/smartpastebutton/overview">Smart Paste Button</a></li></ul><h3 id="build-ai-ready-experiences-with-the-llm-kit">Build AI-ready Experiences with the LLM Kit</h3><p>The LLM Kit isn&rsquo;t just something you bolt on after the fact to keep tabs on your agents. It&rsquo;s a set of components for building the AI-powered experience itself. Chat-style interfaces, multi-step agent workflows, tool-call displays, approval steps, citations, checkpoints: the pieces you&rsquo;d otherwise have to design and build from scratch are already there, ready to wire into your app.</p><p>It gives you a real head start on shipping AI features that feel considered rather than bolted on, and, as a side benefit, your users and your team can actually see what the AI is doing along the way.</p><p>It&rsquo;s available now for <a target="_blank" href="https://demos.telerik.com/blazor-ui/llmkit/overview">Telerik UI for Blazor</a>, <a target="_blank" href="https://demos.telerik.com/aspnet-core/llm-kit">UI for ASP.NET Core</a>, <a target="_blank" href="https://demos.telerik.com/aspnet-mvc/llm-kit">UI for MVC</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/conversational-ui/llm-kit">Kendo UI for Angular</a>, <a target="_blank" href="https://demos.telerik.com/kendo-ui/llm-kit/index">UI for jQuery</a> and <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/conversationalui/llm-kit">KendoReact</a>.</p><h2 id="automate">Automate</h2><h3 id="automate-application-upgrades">Automate Application Upgrades</h3><p>Nobody enjoys upgrading a codebase, and it&rsquo;s rarely the highest-value use of anyone&rsquo;s afternoon. AI-assisted migration is now available for Telerik UI for ASP.NET Core, UI for MVC, UI for WPF, KendoReact and Kendo UI for jQuery through the Upgrade Assistant (part of the MCP engine). It handles the changes it already knows how to make on its own and walks you through the ones that need a human decision, so version bumps stop eating whole sprints.</p><h3 id="automate-reporting-operations-with-ai-agents">Automate Reporting Operations with AI Agents</h3><p>Telerik Report Server now ships with a native MCP Server, so assistants like Copilot, Claude, Cursor and Codex can work directly inside your reporting environment instead of going through a custom integration you&rsquo;d have to build and maintain. Once you&rsquo;ve set up a Personal Access Token, agents can:</p><ul><li>Generate reports</li><li>Manage data sources</li><li>Schedule reports</li><li>Handle general reporting-environment admin</li></ul><p><a target="_blank" href="https://www.telerik.com/report-server/documentation/dotnet-docs/mcp-server">See Report Server MCP docs.</a></p><h3 id="automate-debugging-with-fiddler-mcp">Automate Debugging with Fiddler MCP</h3><p>Debugging gets a lot more useful when your AI assistant can see what actually happened over the wire, not just what the code says should happen. Fiddler MCP connects your coding assistant to real traffic captured in Fiddler Everywhere, actual requests, responses, headers, status codes, so it&rsquo;s reasoning from evidence instead of guessing. Set it up with one click inside Fiddler Everywhere, then go from &ldquo;something&rsquo;s broken&rdquo; to a fix without leaving your IDE.</p><p><a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere/mcp">Explore Fiddler Everywhere MCP.</a></p><h3 id="automate-licensing-for-cicd">Automate Licensing for CI/CD</h3><p>Automation isn&rsquo;t only about what your agents do. It&rsquo;s also about what shouldn&rsquo;t need a human to manage. Deployment Keys give you a dedicated way to license automated builds and deployments: each key ties to a registered application and the exact Telerik or Kendo products it uses, so tracking, monitoring and reporting on product usage across your CI/CD environments gets a lot less manual.</p><h2 id="observe">Observe</h2><p>Building faster and automating more only gets you so far if you can&rsquo;t tell what your AI systems are actually doing once they&rsquo;re live. That&rsquo;s the part teams tend to underinvest in until something goes wrong.</p><h3 id="learn-why-ai-observability-matters">Learn Why AI Observability Matters</h3><p>If you want to go deeper on this, join Jeff Fritz and Progress Product Manager Lyubomir Atanasov on <strong>August 26</strong> for <strong>&ldquo;AI Observability: When AI Goes Off the Rails.&rdquo;</strong> They&rsquo;ll walk through some genuinely memorable public AI failures, then run a live Progress AI Observability demo covering agent tracing, drift and hallucination detection, and performance and cost monitoring.</p><p><a target="_blank" href="https://www.telerik.com/webinars/progress-telerik/when-ai-goes-off-the-rails">Register for the webinar.</a></p><h3 id="help-shape-the-future-of-agentic-development">Help Shape the Future of Agentic Development</h3><p>This is also the thinking behind the Progress Agent Harness Early Access Program: structured, auditable AI workflows, with early adopters getting a direct line to the roadmap. As agents take on more, being able to manage them predictably matters just as much as what they can build.</p><p><a target="_blank" href="https://www.telerik.com/agent-harness-early-access">Join the Agent Harness EAP.</a></p><h2 id="what’s-new-and-release-history">What&rsquo;s New and Release History</h2><p>To see everything that&rsquo; is new in August 2026 release edition, visit the <a target="_blank" href="https://www.telerik.com/support/whats-new">What&rsquo;s New in Telerik and Kendo UI page</a>. For a deeper dive into each product, follow the links below.</p><table><style>table, th, td {
  border: 1px;
  border-color: #bdbdba;
  border-style: dotted;
  border-collapse: collapse;
  margin-right: auto;
  padding:0in 5.4pt 0in 5.4pt;
  text-align :left;
}
</style>
<thead><tr><th><strong>Product</strong></th><th><strong>What&rsquo;s New</strong></th><th><strong>Release History</strong></th></tr></thead><tbody><tr><td>Kendo UI for Angular</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-angular-ui/2026-q3">What&rsquo;s New in Kendo UI for Angular</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/changelogs/kendo-angular-ui#v25.1.0-develop.4">Kendo UI for Angular Release History</a></td></tr><tr><td>KendoReact</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-react-ui/2026-q3">What&rsquo;s New in KendoReact</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/changelogs/ui-for-react#v16.1.0-develop.2">KendoReact Release History</a></td></tr><tr><td>Kendo UI for Vue</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-vue-ui/2026-q3">What&rsquo;s New in Kendo UI for Vue</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/changelogs/ui-for-vue#v9.2.0">Kendo UI for Vue Release History</a></td></tr><tr><td>Kendo UI for jQuery</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-jquery-ui/2026-q3">What&rsquo;s New in Kendo UI for jQuery</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-ui/release-history/kendo-ui-for-jquery-2026-3-811-(2026-q3)">Kendo UI for jQuery Release History</a></td></tr><tr><td>Telerik UI for Blazor</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/blazor-ui/2026-q3">What&rsquo;s New in Telerik UI for Blazor</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/blazor-ui/release-history/telerik-ui-for-blazor-15-0-0-(2026-q3)">Telerik UI for Blazor Release History</a></td></tr><tr><td>Telerik UI for ASP.NET Core</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-core-ui/2026-q3">What&rsquo;s New in Telerik UI for ASP.NET Core</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-core-ui/release-history/telerik-ui-for-asp-net-core-2026-3-811-(2026-q3)">Telerik UI for ASP.NET Core Release History</a></td></tr><tr><td>Telerik UI for ASP.NET MVC</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-mvc/2026-q3">What&rsquo;s New in Telerik UI for ASP.NET MVC</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-mvc/release-history/telerik-ui-for-asp-net-mvc-2026-3-811-(2026-q3)">Telerik UI for ASP.NET MVC Release History</a></td></tr><tr><td>Telerik UI for .NET MAUI</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/maui-ui/2026-q3">What&rsquo;s New in Telerik UI for .NET MAUI</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/maui-ui/release-history/telerik-ui-for-net-maui-15-0-0-(2026-q3)">Telerik UI for .NET MAUI Release History</a></td></tr><tr><td>Telerik UI for WPF</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/wpf/2026-q3">What&rsquo;s New in Telerik UI for WPF</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/wpf/release-history/telerik-ui-for-wpf-2026-3-812-(2026-q3)">Telerik UI for WPF Release History</a></td></tr><tr><td>Telerik UI for WinForms</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/winforms/2026-q3">What&rsquo;s New in Telerik UI for WinForms</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/winforms/release-history/telerik-ui-for-winforms-2026-3-812-(2026-q3)">Telerik UI for WinForms Release History</a></td></tr><tr><td>Telerik Reporting</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/reporting/2026-q3">What&rsquo;s New in Telerik Reporting</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/reporting/release-history/progress-telerik-reporting-2026-q3-(20-2-26-812)">Telerik Reporting Release History</a></td></tr><tr><td>Telerik Report Server</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/report-server/2026-q3">What&rsquo;s New in Telerik Report Server</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/report-server/release-history/progress-telerik-report-server-2026-q3-(12-2-26-812)">Telerik Report Server Release History</a></td></tr><tr><td>Telerik Document Processing</td><td><a target="_blank" href="https://www.telerik.com/document-processing-libraries">What&rsquo;s New in Telerik DPL</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/telerik-document-processing/release-history/progress-telerik-document-processing-2026-3-805">Telerik DPL Release History</a></td></tr></tbody></table>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:5c0764ec-205b-4ef5-8915-8f0094edc496</id>
    <title type="text">What a Useful AI Trace Should Actually Contain (And How to Build One)</title>
    <summary type="text">Learn how to produce the six trace elements using the Progress AI Observability Platform with the architecture choices and tradeoffs you’d want in production.</summary>
    <published>2026-08-19T16:22:27Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Nikolay Iliev </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/what-useful-ai-trace-should-actually-contain-how-to-build"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to produce the six trace elements using the Progress AI Observability Platform with the architecture choices and tradeoffs you&rsquo;d want in production.</span></p><h2 id="tldr">TL;DR</h2><p>If your AI trace doesn&rsquo;t explain decisions, it&rsquo;s just noise. A span that says your LLM call succeeded in 1.3 seconds confirms the infrastructure worked, but it says nothing about what the model received, what it returned, what tools it called, what that cost or whether the output was correct. Useful AI traces contain the prompt sent, the completion returned, every tool invocation with its inputs and outputs, token counts, estimated cost and a quality signal.</p><p>If you&rsquo;re new to the topic, start with <a target="_blank" href="https://www.telerik.com/blogs/six-questions-every-ai-trace-should-answer">The Six Questions Every AI Trace Should Be Able to Answer</a> for the conceptual foundation. This post picks up where that one leaves off.</p><p>This post is an implementation walk-through: it shows how to produce those six trace elements using the <a target="_blank" href="https://www.telerik.com/ai-observability-platform">Progress AI Observability Platform</a> Python SDK (<code>progress-observability</code>), with the architecture choices and tradeoffs you&rsquo;d want in production. The platform also supports .NET and JavaScript/TypeScript&mdash;see <a target="_blank" href="https://www.telerik.com/ai-observability-platform">telerik.com/ai-observability-platform</a>&mdash;free to start, 5-minute setup.</p><h2 id="problem-statement">Problem Statement</h2><p>Picture this: your dashboard shows HTTP 200, p95 latency looks normal, error rate is zero. Your agent ran 4,000 times this morning. Then a customer emails saying it gave them completely wrong product information. Where do you even start?</p><p>When something goes wrong with an AI agent, you need to answer six questions fast. The previous post covers what those questions are and why standard observability can&rsquo;t address them. Briefly: what context did the agent have, what did tools return, what did the model receive, what did it produce, what did it cost and was the output good.</p><p>Standard OpenTelemetry answers none of these. It captures span timing, parent-child relationships and error codes&mdash;necessary, but not sufficient for debugging agent behavior.</p><p>The gap is significant. A request can return HTTP 200, produce no errors and still have:</p><ul><li>Hallucinated a fact that isn&rsquo;t in the context</li><li>Called a tool with an argument that returned stale data</li><li>Consumed 10 times the expected tokens due to an oversized system prompt</li><li>Produced a response that passes format validation but fails on quality</li></ul><p>Without AI-specific context baked into the trace, every one of these failures is invisible until a user reports it.</p><blockquote><p><strong>This post covers trace design</strong>&mdash;what to put into a trace and how to instrument it. For a deep dive on detecting agents that silently return wrong answers despite showing HTTP 200, see <a target="_blank" href="https://www.telerik.com/blogs/when-status-ok-still-failure-taxonomy-silent-ai-agent-breakage-how-detect">When Status OK Is Still a Failure</a>. For tracking and optimizing token spend, see <a target="_blank" href="https://www.telerik.com/blogs/ai-cost-visibility-before-the-invoice">AI Cost Visibility Before the Invoice</a>.</p></blockquote><h2 id="architecture-overview">Architecture Overview</h2><p>The Progress Observability Platform sits between your application code and the observability backend. It uses standard OpenTelemetry plumbing but enriches spans at the collector level with AI-specific data: cost calculations, token attribution and tag enrichment.</p><pre class=" language-text"><code class="prism  language-text">flowchart LR  
App["Your App\n(Python + SDK)"] --&gt;|"OTel spans\n(OTLP/gRPC)"| Collector["Progress Collector\n(Auth + Cost enrichment)"]  
Collector --&gt;|enriched spans| Backend["Platform Backend\n(Storage + Processing)"]  
Backend --&gt; Dashboard["Platform Dashboard\n(Observations, Costs, Scores)"]
</code></pre><p>The SDK handles auto-instrumentation. Once initialized, it patches supported libraries&mdash;OpenAI, LangChain, Anthropic, LlamaIndex and others&mdash;so every LLM call and framework operation creates a span automatically. You don&rsquo;t touch your LLM client code.</p><p>Cost calculation happens in the collector, not in your application. This matters more than it sounds: model providers change pricing with a blog post and a short notice period. If cost logic lives in your app, that&rsquo;s a code change, a PR, a deploy and potentially a rollout across multiple services. If it lives in the collector, it&rsquo;s a configuration update in one place.</p><h2 id="trace-hygiene-vs.-trace-volume">Trace Hygiene vs. Trace Volume</h2><p>The AI observability community has a specific name for the gap between having many spans and having useful ones: the <strong>trace noise problem</strong>.</p><p>Standard OpenTelemetry auto-instrumentation was designed to instrument everything. In practice, that means LLM calls appear alongside HTTP client requests, authentication flows, database queries and framework internals. That&rsquo;s all technically valid telemetry, but none of it is useful when you need to understand <em>why</em> an agent produced a wrong answer.</p><p>This shows up repeatedly across AI observability tooling: a single agent trace can contain the real LangChain or LLM spans right next to Microsoft Graph authentication calls, generic HTTP events and other infrastructure telemetry because OTel does not distinguish AI-relevant spans from infrastructure spans by default. The usual fix is some combination of instrumentation-scope blocklists, span filters or name-based selection, but that filtering surface tends to expand as more libraries get added. Across the market, vendors have added span kind filters, annotation-based filtering, name-based span selection and similar narrowing controls precisely because isolating relevant spans from a noisy store is an ongoing operational challenge.</p><p><strong>Trace volume</strong> is easy to produce. <strong>Trace hygiene</strong>&mdash;capturing the right signals, in the right structure, with the right context&mdash;requires deliberate design.</p><p>As the companion awareness piece <a target="_blank" href="https://www.telerik.com/blogs/six-questions-every-ai-trace-should-answer">The Six Questions Every AI Trace Should Be Able to Answer</a> describes, trace hygiene rests on five implementation principles: <strong>Scope</strong> (instrument AI operations, not all infrastructure), <strong>Structure</strong> (spans that reflect agent logic), <strong>Content</strong> (prompt and completion text captured), <strong>Attribution</strong> (tags for filtering) and <strong>Quality</strong> (evaluation scores tied to traces). The four steps below implement those principles in practice&mdash;each one closes a gap that a timing-only OTel span leaves open.</p><h2 id="implementation-walk-through">Implementation Walk-through</h2><h3 id="step-1-install-and-initialize">Step 1: Install and Initialize</h3><pre class=" language-bash"><code class="prism  language-bash">pip <span class="token function">install</span> progress-observability
</code></pre><p>Initialize once at the very start of your process, before importing LLM libraries. The SDK patches libraries at initialization time. If you import <code>openai</code> first, those calls won&rsquo;t be traced.</p><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">import</span> os  
<span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> Observability  
  
Observability<span class="token punctuation">.</span>instrument<span class="token punctuation">(</span>  
  app_name<span class="token operator">=</span><span class="token string">"support-agent"</span><span class="token punctuation">,</span>  
  api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OBSERVABILITY_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>  
  trace_content<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span> <span class="token comment"># captures actual prompt and   completion text  </span>
<span class="token punctuation">)</span>
</code></pre><p><code>trace_content=True</code> is the most important configuration choice here. Without it, you get timing spans with no content&mdash;which is fine for production environments with PII concerns, but makes debugging nearly impossible. Use environment variables to toggle this per deployment.</p><h3 id="step-2-tag-your-traces">Step 2: Tag Your Traces</h3><p>Tags let you filter observations in the platform by environment, team, experiment or any dimension that matters. Set global tags at initialization:</p><pre class=" language-python"><code class="prism  language-python">Observability<span class="token punctuation">.</span>instrument<span class="token punctuation">(</span>  
  app_name<span class="token operator">=</span><span class="token string">"support-agent"</span><span class="token punctuation">,</span>  
  api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OBSERVABILITY_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>  
  trace_content<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>  
  additional_tags<span class="token operator">=</span><span class="token punctuation">[</span>  
    <span class="token string">"environment:staging"</span><span class="token punctuation">,</span>  
    <span class="token string">"team:support"</span><span class="token punctuation">,</span>  
    <span class="token string">"release:2.4.1"</span><span class="token punctuation">,</span>  
  <span class="token punctuation">]</span><span class="token punctuation">,</span>  
<span class="token punctuation">)</span>
</code></pre><p>For tags that vary per request&mdash;tenant ID, experiment variant&mdash;use <code>propagate_attributes</code> to scope them to a specific block of code:</p><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> propagate_attributes  
  
<span class="token keyword">async</span>  <span class="token keyword">def</span> <span class="token function">handle_request</span><span class="token punctuation">(</span>user_message<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> tenant_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span><span class="token punctuation">:</span>  
  <span class="token keyword">with</span> propagate_attributes<span class="token punctuation">(</span>tags<span class="token operator">=</span><span class="token punctuation">[</span>f<span class="token string">"tenant:{tenant_id}"</span><span class="token punctuation">,</span> <span class="token string">"experiment-prompt-v3"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span>  
    <span class="token comment"># Every span created inside this block inherits these tags</span>
    result <span class="token operator">=</span>  <span class="token keyword">await</span> run_agent<span class="token punctuation">(</span>user_message<span class="token punctuation">)</span>  
  <span class="token keyword">return</span> result
</code></pre><p>Tags from <code>propagate_attributes</code> merge with global tags and are deduplicated automatically. Nesting is supported&mdash;tags accumulate from outer to inner context managers.</p><p>The practical payoff: when a customer reports an issue, filter by <code>tenant:acme-corp</code> combined with <code>release:2.4.1</code> and you&rsquo;ve narrowed a million traces down to the 20that matter.</p><h3 id="step-3-decorate-your-agent-structure">Step 3: Decorate Your Agent Structure</h3><p>Auto-instrumentation captures LLM calls, but it doesn&rsquo;t know your agent&rsquo;s structure. Without decorators, the trace shows a flat list of LLM calls with no context about which workflow triggered them or why a particular tool was invoked.</p><p>The SDK provides four decorator types:</p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th>Decorator</th><th>Use for</th></tr></thead><tbody><tr><td><code>@agent</code></td><td>The AI agent entry point</td></tr><tr><td><code>@workflow</code></td><td>Multi-step orchestration that coordinates tasks</td></tr><tr><td><code>@tool</code></td><td>Functions the agent can invoke</td></tr><tr><td><code>@task</code></td><td>Discrete units of work: retrieval, preprocessing, API calls</td></tr></tbody></table><br /><p>Here&rsquo;s a complete instrumented agent:</p><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> agent<span class="token punctuation">,</span> workflow<span class="token punctuation">,</span> tool<span class="token punctuation">,</span> taskfrom openai <span class="token keyword">import</span> AsyncOpenAI  
<span class="token keyword">import</span> os  
  
client <span class="token operator">=</span> AsyncOpenAI<span class="token punctuation">(</span><span class="token punctuation">)</span>  
  
@task<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"retrieve-context"</span><span class="token punctuation">,</span> attributes<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">"source"</span><span class="token punctuation">:</span> <span class="token string">"vector-db"</span><span class="token punctuation">}</span><span class="token punctuation">)</span>  
<span class="token keyword">async</span>  <span class="token keyword">def</span> <span class="token function">retrieve_relevant_docs</span><span class="token punctuation">(</span>query<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span>  <span class="token builtin">list</span><span class="token punctuation">[</span><span class="token builtin">str</span><span class="token punctuation">]</span><span class="token punctuation">:</span>  
  <span class="token triple-quoted-string string">"""Retrieves relevant documents &mdash; span captures latency and custom attributes."""</span>
  results <span class="token operator">=</span>  <span class="token keyword">await</span> vector_db<span class="token punctuation">.</span>search<span class="token punctuation">(</span>query<span class="token punctuation">,</span> top_k<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span>  
  <span class="token keyword">return</span> <span class="token punctuation">[</span>r<span class="token punctuation">.</span>text <span class="token keyword">for</span> r <span class="token keyword">in</span> results<span class="token punctuation">]</span>  
  
@tool<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"fetch-product-info"</span><span class="token punctuation">)</span>  
<span class="token keyword">async</span>  <span class="token keyword">def</span> <span class="token function">fetch_product</span><span class="token punctuation">(</span>product_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span>  <span class="token builtin">dict</span><span class="token punctuation">:</span>  
  <span class="token triple-quoted-string string">"""Tool span captures the product_id input and the dict return value."""</span>  
  <span class="token keyword">return</span>  <span class="token keyword">await</span> product_service<span class="token punctuation">.</span>get<span class="token punctuation">(</span>product_id<span class="token punctuation">)</span>  
  
@workflow<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"answer-product-question"</span><span class="token punctuation">,</span> version<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">)</span>  
<span class="token keyword">async</span>  <span class="token keyword">def</span> <span class="token function">answer_question</span><span class="token punctuation">(</span>user_query<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> product_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span>  <span class="token builtin">str</span><span class="token punctuation">:</span>
  context <span class="token operator">=</span>  <span class="token keyword">await</span> retrieve_relevant_docs<span class="token punctuation">(</span>user_query<span class="token punctuation">)</span>
  product <span class="token operator">=</span>  <span class="token keyword">await</span> fetch_product<span class="token punctuation">(</span>product_id<span class="token punctuation">)</span>
  context_text <span class="token operator">=</span>  <span class="token string">"\n"</span><span class="token punctuation">.</span>join<span class="token punctuation">(</span>context<span class="token punctuation">)</span>
  messages <span class="token operator">=</span> <span class="token punctuation">[</span>  
    <span class="token punctuation">{</span><span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"system"</span><span class="token punctuation">,</span> <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token string">"You are a helpful product support agent."</span><span class="token punctuation">}</span><span class="token punctuation">,</span>  
    <span class="token punctuation">{</span>  
      <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>  
      <span class="token string">"content"</span><span class="token punctuation">:</span> f<span class="token string">"Context:\n{context_text}\n\nProduct: {product}\n\nQuestion: {user_query}"</span><span class="token punctuation">,</span>  
    <span class="token punctuation">}</span><span class="token punctuation">,</span>  
  <span class="token punctuation">]</span>  
  
  <span class="token comment"># Auto-instrumented: span captures model, messages, completion, token counts</span>
  response <span class="token operator">=</span>  <span class="token keyword">await</span> client<span class="token punctuation">.</span>chat<span class="token punctuation">.</span>completions<span class="token punctuation">.</span>create<span class="token punctuation">(</span>  
    model<span class="token operator">=</span><span class="token string">"gpt-4.1-mini"</span><span class="token punctuation">,</span>  
    messages<span class="token operator">=</span>messages<span class="token punctuation">,</span>  
  <span class="token punctuation">)</span>  
  <span class="token keyword">return</span> response<span class="token punctuation">.</span>choices<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>message<span class="token punctuation">.</span>content  
  
@agent<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"product-support-agent"</span><span class="token punctuation">)</span>  
<span class="token keyword">async</span>  <span class="token keyword">def</span> <span class="token function">handle_request</span><span class="token punctuation">(</span>user_message<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> tenant_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span>  <span class="token builtin">str</span><span class="token punctuation">:</span>  
  <span class="token keyword">with</span> propagate_attributes<span class="token punctuation">(</span>tags<span class="token operator">=</span><span class="token punctuation">[</span>f<span class="token string">"tenant:{tenant_id}"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    product_id <span class="token operator">=</span> extract_product_id<span class="token punctuation">(</span>user_message<span class="token punctuation">)</span>  
    <span class="token keyword">return</span>  <span class="token keyword">await</span> answer_question<span class="token punctuation">(</span>user_query<span class="token operator">=</span>user_message<span class="token punctuation">,</span> product_id<span class="token operator">=</span>product_id<span class="token punctuation">)</span>
</code></pre><p>In the platform, this renders as:</p><pre class=" language-text"><code class="prism  language-text">agent: product-support-agent  
└── workflow: answer-product-question (v2)  
  ├── task: retrieve-context [source: vector-db]  
  ├── tool: fetch-product-info [input: product_id, output: {...}]  
  └── llm: gpt-4.1-mini [tokens: 847, cost: $0.00042]
</code></pre><p>That single view replaced hours of log grepping. Without the decorators, this same run would show a flat list of anonymous <code>llm: gpt-4.1-mini</code> spans with no structural context&mdash;no way to tell which call is the retrieval prompt and which is the final answer generation. You&rsquo;d count spans and guess.</p><h3 id="step-4-shut-down-cleanly">Step 4: Shut Down Cleanly</h3><p>Always flush pending telemetry before the process exits. Without this, buffered spans are lost&mdash;especially in scripts, short-lived containers and serverless functions.</p><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">import</span> asyncio  
<span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> Observability  
  
<span class="token keyword">async</span>  <span class="token keyword">def</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>  
  <span class="token keyword">try</span><span class="token punctuation">:</span>  
    <span class="token keyword">await</span> handle_request<span class="token punctuation">(</span>  
      user_message<span class="token operator">=</span><span class="token string">"What's the return policy for order #12345?"</span><span class="token punctuation">,</span>  
      tenant_id<span class="token operator">=</span><span class="token string">"acme-corp"</span><span class="token punctuation">,</span>  
    <span class="token punctuation">)</span>  
  <span class="token keyword">finally</span><span class="token punctuation">:</span>  
    Observability<span class="token punctuation">.</span>shutdown<span class="token punctuation">(</span><span class="token punctuation">)</span>  
  
asyncio<span class="token punctuation">.</span>run<span class="token punctuation">(</span>main<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre><p>In long-running services, hook <code>Observability.shutdown()</code> into your SIGTERM handler or application lifecycle teardown, not into individual request handlers.</p><h2 id="common-pitfalls">Common Pitfalls</h2><h3 id="initializing-after-llm-imports">Initializing After LLM Imports</h3><p>This is the most common mistake, and the most disorienting to debug. Your agent runs, returns results, no exceptions anywhere. And zero traces appear in the platform. Not errors, not partial traces: silence.</p><p>The SDK patches libraries at the time <code>Observability.instrument()</code> is called. Any library imported before that call won&rsquo;t be patched.</p><pre class=" language-python"><code class="prism  language-python"># Wrong &mdash; openai is imported before instrumentation  
import openai  
from progress.observability import Observability  
Observability.instrument(...)  
  
# Correct &mdash; instrumentation first  
from progress.observability import Observability  
Observability.instrument(...)  
import openai
</code></pre><h3 id="disabling-trace_content-globally">Disabling trace_content Globally</h3><p>Setting <code>trace_content=False</code> everywhere eliminates your ability to debug output quality issues. Use the <code>OBSERVABILITY_TRACE_CONTENT</code> environment variable to control this per deployment without changing code.</p><pre class=" language-python"><code class="prism  language-python"># .env.production  
OBSERVABILITY_TRACE_CONTENT=false  
  
# .env.staging  
OBSERVABILITY_TRACE_CONTENT=true
</code></pre><h3 id="skipping-decorators">Skipping Decorators</h3><p>Auto-instrumentation alone gives you a flat trace of LLM calls. Decorators give you structure. Without them, a three-step agent looks like this in the platform:</p><pre class=" language-python"><code class="prism  language-python">llm: gpt-4.1-mini [tokens: 312]  
llm: gpt-4.1-mini [tokens: 847]  
llm: gpt-4.1-mini [tokens: 203]
</code></pre><p>No workflow context, no tool inputs, no parent-child relationships. You&rsquo;re left counting spans and guessing which one is the retrieval call and which is the final answer.</p><h3 id="missing-the-shutdown-call">Missing the Shutdown Call</h3><p>Short-lived scripts that don&rsquo;t call <code>Observability.shutdown()</code> frequently lose the last batch of spans. This makes traces appear truncated or missing in the platform. In AWS Lambda and other serverless runtimes, the environment can be frozen or terminated between invocations before the buffer flushes&mdash;which means you often lose the most recent trace completely, exactly when you&rsquo;re trying to debug a one-off failure.</p><h2 id="tradeoffs">Tradeoffs</h2><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th>Choice</th><th>When to use it</th><th>What you give up</th></tr></thead><tbody><tr><td><code>trace_content=True</code></td><td>Development, staging, debugging</td><td>Prompt and completion text in trace storage&mdash;PII exposure risk in production</td></tr><tr><td><code>trace_content=False</code></td><td>Production with PII</td><td>Can&rsquo;t inspect what the model received or returned</td></tr><tr><td>Global tags only</td><td>Simple single-environment apps</td><td>No per-tenant or per-experiment filtering</td></tr><tr><td><code>propagate_attributes</code></td><td>Multi-tenant or A/B testing</td><td>Context manager overhead in async code</td></tr><tr><td>Full decorator coverage</td><td>Complex agent workflows</td><td>Some boilerplate per function</td></tr><tr><td>Real-time evaluation tasks</td><td>Ongoing production quality monitoring</td><td>2 units consumed per evaluated span</td></tr></tbody></table><br /><p>Starting fresh? Default to <code>trace_content=True</code> in staging, <code>propagate_attributes</code> for anything multi-tenant and full decorator coverage from day one. These are easy to set up upfront and painful to retrofit when you&rsquo;re trying to debug something at 2 a.m.</p><h2 id="next-steps">Next Steps</h2><ol><li><p><strong>Clone the demo first:</strong> <a target="_blank" href="https://github.com/NickIliev/support-agent-demo">NickIliev/support-agent-demo</a> is a fully runnable version of the agent from this post. Add your API keys to <code>.env</code> and run python <code>support_agent.py</code>. All six trace elements will appear in your dashboard immediately. This is the fastest way to see a complete, well-instrumented trace before building your own.</p></li><li><p><strong>Instrument one agent:</strong> <code>pip install progress-observability</code>, add <code>Observability.instrument()</code> with <code>trace_content=True</code>, apply the decorators from Step 3. You&rsquo;ll have a working trace in under 15 minutes.</p></li><li><p><strong>Open the Observations view</strong> and inspect the span tree. Filter by a tag, drill into a span, verify the exact prompt the model received.</p></li><li><p><strong>Set up one evaluation task</strong> to start tracking output quality automatically.</p></li><li><p><strong>Want a guided path from zero?</strong> Work through the Log Your First AI Trace quickstart <em>(coming soon)</em> for a structured first-trace experience in 10&ndash;15 minutes. In the meantime, the <a target="_blank" href="https://www.telerik.com/ai-observability-platform/documentation/getting-started">getting started guide</a> covers the same ground&mdash;free to start, 5-minute setup.</p></li></ol><hr /><p><strong>AI Observability Reading Path</strong></p><ol><li><a target="_blank" href="https://www.telerik.com/blogs/six-questions-every-ai-trace-should-answer">The Six Questions Every AI Trace Should Be Able to Answer</a> &ndash; Awareness: why AI traces need more than infrastructure observability</li><li><strong>You are here</strong>: What a Useful AI Trace Should Actually Contain (And How to Build One)</li><li><a target="_blank" href="https://github.com/NickIliev/support-agent-demo">NickIliev/support-agent-demo</a> &ndash; Hands-on demo on GitHub</li></ol>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:d487dffa-56e8-493a-b7b3-47bd62e986a2</id>
    <title type="text">Telerik UI for Blazor vs. Syncfusion Blazor: Which Is Better for Long-Term Application Development?</title>
    <summary type="text">Both Telerik UI for Blazor and Syncfusion Blazor can be used to build complex business applications. See how the two stack up across several evaluation areas.</summary>
    <published>2026-08-18T17:08:35Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Maya Mateva </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/telerik-ui-blazor-vs-syncfusion-blazor-which-better-long-term-application-development"/>
    <content type="text"><![CDATA[<p><span class="featured">Both Telerik UI for Blazor and Syncfusion Blazor can be used to build complex business applications. See how the two stack up across several evaluation areas.</span></p><p>Blazor has become a serious platform for building modern web applications with .NET. As the framework has matured, commercial UI component libraries have become an important part of the Blazor development experience.</p><p>Instead of building every Grid, Chart, Scheduler, Form, Editor, and navigation component from scratch, teams can rely on mature component suites to accelerate development, improve UI quality and reduce long-term maintenance effort. Two of the most common options for Blazor development are <strong>Progress Telerik UI for Blazor</strong> and <strong>Syncfusion Blazor</strong>.</p><p>Both are mature. Both provide large component collections, commercial support, documentation, examples and tooling for .NET developers. Both can be used to build complex business applications.</p><p>The decision is rarely about whether one product is objectively &ldquo;better.&rdquo; A more useful question is: &ldquo;Which library better matches the way your team builds, maintains, upgrades and scales Blazor applications over time?&rdquo;</p><p>At a high level, <strong>Syncfusion Blazor</strong> is attractive for teams that want a very broad component catalog, access to many specialized controls and a Community License for qualifying users. <strong><a target="_blank" href="https://www.telerik.com/blazor-ui">Telerik UI for Blazor</a></strong> is a strong fit for teams that value a cohesive developer experience, predictable APIs, strong theming workflows, product polish, support reputation and long-term maintainability.</p><p>This comparison looks at both products through practical evaluation criteria: onboarding, component coverage, grid capabilities, theming, accessibility, performance, support, AI readiness and long-term project fit.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-blazor-dashboard.png?sfvrsn=4c114d9e_2" alt="Blazor Dashboard app" /></p><h2 id="quick-decision-summary">Quick Decision Summary</h2><p>Choose Telerik UI for Blazor if&hellip;</p><ul><li>You are building a long-lived application that will be maintained for several years.</li><li>Multiple developers or product teams will contribute to the same UI codebase.</li><li>You value predictable APIs and standardized implementation patterns.</li><li>You need strong theming, design-token management or designer-developer collaboration.</li><li>You care about upgrade experience, support quality and long-term maintainability.</li><li>You want a component suite that is easy for both developers and AI coding assistants to reason about.</li></ul><h2 id="key-differences-at-a-glance">Key Differences at a Glance</h2><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Evaluation Area</strong></th><th><strong>Telerik UI for Blazor</strong></th><th><strong>Syncfusion Blazor</strong></th></tr></thead><tbody><tr><td>Primary Strength</td><td>Cohesive developer experience, theming, maintainability</td><td>Broad component catalog and ecosystem coverage</td></tr><tr><td>Best Fit</td><td>Long-lived apps, shared UI platforms, design-system driven teams</td><td>Teams prioritizing maximum component availability</td></tr><tr><td>Component Count</td><td>120+ Blazor components</td><td>145+ Blazor components through the Community License page</td></tr><tr><td>Community License</td><td>No equivalent free Community License for qualifying small companies</td><td>Available for qualifying companies and individuals under stated revenue, developer and employee limits</td></tr><tr><td>Theming</td><td>ThemeBuilder with visual customization, CSS/SASS variables, Figma integration, project sharing, version control and user access rights</td><td>Flexible styling and theme customization options</td></tr><tr><td>Accessibility</td><td>Provides a WCAG 2.2 compliance table and Accessibility Conformance Report through VPAT</td><td>Documents WAI-ARIA, WCAG 2.2, Section 508, screen reader, keyboard, color contrast and RTL accessibility support</td></tr><tr><td>Grid</td><td>Strong focus on polished data scenarios, predictable usage patterns and accessibility documentation</td><td>Broad Grid feature set and detailed accessibility documentation</td></tr><tr><td>AI Readiness</td><td>Public documentation references AI/WebMCP-related work in Telerik Blazor docs</td><td>Syncfusion has published Blazor skills and license-related resources for AI-assisted component workflows</td></tr><tr><td>Overall Positioning</td><td>Strong fit when coherence, team productivity and UI governance matter most</td><td>Strong fit when breadth and licensing flexibility matter most</td></tr></tbody></table><br /><h2 id="what-most-teams-eventually-optimize-for">What Most Teams Eventually Optimize For</h2><p>When teams first compare component libraries, they usually start with feature lists.</p><p>That makes sense. Component count, available controls, Grid features, Scheduler options, chart types, templates, exporting and documentation examples are easy to compare.</p><p>But once a Blazor application moves into production, different questions become more important:</p><ul><li>How easy is the application to maintain?</li><li>Can new developers understand the UI code quickly?</li><li>Are component APIs predictable?</li><li>How painful are upgrades?</li><li>Can we apply the same branding across multiple applications?</li><li>Can designers and developers work from the same design system?</li><li>How reliable is support when something breaks?</li><li>Can AI coding assistants generate correct and maintainable UI code?</li></ul><p>This is where the distinction between <strong>breadth</strong> and <strong>cohesion</strong> matters.</p><p>Syncfusion has a strong case when a team wants access to a broad collection of components, related document libraries and specialized controls. Telerik has a very strong case when a team wants a more unified development model, mature theming workflows and a polished component experience across the core UI scenarios that most business applications depend on.</p><p>Neither priority is wrong. The better choice depends on what your team values most.</p><h2 id="time-to-first-value">Time to First Value</h2><p>Feature comparisons are useful, but the first developer experience is usually more practical:</p><ul><li>How do I install the package?</li><li>How do I configure licensing?</li><li>How do I set up NuGet access?</li><li>How do I create the first Blazor project?</li><li>How quickly can I render a real component?</li><li>How many manual steps are required before I can start evaluating the library?</li></ul><p>This is where <strong>time to first value</strong> becomes important.</p><h3 id="time-to-first-value-telerik-ui-for-blazor">Time to First Value: Telerik UI for Blazor</h3><p>Telerik puts a strong emphasis on guided onboarding. The product experience includes getting-started documentation, templates, licensing guidance, CLI-based workflows and integration with Telerik tooling.</p><p>Telerik focuses heavily on onboarding and guided setup, and this includes:</p><ul><li>Structured getting-started documentation</li><li>Starter project guidance</li><li>Project templates and configuration assistance</li><li>Consistent setup patterns across components</li></ul><p>The Telerik CLI can help reduce setup friction by assisting with project creation, package configuration, licensing-related setup, templates and related tooling.</p><p>This matters because early evaluation is not only about installing a NuGet package. It is about reducing the number of small setup decisions that slow teams down before they can assess the actual components.</p><p>The setup becomes as simple as:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-cli.png?sfvrsn=d8047b3e_2" width="400" alt="dotnet tool install -g Telerik.CLI  Telerik setup blazor  dotnet new Telerik-blazor -o MyApplication" /></p><p>For larger teams, this kind of guided setup can be useful because new developers do not need to rediscover the same configuration steps independently.</p><h3 id="time-to-first-value-syncfusion-blazor">Time to First Value: Syncfusion Blazor</h3><p>Syncfusion also has a strong onboarding story. Its ecosystem includes product documentation, sample applications and a large collection of examples. The Syncfusion Blazor samples repository describes demos for Blazor Server and Blazor WebAssembly apps, including project files targeting modern .NET versions.</p><p>That breadth of examples can be valuable when a developer is looking for a specific scenario or specialized component behavior.</p><h3 id="time-to-first-value-takeaway">Time-to-First-Value Takeaway</h3><p>For a small proof of concept, both products can get developers started quickly.</p><p>The advantage of Telerik UI is more about guided setup and a cohesive path from installation to production-style usage. Syncfusion&rsquo;s advantage is the large ecosystem of samples.</p><h2 id="component-coverage">Component Coverage</h2><p>Component count is one of the most visible comparison points.</p><p>Syncfusion Blazor is known for broad component coverage. Its Community License page describes access to <strong>145+ Blazor components</strong> for eligible users and organizations. Syncfusion&rsquo;s broader product line also includes UI components, document SDKs and standalone UI SDKs under the Community License description.</p><p>That breadth is a legitimate advantage. Teams that need niche controls, document processing capabilities, PDF-related libraries, Office document support or specialized UI scenarios may find Syncfusion difficult to overlook.</p><p>Telerik UI for Blazor takes a somewhat different approach. It provides a large suite of core Blazor components, but its strongest value proposition is not simply the number of controls. Telerik UI is strongest when the application depends heavily on polished core components such as Grid, Charts, Scheduler, Forms, Inputs, Editors, Layout, Navigation, Upload and theming.</p><p>For many business applications, both libraries cover the essential requirements. The practical difference is often not &ldquo;Does a component exist?&rdquo; but:</p><ul><li>How predictable is it to configure?</li><li>Does it follow familiar patterns from other components in the same suite?</li><li>Does documentation match real development scenarios?</li><li>Is the component easy to theme and maintain?</li><li>Does it fit into a shared UI platform?</li></ul><h3 id="component-coverage-takeaway">Component Coverage Takeaway</h3><p>Syncfusion has the advantage in overall breadth and specialized coverage.</p><p>Telerik has the advantage when teams want a cohesive suite focused on core business application scenarios, polished UI behavior and maintainable implementation patterns.</p><h2 id="the-grid-the-component-that-often-decides-the-evaluation">The Grid: The Component That Often Decides the Evaluation</h2><p>If one component influences UI library decisions more than any other, it is usually the grid.</p><p>Business applications spend a large amount of time displaying, filtering, grouping, sorting, editing, exporting and analyzing data. For many teams, the grid is not just one component. It is the center of the application.</p><p>Both Telerik UI for Blazor and Syncfusion Blazor provide capable Grid components with support for common business scenarios such as:</p><ul><li>Sorting</li><li>Filtering</li><li>Grouping</li><li>Paging</li><li>Virtualization</li><li>Inline editing</li><li>Batch editing</li><li>Templates</li><li>Aggregates</li><li>Exporting</li><li>Hierarchical data</li><li>Large dataset scenarios</li></ul><p>Both vendors can support serious data-heavy applications. In practice, grid performance depends not only on the component itself but also on data access strategy, rendering mode, server communication, virtualization configuration and application architecture.</p><h3 id="telerik-ui-for-blazor-grid">Telerik UI for Blazor Grid</h3><p>The Telerik UI for <a target="_blank" href="https://demos.telerik.com/blazor-ui/grid/overview">Blazor Grid</a> is strongest when teams care about a polished and predictable implementation model.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-blazor-grid.png?sfvrsn=3e25ad7e_2" alt="Telerik Blazor grid for shipment tracker" /></p><p>The main advantages are not only feature availability, but how the Grid fits into the broader development experience:</p><ul><li>Predictable configuration patterns</li><li>Strong template support</li><li>Familiar event and data-binding approaches</li><li>Alignment with the rest of the Telerik component suite</li><li>Accessibility-specific documentation</li><li>A mature experience for common business data scenarios</li></ul><p>Telerik Grid accessibility is <strong>WCAG 2.2 AA and Section 508 compliant</strong>, follows WAI-ARIA best practices for keyboard navigation and is tested with popular screen readers. That is a concrete point worth considering for teams building applications with formal accessibility requirements.</p><h3 id="syncfusion-blazor-grid">Syncfusion Blazor Grid</h3><p>The Syncfusion Grid is also highly capable and has a broad feature surface. It is particularly attractive when teams need extensive configuration options or specialized behaviors.</p><p>Syncfusion&rsquo;s Grid supports accessibility standards including ADA, Section 508, WCAG 2.2 and ARIA roles, with details on screen reader support, RTL support, color contrast, mobile device support, keyboard navigation and Axe-core validation.</p><h3 id="grid-takeaway">Grid Takeaway</h3><p>Both Grids are mature and capable.</p><p>Syncfusion&rsquo;s Grid stands out for broad functionality and configurability. The Telerik Grid stands out for a polished developer experience, accessibility documentation and a usage model that tends to fit well into long-lived applications with multiple contributors.</p><p>If your evaluation is based on a specific Grid feature, test that feature in both products. If your evaluation is based on long-term maintainability, onboarding, accessibility and shared implementation patterns, Telerik deserves stronger consideration.</p><h2 id="scheduler-charts-and-dashboards">Scheduler, Charts and Dashboards</h2><p>After the Grid, Scheduler and Charts are often among the most important components in business applications.</p><h3 id="scheduler">Scheduler</h3><p>Both Telerik and Syncfusion support common scheduling scenarios such as:</p><ul><li>Day, week and month views</li><li>Recurring events</li><li>Resource grouping</li><li>Drag-and-drop editing</li><li>Custom templates</li><li>Business calendar scenarios</li></ul><p>Syncfusion is attractive when the team wants broad scenario coverage and many examples.</p><p>Telerik is attractive when the <a target="_blank" href="https://demos.telerik.com/blazor-ui/scheduler/overview">Blazor Scheduler</a> needs to feel like part of the same UI platform as the Grid, Forms, Inputs, Dialogs, Charts and navigation components.</p><h3 id="charts-and-dashboards">Charts and Dashboards</h3><p>Both libraries provide charting capabilities suitable for dashboards, reporting UIs, KPI tracking, analytics screens and business intelligence-style interfaces.</p><p>The distinction is less about whether common chart types exist and more about the surrounding workflow:</p><ul><li>How easily can charts be themed?</li><li>Can they follow the same visual system as the rest of the app?</li><li>Can multiple teams reuse the same design language?</li><li>Can dashboard screens stay visually aligned over time?</li></ul><p>This is where the Telerik theming story becomes especially relevant.</p><h2 id="theming-and-design-system-governance">Theming and Design-System Governance</h2><p>Theming is often underestimated during evaluations.</p><p>At the start of a project, teams usually focus on functionality. Later, especially in long-lived applications, theming becomes a product and architecture concern.</p><p>Questions start to appear:</p><ul><li>Can we align components with our brand?</li><li>Can designers and developers collaborate without translating everything manually?</li><li>Can we reuse themes across multiple applications?</li><li>Can we manage design tokens centrally?</li><li>Can we support light and dark modes?</li><li>Can we avoid one-off CSS overrides that become hard to maintain?</li><li>Can we update the visual language without rewriting large parts of the UI?</li></ul><p>This is one of Progress Telerik UI&rsquo;s clearest differentiators.</p><h3 id="progress-telerik-themebuilder">Progress Telerik ThemeBuilder</h3><p><a href="https://www.telerik.com/themebuilder" target="_blank">Progress Telerik ThemeBuilder</a> is a SaaS tool that enables teams to create custom themes and preview how they affect component appearance. It can generate a CSS file that can be used in a Blazor app instead of a built-in theme.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/progress-themebuilder-blazor.png?sfvrsn=d5ab735f_2" alt="Progress ThemeBuilder showing button styles" /></p><p>ThemeBuilder also supports capabilities that matter in design-system driven organizations, including:</p><ul><li>Atomic customization</li><li>CSS/SASS variables</li><li>Custom HTML support</li><li>Figma integration</li><li>Theme modes and built-in themes</li><li>Custom fonts and iconography</li><li>Project sharing</li><li>Version control</li><li>Collaboration and user access rights</li></ul><p>This is more than visual customization. It helps teams create a shared styling workflow between design and engineering.</p><p>For organizations with multiple product teams, this can reduce duplicated CSS work, prevent visual drift between applications and make brand updates easier to manage.</p><h3 id="syncfusion-customization">Syncfusion Customization</h3><p>Syncfusion also provides styling and customization options. For many teams, those capabilities will be enough.</p><p>However, if centralized design-system governance is a major requirement, Progress Telerik ThemeBuilder gives it a particularly strong position.</p><h3 id="theming-takeaway">Theming Takeaway</h3><p>Both libraries can be customized.</p><p>Telerik has a clearer advantage when teams need design tokens, Figma-related workflows, centralized branding, visual governance and reusable themes across multiple applications.</p><h2 id="blazor-app-accessibility">Blazor App Accessibility</h2><p>Accessibility should be part of any serious UI library evaluation.</p><p>Teams building applications for public sector, healthcare, finance, education, government or large internal platforms often need to consider:</p><ul><li>WCAG</li><li>Section 508</li><li>Keyboard navigation</li><li>Screen reader support</li><li>WAI-ARIA roles and attributes</li><li>Color contrast</li><li>Accessible interaction alternatives</li><li>Accessibility conformance documentation</li></ul><h3 id="telerik-accessibility">Telerik Accessibility</h3><p><a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/accessibility/compliance#compliance-table">Telerik UI for Blazor provides an accessibility compliance table</a> that lists WCAG 2.2 compliance levels for individual components. Progress Telerik also provides an Accessibility Conformance Report through VPAT.</p><p>For the Grid specifically, Telerik documents WCAG 2.2 AA and Section 508 compliance, WAI-ARIA best practices, keyboard navigation, and screen reader testing.</p><h3 id="syncfusion-accessibility">Syncfusion Accessibility</h3><p>Syncfusion documents accessibility support for Blazor components, including WAI-ARIA, WCAG 2.2, Section 508, keyboard navigation, screen reader support, RTL support and color contrast considerations.</p><p>Syncfusion&rsquo;s DataGrid accessibility documentation also provides a detailed view of supported standards and limitations for complex interactions.</p><h3 id="accessibility-takeaway">Accessibility Takeaway</h3><p>Both vendors take accessibility seriously and provide documentation around standards and component behavior.</p><p>The Telerik component-level compliance table and documented Grid compliance are helpful for teams that need formal accessibility evidence. Syncfusion also provides broad accessibility documentation, including detailed DataGrid accessibility coverage.</p><p>For accessibility-sensitive applications, the best approach is to test the exact components and configurations your application will use.</p><h2 id="support-documentation-and-learning-resources">Support, Documentation and Learning Resources</h2><p>Support quality is hard to capture in a comparison table, but it often becomes critical after adoption.</p><p>During a trial, teams should evaluate:</p><ul><li>How quickly developers find answers in documentation</li><li>Whether examples match real scenarios</li><li>How clear upgrade notes are</li><li>How support handles complex issues</li><li>Whether feedback and bug reports are visible</li><li>Whether the vendor provides practical guidance beyond simple demos</li></ul><h3 id="telerik-ui-for-blazor-support">Telerik UI for Blazor Support</h3><p>Progress Telerik has a long-standing reputation in the ecosystem for polished components, detailed documentation, active forums, feedback channels and strong support. That reputation is one of the reasons Telerik UI is often favored by teams building long-lived applications.</p><p>This should still be validated during evaluation. The best support test is not reading a marketing page. It is submitting realistic trial questions and measuring how useful the answers are.</p><h3 id="syncfusion-blazor-support">Syncfusion Blazor Support</h3><p>Syncfusion also provides commercial support, documentation, samples and a large knowledge base. Its broad ecosystem can be helpful when developers are searching for examples across many controls and scenarios.</p><p>Syncfusion&rsquo;s large sample repository is especially useful when teams want to inspect working examples and compare usage across components.</p><h3 id="support-takeaway">Support Takeaway</h3><p>Both vendors provide serious support and learning resources.</p><p>Telerik UI is often especially attractive for teams that prioritize product polish, support confidence and long-term UI quality. Syncfusion is strong for teams that value broad documentation coverage, many samples and a large ecosystem.</p><h2 id="product-quality-stability-and-upgrade-experience">Product Quality, Stability and Upgrade Experience</h2><p>Product quality is more than whether a component works in a demo.</p><p>For long-lived applications, quality usually means:</p><ul><li>Stable APIs</li><li>Clear documentation</li><li>Predictable behavior</li><li>Accessibility guidance</li><li>Reliable release notes</li><li>Manageable upgrades</li><li>Good support for modern .NET versions</li><li>Transparent handling of bug fixes and breaking changes</li><li>Similar concepts across related components</li></ul><p>This is where the Telerik UI value proposition becomes stronger. Its advantage is not only that the components are polished, but that the suite tends to feel like a coherent product rather than a collection of unrelated controls.</p><p>That can reduce cognitive load for teams. Developers who learn common Telerik concepts in one component often find similar ideas elsewhere in the suite.</p><p>Syncfusion&rsquo;s broader catalog is valuable, but naturally creates a larger surface area to learn, test and govern. That tradeoff may be completely acceptable if the team needs the additional breadth.</p><h3 id="upgrade-experience-takeaway">Upgrade Experience Takeaway</h3><p>For short-lived apps, upgrade experience may not matter much.</p><p>For applications expected to live for several years, upgrade predictability and maintainable implementation patterns become important. Telerik UI is particularly compelling for teams that prioritize those concerns.</p><h2 id="ai-readiness-and-agentic-development">AI Readiness and Agentic Development</h2><p>A few years ago, component library evaluations focused primarily on features, performance and support. Today, many teams are adding another criterion to their evaluation:</p><p><strong>How well does the platform integrate with AI-assisted development workflows?</strong></p><p>The discussion is no longer limited to AI-powered components. Developers increasingly expect AI tools that can help generate interfaces, configure components, validate code, enforce design-system rules and accelerate application development.</p><h3 id="progress-telerik-ai-approach">Progress Telerik AI Approach</h3><p>Progress Telerik has invested in AI-assisted development across several areas of the product ecosystem, combining UI components, AI-powered tooling and agentic workflows into a more complete development experience.</p><p>The foundation of this effort is the <a href="https://www.telerik.com/mcp-servers#ai-coding-assistant" target="_blank">Telerik AI Coding Assistant</a>, which integrates directly into developer workflows through IDE-based experiences, GitHub Copilot extensions, and <a href="https://www.telerik.com/mcp-servers" target="_blank">MCP-based tooling</a>. The assistant can help generate Telerik UI for Blazor code, configure components, scaffold common scenarios, generate sample data and provide implementation guidance using Telerik-specific knowledge rather than generic web examples.</p><p>Beyond code generation, Progress Telerik has introduced an <a href="https://www.telerik.com/mcp-servers#agentic-ui-generator" target="_blank">Agentic UI Generator</a> that uses a collection of specialized assistants working together through an MCP-based architecture. Rather than generating isolated snippets, it can help create complete pages, dashboards, layouts and component configurations while following Telerik component patterns and design-system guidance.</p><p>The platform includes specialized assistants for:</p><ul><li>Page and UI generation</li><li>Component selection and configuration</li><li>Layout creation</li><li>Styling and theming</li><li>Icons and visual assets</li><li>Accessibility guidance</li><li>Validation and quality checks</li><li>Project onboarding and setup workflows</li></ul><p>These capabilities are orchestrated through the Agentic UI Generator, allowing developers to work at a higher level of abstraction when creating Blazor applications.</p><h4 id="telerik-ai-plugins-mcp-and-development-tools">Telerik AI Plugins, MCP and Development Tools</h4><p>One notable aspect of the Telerik AI strategy is the focus on developer tools rather than only AI-enabled components.</p><p>The Telerik MCP-based tooling includes plugin capabilities for:</p><p>![Telerik MCP-based tooling includes UI Generator/Orchestrator, Getting Started Assistant, Component Assistant, Icon Assistant, Layout Assistant, Styling Assistant, Accessibility Assistant, Validator Assistant](/sfimages/default-source/blogs/2026/2026-08/telerik-mcp- plugins)</p><p>This allows AI tools to work with Telerik-specific knowledge instead of relying solely on generic LLM training data, which can help reduce incorrect component usage and configuration errors.</p><p>In addition, Telerik provides AI-enhanced capabilities within its component ecosystem, including AI-powered chat experiences, semantic search scenarios, AI-assisted data operations, AI integrations within Grid and Editor components, WebMCP support, and integration scenarios involving technologies such as <a target="_blank" href="https://demos.telerik.com/blazor-ui/a2ui/ai-a2ui">A2UI</a>, AG-UI and Microsoft Agent Framework.</p><h3 id="syncfusion-ai-position">Syncfusion AI Position</h3><p>Syncfusion is also actively investing in AI-based developer experiences and provides AI-related features, assistants and tooling across its ecosystem. Like Progress Telerik, Syncfusion is exploring AI-assisted development workflows and has published AI-related resources and component skills for developers working with its platform.</p><p>One of Syncfusion&rsquo;s broader advantages remains the size of its overall ecosystem, which includes a large collection of UI components, document processing libraries and developer tools that can participate in AI-assisted workflows.</p><h3 id="ai-readiness-takeaway">AI Readiness Takeaway</h3><p>Which platform has the stronger AI story? Today, both Progress Telerik and Syncfusion are investing in AI.</p><p>The difference is less about whether AI capabilities exist and more about where the investment is focused.</p><p>Syncfusion&rsquo;s strength comes from the breadth of its ecosystem and the large number of controls, libraries, and scenarios available to developers.</p><p>Telerik UI&rsquo;s strength comes from providing an increasingly connected AI development experience that spans:</p><ul><li>AI Coding Assistant</li><li>Agentic UI Generator</li><li>MCP-based development tooling</li><li>Specialized AI assistants</li><li>Validation workflows</li><li>Accessibility guidance</li><li>Design-system alignment</li><li>AI-enhanced components and features</li></ul><p>For teams looking beyond AI-powered controls and toward AI-assisted UI development, Telerik currently offers one of the more comprehensive AI stories available in the Blazor ecosystem. The combination of UI generation, component-aware assistants, validation tooling, theming guidance and MCP integration makes the AI capabilities feel like part of the overall developer workflow rather than a collection of isolated features.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Progress Telerik Agentic UI Generator vs. Syncfusion Agentic UI Builder</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/progress-telerik-agentic-ui-generator-vs-syncfusion-agentic-ui-builder">See how the AI-based UI creation tools</a> from devtools powerhouses Progress and Syncfusion stack up when they go head to head.</p></div></div><hr class="u-mb3" /></aside><h2 id="where-telerik-has-a-clear-advantage">Where Telerik Has a Clear Advantage</h2><p>Telerik is strongest when the team cares less about maximizing the catalog and more about building a maintainable UI platform.</p><p>Telerik is particularly compelling when:</p><ul><li>The application will live for several years.</li><li>Multiple developers or product teams will work in the same codebase.</li><li>The Grid, Forms, Charts, Scheduler and layout components need to feel cohesive.</li><li>Design-system governance matters.</li><li>The team wants strong theming workflows through ThemeBuilder.</li><li>Accessibility evidence is important.</li><li>Support confidence is a major factor.</li><li>AI-assisted development is becoming part of the engineering workflow.</li></ul><p>The clearest Telerik differentiator is ThemeBuilder. Its support for visual customization, variables, Figma integration, theme modes, custom fonts, project sharing, version control and collaboration makes it especially useful for teams that need a structured design-to-development workflow.</p><h2 id="pros-and-cons">Pros and Cons</h2><p><strong>Telerik UI for Blazor</strong></p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Pros</strong></th><th><strong>Cons</strong></th></tr></thead><tbody><tr><td>Cohesive component suite</td><td>Commercial licensing required</td></tr><tr><td>Strong Grid experience</td><td>Smaller component catalog than Syncfusion (Telerik &ndash; 120+, Syncfusion &ndash; 145+)</td></tr><tr><td>Predictable APIs and implementation patterns</td><td>Fewer niche controls in some specialized areas</td></tr><tr><td>Strong ThemeBuilder and design-system workflow</td><td>Some advanced scenarios may require custom implementation</td></tr><tr><td>Accessibility documentation and compliance resources</td><td>Not the best fit if raw component count is the top priority</td></tr><tr><td>Strong fit for long-lived applications</td><td>&nbsp;</td></tr><tr><td>Good alignment with AI-assisted development trends</td><td>&nbsp;</td></tr><tr><td>Strong support reputation in the .NET ecosystem</td><td>&nbsp;</td></tr></tbody></table><br /><p><strong>Syncfusion Blazor</strong></p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Pros</strong></th><th><strong>Cons</strong></th></tr></thead><tbody><tr><td>Broad component catalog</td><td>Larger API surface can increase complexity</td></tr><tr><td>Community License for qualifying users</td><td>More governance may be needed to standardize usage</td></tr><tr><td>Document and file-format related ecosystem</td><td>Component experience may vary more across the suite</td></tr><tr><td>Many samples and examples</td><td>Teams should carefully test upgrades and scenario-specific behavior</td></tr><tr><td>Strong specialized-control coverage</td><td>&nbsp;</td></tr></tbody></table><br /><h2 id="decision-framework">Decision Framework</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-vs-syncfusion-blazor.png?sfvrsn=ee697bc7_2" alt="Telerik vs Syncfusion for Blazor decision tree" /></p><p><strong>Telerik UI for Blazor is likely the better fit if&hellip;</strong></p><ol><li>The application will be maintained for several years.</li><li>Multiple developers or teams will contribute to the UI.</li><li>You need a shared design system.</li><li>The Grid is central to your application.</li><li>Accessibility documentation is important.</li><li>You want a polished and cohesive development experience.</li><li>Support confidence is part of the buying decision.</li><li>You expect AI-assisted development to become more important.</li><li>You value maintainability more than raw component count.</li></ol><p><strong>Syncfusion Blazor is likely the better fit if&hellip;</strong></p><ol><li>You need the large component catalog.</li><li>You need niche components.</li><li>The Community License applies to you.</li><li>Your team is comfortable managing a larger API surface.</li><li>Your evaluation is primarily driven by breadth and feature availability.</li></ol><h2 id="final-verdict">Final Verdict</h2><p>If your primary goal is maximizing the number of available controls, accessing specialized components, benefiting from a Community License, <strong>Syncfusion Blazor is difficult to overlook</strong>.</p><p>If your priority is building a long-lived Blazor application with a cohesive developer experience, strong theming, accessibility documentation, support confidence, predictable implementation patterns and maintainable UI architecture, <strong>Telerik UI for Blazor offers a very compelling value proposition</strong>.</p><p>The choice comes down to what your team wants to optimize for. Feature breadth matters during evaluation. Maintainability matters in production.</p><p>For teams building applications that need to scale across developers, products, themes, accessibility requirements, and future AI-assisted workflows, <strong>Telerik UI for Blazor stands out as the stronger long-term choice</strong>&mdash;not because Syncfusion is weak, but because Telerik UI for Blazor&rsquo;s strengths align especially well with the needs of structured, design-system driven, maintainable Blazor development.</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-blazor" class="Btn">Try Telerik UI for Blazor</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:0581803f-f73a-44d1-a241-a01e3c86f27d</id>
    <title type="text">The New Full-Stack Developer: Building Applications for Humans, AI Assistants and Autonomous Agents</title>
    <summary type="text">We’re kicking off a new series, AI Engineering Basics: From Full-stack Developer to AI Engineer. First, we’ll look at how the full-stack role is evolving, clarify the difference between AI-powered apps, assistants and agents, and map out the skills that matter next.</summary>
    <published>2026-08-18T15:44:04Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/new-full-stack-developer-building-applications-humans-ai-assistants-autonomous-agents"/>
    <content type="text"><![CDATA[<p><span class="featured">We&rsquo;re kicking off a new series, AI Engineering Basics: From Full-stack Developer to AI Engineer. First, explore how the full-stack role is evolving and map out the skills that matter next.</span>
</p><p>The full-stack developer role has never stood still. Many of us started out rendering pages on the server, then learned to build single-page applications when the frontend exploded in complexity.</p><p>We picked up <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/REST">REST</a> and <a target="_blank" href="https://graphql.org/">GraphQL</a> as APIs became products of their own. And we absorbed <a target="_blank" href="https://www.docker.com/resources/what-container/">containers</a>, CI/CD and cloud deployments when &ldquo;you build it, you run it&rdquo; became the norm. Each shift added a new layer to the job, and each time the developers who adapted came out ahead.</p><p>AI is the next in this series of shifts, but it&rsquo;s different in an important way. Previous shifts changed <em>how</em> we build applications. This one also changes <em>who</em> (or what) uses them. The applications we build today are increasingly used not just by people clicking through a UI, but by AI assistants acting on a person&rsquo;s behalf and by autonomous agents completing entire workflows on their own.</p><p>That&rsquo;s the core idea behind this series, <a target="_blank" href="https://www.telerik.com/blogs/tag/ai-engineering-basics"><strong>AI Engineering Basics: From Full-stack Developer to AI Engineer</strong></a>. AI isn&rsquo;t replacing application development. It&rsquo;s changing what applications are, who consumes them and what we as developers are responsible for. In this first article, we&rsquo;ll look at how the full-stack role is evolving, clarify the difference between AI-powered apps, assistants and agents, and map out the skills that matter next.</p><h2 id="ai-powered-applications-are-becoming-the-default">AI-Powered Applications Are Becoming the Default</h2><p>Not long ago, adding AI to an application meant one of two things: a recommendation engine built by a specialized machine learning team, or a chatbot bolted onto the corner of the screen. That has changed quickly.</p><p>With large language models (LLMs) available behind simple APIs from providers like <a target="_blank" href="https://platform.openai.com/">OpenAI</a>, <a target="_blank" href="https://www.anthropic.com/">Anthropic</a> and <a target="_blank" href="https://ai.google.dev/">Google</a>, intelligent features are now something any full-stack developer can ship.</p><p>An <strong>AI-powered application</strong> is one where a model participates in the application&rsquo;s core functionality rather than sitting off to the side. Some familiar examples:</p><ul><li><strong>Conversational interfaces</strong> that let users ask questions in natural language instead of navigating menus and filters.</li><li><strong>Retrieval-augmented features</strong> that ground model responses in the company&rsquo;s actual documentation, data or policies (a pattern known as RAG, or Retrieval-Augmented Generation, which we&rsquo;ll cover in depth later in this series).</li><li><strong>Intelligent workflows</strong> that summarize, classify, extract or generate content as part of an existing process, like triaging support tickets or drafting a first pass of a report.</li><li><strong>Adaptive interfaces</strong> that reshape what&rsquo;s on screen based on what the user is trying to accomplish.</li></ul><p>The 2025 Stack Overflow Developer Survey found that <a target="_blank" href="https://survey.stackoverflow.co/2025/ai/#1-ai-tools-in-the-development-process">84% of developers are using or planning to use AI tools in their workflow</a>. The same transformation is happening to the applications we ship. Users who talk to <a target="_blank" href="https://chatgpt.com/">ChatGPT</a> every day increasingly expect the software they use at work to understand natural language, remember context and do more of the work for them.</p><p>For full-stack developers, this is good news. The skills required to build these features (API integration, state management, data modeling, UX judgment) are the skills we already have. What&rsquo;s new is the layer of model-related decisions sitting on top, and that&rsquo;s what the rest of this series is about.</p><h2 id="assistants-vs.-agents">Assistants vs. Agents</h2><p>Two terms come up constantly in this space, and they&rsquo;re often used interchangeably when they shouldn&rsquo;t be: <strong>assistants</strong> and <strong>agents</strong>.</p><p>An <strong>AI assistant</strong> helps a human do their work, one exchange at a time. The human asks, the assistant responds, and the human decides what happens next. A chat interface that answers questions about your product documentation is an assistant. So is the autocomplete in your editor. The defining trait is that a person stays in the loop for every meaningful step.</p><p>An <strong>autonomous agent</strong> is given a goal rather than a question. It plans the steps needed to reach that goal, uses tools to act (calling APIs, querying databases, updating records), evaluates the results and adjusts until the task is done. The human sets the objective and reviews the outcome, but the intermediate steps happen without them.</p><p>The table below summarizes how the two compare:</p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th>Dimension</th><th>AI Assistant</th><th>Autonomous Agent</th></tr></thead><tbody><tr><td><strong>Interaction</strong></td><td>Responds to individual human requests</td><td>Pursues a goal across multiple steps</td></tr><tr><td><strong>Human involvement</strong></td><td>In the loop for every exchange</td><td>Sets the goal, reviews the outcome</td></tr><tr><td><strong>Tool use</strong></td><td>Limited or none</td><td>Central to how it operates</td></tr><tr><td><strong>Scope</strong></td><td>A single answer, suggestion or draft</td><td>An entire workflow or task</td></tr><tr><td><strong>Example</strong></td><td>A docs chatbot, editor autocomplete</td><td>An agent that investigates a bug, writes a fix and opens a pull request</td></tr></tbody></table><br /><p>In practice, the line between the two blurs. Many products start as assistants and gain agent-like capabilities over time, like a support assistant that first only answers questions, then later gains the ability to issue refunds within a set limit.</p><p>Understanding where a feature sits on this spectrum matters, because the engineering demands (tooling, permissions, oversight, error handling) grow significantly as autonomy increases.</p><h2 id="our-users-are-no-longer-only-human">Our Users Are No Longer Only Human</h2><p>The bigger shift, and the one this series is named after, is about who we build for. For the entire history of web development, we&rsquo;ve built applications with one type of consumer in mind: a person looking at a screen. That assumption no longer holds. The applications we build now have three distinct types of consumers:</p><ol><li><strong>Humans</strong>, interacting through the UI the way they always have.</li><li><strong>AI assistants</strong>, acting on a human&rsquo;s behalf: answering questions about the app&rsquo;s data, filling in forms or navigating flows for a user.</li><li><strong>Autonomous agents</strong>, completing tasks end to end: an agent might book the meeting, file the expense report or reorder the inventory without a person touching the interface at all.</li></ol><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/who-we-build-for.webp?sfvrsn=ee00b68_1" height="2880" style="max-width:100%;height:auto;" title="who-we-build-for" width="2880" alt="One application, three consumers: humans, AI assistants, autonomous agents" sf-size="183248" /></p><p>This changes how we design applications in concrete ways. A dropdown menu is self-explanatory to a human, but an agent needs to know what the options mean and what selecting one will do. A multistep wizard guides a person nicely, but an agent would rather skip the screens entirely and perform the operation directly. In other words, agents care less about our UI than about the capability behind it.</p><p>That&rsquo;s the thinking behind <strong>structured actions</strong>: taking an operation that currently lives inside click handlers and form submissions and exposing it as a clearly defined, callable action with a name, a description and typed parameters. The description matters as much as the types, because it&rsquo;s how a model decides when the action applies and what its limits are.</p><p>A structured action for a refund flow might look like this:</p><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
  <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"create_refund"</span><span class="token punctuation">,</span>
  <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Issue a refund for a customer order. Refunds over $500 require human approval."</span><span class="token punctuation">,</span>
  <span class="token string">"parameters"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">"order_id"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"The order to refund"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token string">"amount"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"number"</span><span class="token punctuation">,</span> <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Refund amount in USD"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token string">"reason"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Why the refund is being issued"</span> <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Nothing about this schema is unfamiliar. It&rsquo;s the same discipline we already apply to good API design, applied to a new consumer.</p><p>Notice too that the description encodes a boundary (refunds over $500 require human approval), which points at the other half of designing for agents: permissions. An agent operating with a user&rsquo;s full credentials is a serious liability, so autonomous consumers need scoped access and explicit limits on what they can and cannot do.</p><p>Standards are also emerging to make these connections consistent, most notably the <a target="_blank" href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol (MCP)</a>, which gives models a standardized way to discover and use tools and data sources. We&rsquo;ll dedicate a full article to MCP later in this series.</p><h2 id="from-full-stack-developer-to-ai-engineer">From Full-Stack Developer to AI Engineer</h2><p>Where does all of this leave the full-stack developer? The answer isn&rsquo;t a career change. It&rsquo;s an expanded surface area, the same way frontend developers absorbed build tooling and backend developers absorbed infrastructure. The <strong>AI Engineer</strong> is a full-stack developer who has added a set of model-related responsibilities to their existing ones.</p><p>The encouraging part is that most of these responsibilities build on skills full-stack developers already have:</p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th>Existing skill</th><th>AI-era counterpart</th></tr></thead><tbody><tr><td>API design</td><td>Designing tools and structured actions for models</td></tr><tr><td>Database modeling</td><td>Working with embeddings and vector stores</td></tr><tr><td>Writing tests</td><td>Building evaluations for non-deterministic outputs</td></tr><tr><td>Monitoring and logging</td><td>Observing model behavior, token usage and cost</td></tr><tr><td>Authentication and authorization</td><td>Guardrails and scoped permissions for agents</td></tr><tr><td>UX design</td><td>Designing human-in-the-loop and agent-ready experiences</td></tr></tbody></table><br /><p>Notice what&rsquo;s missing from all of this: training models, designing neural network architectures or anything that requires a research background. AI Engineering is an integration discipline. The models are built by someone else, and our job is to build reliable, trustworthy applications around them.</p><h2 id="whats-ahead-in-this-series">What&rsquo;s Ahead in This Series</h2><p>This article is the starting point for a series that will take these ideas from concepts to working systems. Upcoming articles will cover five broad areas:</p><ul><li><strong>AI foundations</strong>: The modern AI stack (LLMs, embeddings, vector databases, RAG, agents, orchestration), how AI-powered applications are architected and why prompt engineering alone isn&rsquo;t enough.</li><li><strong>AI-powered applications</strong>: Designing AI-native user experiences, building agent-ready interfaces and patterns for human-agent collaboration.</li><li><strong>Agent engineering</strong>: What makes an application agentic, giving agents tools, memory patterns, planning and reasoning, and single-agent vs. multi-agent systems.</li><li><strong>AI operations</strong>: Why most AI demos fail in production, understanding token costs, evaluation, observability, security and governance.</li><li><strong>AI-powered development</strong>: How coding assistants and agents are changing the way we build software itself.</li></ul><h2 id="wrap-up">Wrap-up</h2><p>The full-stack role isn&rsquo;t going anywhere, but it is expanding again.</p><p>The applications we build are gaining intelligent features as a baseline expectation, and they&rsquo;re gaining new consumers: assistants acting on behalf of our users and agents acting on their own. Developers who treat AI as a new layer of the stack to master, the same way we once mastered the frontend, the API layer and the cloud, will be well positioned for what comes next.</p><p>You don&rsquo;t need to become a machine learning researcher to make this transition. You need to understand how models behave, how to connect them to your data and your actions safely and how to apply the engineering judgment you already have to a new class of systems. That&rsquo;s what this series is here to help with, one topic at a time.</p><p>For more on how Progress is approaching AI-powered application and agent development, check out the following resources:</p><ul><li><a href="https://www.telerik.com/ai-engineering" target="_blank">Progress AI Engineering Solutions</a></li><li><a target="_blank" href="https://www.progress.com/">Progress Software: AI-Powered Applications</a></li><li><a target="_blank" href="https://www.progress.com/agentic-rag">Progress Agentic RAG</a></li></ul>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:147ede5a-1142-489e-9a4b-a4a105b2b5ed</id>
    <title type="text">Useful JavaScript Additions in ECMAScript 2026</title>
    <summary type="text">The useful part of the ECMAScript 2026 standard for JavaScript developers is not a dramatic change to the language. It is that several common use cases now have direct names.</summary>
    <published>2026-08-17T16:34:58Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Peter Mbanugo </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/useful-javascript-additions-ecmascript-2026"/>
    <content type="text"><![CDATA[<p><span class="featured">The useful part of the ECMAScript 2026 standard for JavaScript developers is not a dramatic change to the language. It is that several common use cases now have direct names.</span></p><p>JavaScript gets a new language standard every year. Some editions introduce syntax that changes how programs are written or how they perform. The <a target="_blank" href="https://ecma-international.org/publications-and-standards/standards/ecma-262/">ECMAScript 2026</a> (ES17) version brings a handful of useful features which we will look at. These features add focused APIs for tasks that JavaScript developers already handle with small helpers, repeated checks or easy-to-miss workarounds.</p><p>In this article, I will focus on additions that can remove code, make intent clearer or prevent subtle errors.</p><blockquote><p><strong>Runtime support:</strong> These APIs did not arrive in every runtime at the same time. Check the <strong>Browser compatibility</strong> table on each linked MDN page for the browsers and runtimes you support. Use a tested fallback when an API is unavailable.</p></blockquote><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/useful-javascript-additions-in-ecmascript-2026.png?sfvrsn=c4d86b5a_2" height="941" style="max-width:100%;height:auto;" title="Useful JavaScript Additions in ECMAScript 2026" width="1672" alt="Useful JavaScript Additions in ECMAScript 2026" sf-size="1499143" /><br /><span style="font-size:11px;">Image generated with AI</span></p><h2 id="build-a-map-as-values-arrive">Build a Map as Values Arrive</h2><p>In <a target="_blank" href="https://pmbanugo.me/blog/array-grouping-in-javascript">my article about array grouping</a>, I showed a simple way to group related things using <code>Object.groupBy()</code> and <code>Map.groupBy()</code>. Both methods begin with a collection that already exists, but what if you want to do the same for streaming data?</p><p>For example, this code groups employees from an asynchronous stream:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> employeesByDepartment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> employee <span class="token keyword">of</span> employeeStream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>employeesByDepartment<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    employeesByDepartment<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  employeesByDepartment
    <span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>employee<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>There is nothing complicated here, but the <code>has()</code>, <code>set()</code> and <code>get()</code> sequence is boilerplate code around the operation we actually care about&mdash;adding a new employee to a department.</p><p>ECMAScript 2026 adds <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsert"><code>Map.prototype.getOrInsert()</code></a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsertComputed"><code>Map.prototype.getOrInsertComputed()</code></a>. These methods return the value corresponding to the specified key. If not present, it inserts a new entry with the key and a given default value, and returns the inserted value.</p><p>You should use <code>getOrInsertComputed()</code> whenever the default should be created lazily, particularly when creating it is expensive, has side effects or allocates a mutable object such as an array. With the computed version, the code from before becomes:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> employeesByDepartment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> employee <span class="token keyword">of</span> employeeStream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  employeesByDepartment
    <span class="token punctuation">.</span><span class="token function">getOrInsertComputed</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>employee<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The result is simple and compact.</p><h2 id="collect-an-asynchronous-iterable-into-an-array">Collect an Asynchronous Iterable into an Array</h2><p>An asynchronous generator is a useful way to hide pagination. Its caller can consume one sequence without knowing where one response page ends and the next begins. For example:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">async</span> <span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">while</span> <span class="token punctuation">(</span>url<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Request failed: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>response<span class="token punctuation">.</span>status<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> page <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">yield</span><span class="token operator">*</span> page<span class="token punctuation">.</span>items<span class="token punctuation">;</span>
    url <span class="token operator">=</span> page<span class="token punctuation">.</span>next<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>To collect every result into an array, you would normally write a loop:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> issues <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> issue <span class="token keyword">of</span> <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span><span class="token string">"/api/issues"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  issues<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>issue<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync"><code>Array.fromAsync()</code> method</a> can be used to perform that collection directly:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> issues <span class="token operator">=</span> <span class="token keyword">await</span> Array<span class="token punctuation">.</span><span class="token function">fromAsync</span><span class="token punctuation">(</span>
  <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span><span class="token string">"/api/issues"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Isn&rsquo;t that shorter and simpler?</p><p>Despite its name, the method also accepts synchronous iterables and array-like objects. It awaits values from those sources one at a time.</p><p>Array.fromAsync() also accepts a mapping function, and the runtime waits for the mapped result before reading the next value. For example, the same code can be adjusted to return only the issue title using the map function:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> issueTitles <span class="token operator">=</span> <span class="token keyword">await</span> Array<span class="token punctuation">.</span><span class="token function">fromAsync</span><span class="token punctuation">(</span>
  <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span><span class="token string">"/api/issues"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">(</span>issue<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> issue<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Given that example, it might be misleading to think that <code>Array.fromAsync()</code> runs independent operations concurrently. For example:</p><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// The requests start one after another.</span>
<span class="token keyword">const</span> sequential <span class="token operator">=</span> <span class="token keyword">await</span> Array<span class="token punctuation">.</span><span class="token function">fromAsync</span><span class="token punctuation">(</span>urls<span class="token punctuation">,</span> fetchJson<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// All requests are initiated before any of them is awaited.</span>
<span class="token keyword">const</span> concurrent <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>urls<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>fetchJson<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You should use <code>Promise.all()</code> when independent operations can run at the same time. And reach for <code>Array.fromAsync()</code> when the source or mapping step may be asynchronous, and you want lazy, ordered consumption. Also remember what the method returns&mdash;an array containing every result. Keep the <code>for await...of</code> loop when you want to process a large stream incrementally and avoid collecting a stream that may never end.</p><h2 id="combine-iterables-into-one-lazy-sequence">Combine Iterables into One Lazy Sequence</h2><p>Sometimes several iterable sources should behave like one continuous sequence. Suppose an application checks its built-in routes first, then registered routes by plugins and finally a catch-all route. You could spread everything into a new array, but that would eagerly consume each iterable and allocate another collection.</p><p>A generator can keep the sequence lazy:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">allRoutes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">yield</span><span class="token operator">*</span> builtInRoutes<span class="token punctuation">;</span>
  <span class="token keyword">yield</span><span class="token operator">*</span> pluginRoutes<span class="token punctuation">;</span>
  <span class="token keyword">yield</span> fallbackRoute<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/concat"><code>Iterator.concat()</code></a>, on the other hand, expresses the same operation without the custom generator:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> allRoutes <span class="token operator">=</span> Iterator<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>
  builtInRoutes<span class="token punctuation">,</span>
  pluginRoutes<span class="token punctuation">,</span>
  <span class="token punctuation">[</span>fallbackRoute<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The result yields the built-in routes first, followed by the plugin routes and then the fallback. Values are pulled only as the consumer advances the iterator; <code>Iterator.concat()</code> does not collect them into a new array first. Each argument must be an iterable object, which is why the example wraps <code>fallbackRoute</code> in an array.</p><p>The <code>Iterator.concat()</code>method works with synchronous iterables only. It does not combine asynchronous iterables.</p><h2 id="preserve-large-integers-in-json">Preserve Large Integers in JSON</h2><p>Chat platforms often hand out snowflake IDs as part of a message or other kinds of data. These are commonly 64-bit integers and can exceed <code>Number.MAX_SAFE_INTEGER</code>, so parsing them as JSON numbers can silently lose precision. Consider this response:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token string">`{
  "messageId": 1183028002140618753,
  "channel": "general"
}`</span></span><span class="token punctuation">;</span>

<span class="token keyword">const</span> event <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>messageId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 1183028002140618800</span>
</code></pre><p>The logged value isn&rsquo;t the value in the JSON text.</p><p>ECMAScript 2026 gives the <code>JSON.parse()</code> reviver function a third argument, named <code>context</code>. When the value is an unmodified primitive from the parser, <code>context.source</code> contains the original JSON text. We can use that to parse and convert the text to the proper type, in this case <code>BigInt</code>.</p><p>Here&rsquo;s the sample from earlier, rewritten using the reviver function:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> event <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>
  payload<span class="token punctuation">,</span>
  <span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">===</span> <span class="token string">"messageId"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>context<span class="token punctuation">.</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> value<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>messageId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 1183028002140618753n</span>
</code></pre><p>By the time the reviver receives <code>value</code>, the <code>Number</code> has already lost precision. However, <code>context.source</code> lets the code ignore that damaged value and build a <code>BigInt</code> from the original digits instead.</p><p>Serialization has the opposite problem: <code>JSON.stringify()</code> throws when it reaches a <code>BigInt</code>, unless you handle the value. The new <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/rawJSON"><code>JSON.rawJSON()</code></a> method lets a replacer function for <code>JSON.stringify</code> provide valid JSON text for a primitive value. The <code>JSON.rawJSON()</code> method creates a &ldquo;raw JSON&rdquo; object containing JSON text.</p><p>Here&rsquo;s an example using <code>JSON.stringify()</code> and <code>JSON.rawJSON()</code> together:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> json <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>
  event<span class="token punctuation">,</span>
  <span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">typeof</span> value <span class="token operator">===</span> <span class="token string">"bigint"</span>
      <span class="token operator">?</span> JSON<span class="token punctuation">.</span><span class="token function">rawJSON</span><span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">:</span> value<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// {"messageId":1183028002140618753,"channel":"general"}</span>
</code></pre><p>Used together, the two APIs let this program recover the original digits as a <code>BigInt</code> and serialize those digits back into JSON without rounding them.</p><p>Using those APIs doesn&rsquo;t mean you should turn every integer into a <code>BigInt</code>. A count, price and database identifier may all appear as JSON numbers, but they do not necessarily belong in the same underlying JavaScript type. That said, it is important to remember that the parser exposes <code>context.source</code> only for unmodified primitive values, and <code>JSON.rawJSON()</code> accepts only valid JSON text representing a primitive value.</p><h2 id="convert-bytes-to-and-from-base64-or-hex">Convert Bytes to and from Base64 or Hex</h2><p>Binary data has often taken an unnecessary detour through strings. Converting to and from bytes wasn&rsquo;t a natural interface in the language. I think Bun was the first JS runtime I used that had a built-in API for converting bytes to various data types. Fortunately, the 2026 ECMAScript standard release brings the following functions for converting bytes:</p><ul><li>Uint8Array.prototype.toBase64</li><li>Uint8Array.prototype.toHex</li><li>Uint8Array.prototype.setFromHex and its static form Uint8Array.fromHex</li><li>Uint8Array.prototype.setFromBase64 and its static form Uint8Array.fromBase64</li></ul><p>How are they useful, you may ask?</p><p>Imagine you want to create a URL-safe token. The code might look like this:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> bytes <span class="token operator">=</span> crypto<span class="token punctuation">.</span><span class="token function">getRandomValues</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> token <span class="token operator">=</span> <span class="token function">btoa</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token function">fromCharCode</span><span class="token punctuation">(</span><span class="token operator">...</span>bytes<span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"+"</span><span class="token punctuation">,</span> <span class="token string">"-"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> <span class="token string">"_"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/=+$/</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The program starts with bytes, turns them into a temporary string, encodes that string and then adjusts the alphabet and padding. It works, but none of those intermediate steps express the real task: encode these bytes as base64url.</p><p>We can use the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64"><code>Uint8Array.prototype.toBase64()</code></a> method to do the conversion instead:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> bytes <span class="token operator">=</span> crypto<span class="token punctuation">.</span><span class="token function">getRandomValues</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> token <span class="token operator">=</span> bytes<span class="token punctuation">.</span><span class="token function">toBase64</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  alphabet<span class="token punctuation">:</span> <span class="token string">"base64url"</span><span class="token punctuation">,</span>
  omitPadding<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The reverse conversion is also as simple as:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> decoded <span class="token operator">=</span> Uint8Array<span class="token punctuation">.</span><span class="token function">fromBase64</span><span class="token punctuation">(</span>token<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  alphabet<span class="token punctuation">:</span> <span class="token string">"base64url"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The <code>setFromBase64()</code> and <code>setFromHex()</code> methods write into an existing array and return an object with <code>read</code> and <code>written</code> counts. Unlike <code>fromBase64()</code> and <code>fromHex()</code>, they are useful when you need to control memory allocation, decode into a preallocated buffer or track how much input fits. See the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64">docs for <code>Uint8Array.fromBase64()</code></a> to learn about the available input options and runtime support.</p><p>These methods do not replace <code>TextEncoder</code> or <code>TextDecoder</code>. Use those APIs to convert between text and bytes; use the new <code>Uint8Array</code> methods to convert between bytes and base64 or hexadecimal representations.</p><h2 id="recognize-error-objects-across-realms">Recognize Error Objects Across Realms</h2><p>The <code>instanceof Error</code> looks like the obvious way to check if an object is an <code>Error</code>. It stops being reliable when the value comes from another JavaScript realm, e.g., an iframe or the Node.js <code>vm</code> context. Each realm has its own <code>Error</code> constructor, so a genuine error from another realm can fail an <code>instanceof</code> check.</p><p>Try this in the browser console:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> iframe <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"iframe"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
document<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>iframe<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> otherError <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">iframe<span class="token punctuation">.</span>contentWindow<span class="token punctuation">.</span>Error</span><span class="token punctuation">(</span><span class="token string">"Failure"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>otherError <span class="token keyword">instanceof</span> <span class="token class-name">Error</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// false</span>
</code></pre><p>This may not surprise many experienced JavaScript programmers who have been deceived by some JavaScript quirkiness.</p><p>The solution is to use the new <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError"><code>Error.isError()</code></a>. <code>Error.isError()</code> performs a built-in check by testing for the internal <code>[[ErrorData]]</code> slot instead of relying on the current realm&rsquo;s prototype chain. This makes it analogous to <code>Array.isArray()</code> as a reliable cross-realm check.</p><p>If you append <code>console.log(Error.isError(otherError))</code> to the previous code snippet you ran in your browser console, you should see the correct result.</p><p>The method is also useful in a <code>catch</code> block because JavaScript allows any value to be thrown:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">try</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> <span class="token function">runPlugin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> error <span class="token operator">=</span> Error<span class="token punctuation">.</span><span class="token function">isError</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    <span class="token operator">?</span> value
    <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token function">String</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> cause<span class="token punctuation">:</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">reportError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>You should use <code>Error.isError()</code> when you need to know whether a value is a real Error object. It deliberately does not treat a plain object with <code>name</code> and <code>message</code> properties as one.</p><h2 id="sum-floating-point-values-more-accurately">Sum Floating-point Values More Accurately</h2><p>A straightforward <code>reduce()</code> can lose information while adding floating-point values, and the failure mode is sneakier than you&rsquo;d expect, because the result doesn&rsquo;t always look obviously wrong. Consider a motion-sensor library that applies a large per-device calibration offset, adds a small reading, then removes the offset again:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> readings <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1e16</span><span class="token punctuation">,</span> <span class="token number">3.5</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1e16</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> total <span class="token operator">=</span> readings<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span>sum<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> value<span class="token punctuation">,</span>
  <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>total<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 4</span>
</code></pre><p>The correct answer is <code>3.5</code>&mdash;that&rsquo;s the actual reading once the offset cancels out. Near <code>1e16</code>, adjacent representable numbers are two units apart. The exact value <code>1e16 + 3.5</code> therefore rounds to <code>1e16 + 4</code>, and subtracting the offset leaves 4 instead of 3.5. The result isn&rsquo;t a crash but a plausible-looking wrong number, which is what makes this type of bug easy to miss in code review.</p><p>That&rsquo;s where <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sumPrecise"><code>Math.sumPrecise()</code></a> comes in. It uses a more accurate summation algorithm, so switching from <code>.reduce()</code> to <code>Math.sumPrecise()</code> gets you the correct answer.</p><p>Here&rsquo;s an accurate way to rewrite it:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> total <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">sumPrecise</span><span class="token punctuation">(</span>readings<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>total<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 3.5</span>
</code></pre><p>The <code>Math.sumPrecise()</code> method accepts an iterable of numbers. It does not coerce strings or <code>BigInt</code> values into numbers. An empty iterable, or one containing only <code>-0</code>, returns <code>-0</code>. The name deserves one warning though, because <code>Precise</code> does not mean decimal arithmetic precision. This <em>familiar</em> result does not change:</p><pre class=" language-js"><code class="prism  language-js">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">sumPrecise</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span> <span class="token number">0.2</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 0.30000000000000004</span>
</code></pre><p>Both inputs are already binary floating-point approximations. <code>Math.sumPrecise()</code> reduces the additional error introduced while summing them; it does not change how JavaScript represents numbers. That makes it useful for numerical aggregation, but not a complete solution for money. Use an appropriate decimal type or an integer representation for financial values.</p><h2 id="thats-a-wrap">That&rsquo;s a Wrap</h2><p>The useful part of the ECMAScript 2026 standard is not a dramatic change to the language. It is that several common use cases now have direct names: get a Map value or create it, preserve the original digits from JSON, encode bytes without pretending they are text, collect an asynchronous sequence, recognize a real Error, sum numbers with less loss and join iterables lazily. None of these APIs will transform an application on its own. They can, however, replace code that is easy to repeat (boilerplate code), easy to get slightly wrong or harder to understand than the operation it performs.</p><p>That is why JavaScript has become nicer to use!</p><p>Make sure to check runtime support before using these additions in production. The standard defines the language, but every runtime follows its own release schedule. You can get the source code for some of the examples on <a target="_blank" href="https://github.com/pmbanugo/es2026-samples-and-verifier">GitHub</a>.</p><h2 id="further-reading">Further Reading</h2><ul><li><a target="_blank" href="https://ecma-international.org/publications-and-standards/standards/ecma-262/">ECMAScript 2026 language specification</a></li><li><a target="_blank" href="https://github.com/tc39/proposals/blob/cae61138d3872cb9748effe04b729b7152f71369/finished-proposals.md">TC39 finished proposals at the ECMAScript 2026 cutoff</a></li></ul><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">AI Can&rsquo;t Solve It All: What Frontend Devs Still Hate Working On</h4></div><div class="col-8"><p class="u-fs16 u-mb0">What still causes the most friction when building modern web applications? <a target="_blank" href="https://www.telerik.com/blogs/ai-cant-solve-all-what-120-frontend-developers-say-they-still-hate-working">120+ developers at JSNation and React Summit weigh in.</a></p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9d3005ca-66d0-4da4-b38a-8d3fc76666d1</id>
    <title type="text">The AI Agent Ate My Budget! Here Is What I Should Have Built Instead</title>
    <summary type="text">Why deterministic automation may be the wisest investment you make before the token economy catches up with your credit card.</summary>
    <published>2026-08-17T15:24:28Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Jefferson S. Motta </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/ai-agent-ate-budget-what-should-have-built-instead"/>
    <content type="text"><![CDATA[<p><span class="featured">Why deterministic automation may be the wisest investment you make before the token economy catches up with your credit card.</span></p><p>In this post, I want to talk about a choice most teams are making without realizing it: delegating work to AI agents when they should be automating it deterministically, and the financial cliff that choice creates.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/ai-ate-my-budget.png?sfvrsn=aa5a791a_2" height="639" style="max-width:100%;height:auto;" title="ai-ate-my-budget" width="601" alt="AI Ate My Budget - money flying away" sf-size="983167" /><br /><span style="font-size:11px;">Image generated with AI</span></p><p>We are leaving the golden age of cheap tokens. The era where companies handed AI a blank check and called it innovation is ending. Reports across the industry tell the same story: budgets consumed in months instead of years, teams scrambling to explain runaway costs and executives quietly asking whether the return ever justified the spending. AI agents, the ones that loop, retry, reason and call themselves back, turned out to be remarkably efficient at one thing: burning through tokens.</p><p>We talk about the current AI cycle resembling a bubble. And although it has not fully burst yet, the air is leaving. What remains will be expensive. The professionals who built their entire workflow on token-hungry agents will be the first to feel the squeeze.</p><h2 id="the-difference-nobody-talks-about">The Difference Nobody Talks About</h2><p>There is a fundamental distinction between the two approaches that the industry keeps conflating into a single term.</p><p>An <strong>AI agent</strong> is a system that receives a goal, reasons about it, makes decisions at runtime and consumes tokens with every step. It is powerful. It is also unpredictable in cost because you are paying for the machine to think every time it runs.</p><p><strong>Deterministic automation</strong> is a system that analyzes a known structure, applies fixed rules and produces a predictable output, without tokens or runtime reasoning. That will reduce the surprises on your invoice.</p><p>Both use intelligence. One front-loads it. The intelligence lives in the design, not in the execution. I call it <em>deterministic AI</em>: a system shaped by years of architectural thinking, where the machine does not need to reason because the reasoning already happened when you built it.</p><blockquote><p><strong><em>The most intelligent system is the one that does not need to think at runtime.</em></strong></p></blockquote><h2 id="what-i-built-instead">What I Built Instead</h2><p>I spent years building a platform that generates entire SaaS applications from a data structure. Not a prototype, and not a scaffold. A complete, production-grade system: backend in .NET, frontend in React, data-first, mobile-first, unit test coverage on both sides, observability baked in with Prometheus, Loki and Tempo, and ready-made Grafana dashboard templates. Roughly 60 files per entity, generated deterministically from the shape of your data.</p><p>When a client asked me for a Micro CRM to be delivered overnight, I did not open an AI chat and start prompting. I ran my automation. In under eight hours, including three hours of database design, I made it myself. It was done. Backend, frontend, tests, monitoring. Everything in less than eight hours.</p><p>And with zero tokens consumed. (That also cuts unpredictable costs.) The result was identical to what I would have delivered if I had spent a week writing it by hand, because the automation encodes years of decisions I already made.</p><p>This is not anti-AI. This is <em>anti-waste</em>.</p><h2 id="where-ai-still-earns-its-place">Where AI Still Earns Its Place</h2><p>I am not arguing against AI. I use it. But I use it surgically, not as a crutch.</p><p>My automation already had a structure designed around C# interfaces, built specifically so that an AI model could understand and navigate the codebase. When I needed to expand unit test coverage, I pointed an AI coding assistant at the existing tests and had it generate new ones that followed the same patterns. I ran it for about six hours over two days. The cost was around $20 in tokens.</p><p>Twenty dollars. A manageable, traceable cost for a well-scoped task. But here is the part that matters: I had set a spending limit. Without it, that same task could have spiraled into hundreds, maybe thousands. And that, I believe, is exactly where companies bleeding money on AI are failing: not in choosing to use it, but in using it without boundaries.</p><p>I also explored running a custom LLM locally using Ollama and integrating it with my coding environment. It offers unlimited token usage at zero marginal cost, but it demands serious hardware: a GPU with at least 16 or 20 GB of VRAM. For teams that can invest in the infrastructure, it is a path worth exploring, one that decouples your productivity from someone else&rsquo;s pricing model. <a target="_blank" href="https://medium.com/@jsmotta3000/2a906b815b56">Here is a tutorial about my journey implementing custom LLM on Cursor.</a></p><blockquote><p><strong><em>Use AI where it multiplies your intelligence. Automate where it would only repeat it.</em></strong></p></blockquote><h2 id="build-the-machine-that-does-not-need-the-machine">Build the Machine That Does Not Need the Machine</h2><p>There is a deeper lesson here, and it goes beyond cost optimization.</p><p>If your entire delivery pipeline depends on an external AI service to function, you have a single point of failure priced by someone else. Token prices go up. APIs go down. Models get deprecated. Rate limits tighten. And when any of that happens, your ability to deliver stops with it.</p><p>Deterministic automation is yours. You own it. It runs on your terms. It does not get more expensive overnight. And it does not hallucinate.</p><p>The architect who invests in automation today is building a shelter for when the token economy changes, and it will change. The one who delegates everything to agents is building on rented land.</p><blockquote><p><strong><em>Automate with AI. But never depend on AI to execute.</em></strong></p></blockquote><h2 id="conclusion">Conclusion</h2><p>We are at an inflection point. The decisions architects and developers make right now, about what to automate deterministically and what to delegate to AI agents, will define the resilience and economics of their products for years to come.</p><p>The question is not whether AI is useful. It is. The question is whether you are building something that survives when the cost of thinking doubles.</p><blockquote><p><strong><em>So before you spin up another agent, ask yourself: could this be a machine that already knows the answer?</em></strong></p></blockquote><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">The Six Questions Every AI Trace Should Be Able to Answer</h4></div><div class="col-8"><p class="u-fs16 u-mb0">To be useful, an <a target="_blank" href="https://www.telerik.com/blogs/six-questions-every-ai-trace-should-answer">AI trace should explain what the agent decided</a>, what data it used, what it cost or whether the output was any good. Progress AI Observability Platform does this for teams building in Python, .NET and JavaScript/TypeScript.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:4ede9294-b69b-4d98-9b2d-bffd4b057997</id>
    <title type="text">Keep Building Without Getting Paid</title>
    <summary type="text">The best developers don’t stop building when they close the laptop. In an age of agents, your hobby might be the most irreplaceable thing about you.</summary>
    <published>2026-08-14T16:03:42Z</published>
    <updated>2026-08-27T13:35:57Z</updated>
    <author>
      <name>Teon Beijl </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/keep-building-without-getting-paid"/>
    <content type="text"><![CDATA[<p><span class="featured">The best developers don&rsquo;t stop building when they close the laptop. In an age of agents, your hobby might be the most irreplaceable thing about you.</span></p><p>Have you ever heard of Pan de Jam&oacute;n? I hadn&rsquo;t&mdash;until I met Mike, one of the best developers I ever worked with.</p><p>This isn&rsquo;t a &ldquo;here&rsquo;s what bread taught me about life&rdquo; post. It&rsquo;s an observation that might bring some light to an AI outlook that can feel pretty dark sometimes.</p><p>What made Mike stand out wasn&rsquo;t only his personality or skillset. It was that he went home and baked Venezuelan Christmas bread.</p><h2 id="ode-to-the-hobbyist">Ode to the Hobbyist</h2><p>And it wasn&rsquo;t only Mike. Most developers I remember, I remember by their hobby.</p><p>Just off the top of my mind:</p><ul><li>The genius printing parts on a 3D printer he built himself</li><li>The audiophile building a sound system for a perfect pitch</li><li>The home-automation engineer who wired his house with PLCs</li><li>The architect drilling a water well in his own backyard</li><li>The rotary-engine-obsessed designer with a Mazda RX-7</li><li>The 3D specialist restoring a surf van with his son</li></ul><p>None of this is on their LinkedIn profile. None of it pays. And it&rsquo;s the most interesting thing about them.</p><p>I&rsquo;ve spent a long time in enterprise software, working with developers who write code that runs critical infrastructure.</p><p>The pattern is so consistent it stopped being a pattern and started looking like a characteristic: The best developers I&rsquo;ve worked with don&rsquo;t stop building when they close the laptop. They go home and make something else&mdash;bread, sound, water, vans. They just stop getting paid for it.</p><h2 id="what-agents-dont-have">What Agents Don&rsquo;t Have</h2><p>We live in interesting times. Agents are now colleagues that cooperate in writing our code. And companies are considering, or actively pursuing, what that means for headcount. More and more developers are asking: &ldquo;Will there be a job for me? How do I compete?&rdquo;</p><p>But here&rsquo;s what agents don&rsquo;t do. They don&rsquo;t drive home and pick up a task that&rsquo;s not on a kanban board. They don&rsquo;t lose a Saturday to a bad weld or experiment with different kinds of dough and oven temperatures. They don&rsquo;t bake a loaf that fails three weekends in a row and quietly absorb the lesson that some processes can&rsquo;t be hurried, no matter how clever the idea. They don&rsquo;t ferment, restore, dig, solder, role-play or rebuild.</p><p>Agents have no Saturday. They have no irrational, over-obsessed curiosity we call a hobby.</p><p>Hobbies are unpressured, unstructured, unscheduled&mdash;messy mindbenders. That&rsquo;s what agents miss. And this unpaid labor is where a lot of real learning happens. The kind that doesn&rsquo;t show up in the sprint demo, but shows up in great architecture decisions, clever unit tests and surprising solutions.</p><h2 id="dont-give-up-on-your-hobbies">Don&rsquo;t Give Up on Your Hobbies</h2><p>If you&rsquo;re a developer wondering what to do with the noise about AI replacing you, here&rsquo;s an honest answer: <strong>Look beyond the code. Lean harder into the things you make that nobody pays you for.</strong></p><p>Your hobby isn&rsquo;t a side-quest to the main game of being a developer. It&rsquo;s not only about balance, self-care or free time. It&rsquo;s where you keep growing the part of yourself that an agent can&rsquo;t copy. Where character is formed. Where unconstrained thinking is allowed and you explore solutions only you have to justify.</p><p>That wonderful waste of a Saturday is the perspective that breaks through on a mundane Monday morning.</p><p>The coffee chat &ldquo;what did you do over the weekend?&rdquo; might be the context that wins over the agent whose most coffee-related moment is running a brew command in the terminal.</p><p>So bake the bread. Dig the well. Restore the van. Roll the dice.</p><p>Keep building without getting paid. That&rsquo;s the work that keeps you irreplaceable.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Question Everything: Why the Question Matters More Than the Answer</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/question-everything-why-question-matters-more-than-answer">Questions unlock better solutions.</a> &ldquo;How might we&rdquo; is a powerful divergent thinking tool, especially when AI defaults to answers.</p></div></div></aside>]]></content>
  </entry>
</feed>
