{"page":1,"limit":20,"total":102,"totalPages":6,"todayTotal":0,"data":[{"source":"https://dev.to/feed/tag/typescript","sourceHost":"dev.to","title":"OpenLiDARViewer: A Browser-Based LiDAR and Point-Cloud Viewer","link":"https://dev.to/aurtechmx/building-openlidarviewer-a-browser-based-lidar-and-point-cloud-viewer-3dcl","pubDate":"Fri, 22 May 2026 23:08:31 +0000","description":"<h1>\n  \n  \n  Rendering LiDAR Scans in the Browser Without Uploading Anything\n</h1>\n\n<p>Most point-cloud workflows still start the same way.</p>\n\n<p>Install a desktop tool.<br>\nImport the scan.<br>\nWait.<br>\nInspect.<br>\nMeasure.<br>\nExport.</p>\n\n<p>That workflow makes sense when you are doing serious GIS, photogrammetry, survey processing, classification, or production work.</p>\n\n<p>But there is another moment that is much simpler:</p>\n\n<p>You just received a scan and want to know what is inside it.</p>\n\n<p>Does it open?<br>\nIs it clean?<br>\nDoes it have color, intensity, or classification?<br>\nCan I measure something quickly?<br>\nCan I show it to someone without making them install software?</p>\n\n<p>That is the moment I wanted to improve with <strong>OpenLiDARViewer</strong>.</p>\n\n<p>It is an open-source, browser-based LiDAR and point-cloud viewer built around one simple idea:</p>\n\n<p><strong>Drop a scan into the browser and inspect it locally.</strong></p>\n\n<p>No upload.<br>\nNo account.<br>\nNo desktop install.<br>\nNo conversion step for supported formats.</p>\n\n<p>Live demo: <a href=\"https://lidar.aurtech.mx/\" rel=\"noopener noreferrer\">https://lidar.aurtech.mx/</a><br>\nGitHub: <a href=\"https://github.com/Aurtechmx/openlidarviewer/\" rel=\"noopener noreferrer\">https://github.com/Aurtechmx/openlidarviewer/</a></p>\n\n\n\n\n<h2>\n  \n  \n  The real goal: own the first 60 seconds\n</h2>\n\n<p>There are already great tools for point-cloud work.</p>\n\n<p>CloudCompare is powerful.<br>\nQGIS is powerful.<br>\nPotree is excellent for publishing point clouds on the web.<br>\nProfessional LiDAR software exists for a reason.</p>\n\n<p>OpenLiDARViewer is not trying to replace those tools.</p>\n\n<p>It is focused on a smaller, earlier step:</p>\n\n<p><strong>the first 60 seconds after you get a scan.</strong></p>\n\n<p>Before you process it.<br>\nBefore you classify it.<br>\nBefore you prepare a report.<br>\nBefore you decide what workflow it belongs to.</p>\n\n<p>Sometimes you just need a fast first look.</p>\n\n<p>That sounds simple, but LiDAR data makes “just open it” surprisingly complicated.</p>\n\n\n\n\n<h2>\n  \n  \n  Keeping the scan on the device\n</h2>\n\n<p>One thing I wanted from the start was simple:</p>\n\n<p><strong>The scan should not have to leave the user’s machine just to be inspected.</strong></p>\n\n<p>For LiDAR and 3D scan data, that matters.</p>\n\n<p>A scan can represent a private site, a client project, a building, a terrain model, an industrial asset, or a research dataset. Uploading it to a random cloud viewer just to take a quick look is not always acceptable.</p>\n\n<p>So OpenLiDARViewer runs client-side.</p>\n\n<p>The browser loads the app, but the scan itself is read, parsed, analyzed, and rendered locally.</p>\n\n<p>There is no backend parser.<br>\nNo server-side preprocessing.<br>\nNo cloud ingestion pipeline.<br>\nNo “upload your file and wait.”</p>\n\n<p>Just the browser, the GPU, and the file on your machine.</p>\n\n\n\n\n<h2>\n  \n  \n  Supporting real-world scan formats\n</h2>\n\n<p>The viewer now supports common point-cloud and scan formats such as:</p>\n\n<ul>\n<li>LAS</li>\n<li>LAZ</li>\n<li>E57</li>\n<li>PLY</li>\n<li>OBJ</li>\n<li>GLB / GLTF</li>\n<li>XYZ</li>\n<li>CSV</li>\n</ul>\n\n<p>That mix is intentional.</p>\n\n<p>I wanted the same viewer to handle both:</p>\n\n<ul>\n<li>drone / aerial LiDAR</li>\n<li>phone and mobile 3D scans</li>\n</ul>\n\n<p>Those worlds often feel separate, but in practice they are starting to overlap. People capture data from drones, phones, scanners, photogrammetry tools, and AR apps. A lightweight viewer should not care where the scan came from as long as the data can be parsed and rendered.</p>\n\n<p>E57 support was especially important because it is a serious professional exchange format. It also brings more complexity: metadata, scan structure, transforms, optional attributes, and vendor differences.</p>\n\n<p>Getting that kind of file working in the browser is not just a checkbox. It is an interoperability milestone.</p>\n\n\n\n\n<h2>\n  \n  \n  Measurement had to become more than a demo feature\n</h2>\n\n<p>The first measurement tool was simple: pick two points and get a distance.</p>\n\n<p>Useful, but limited.</p>\n\n<p>The newer measurement workflow is more practical. It includes:</p>\n\n<ul>\n<li>distance</li>\n<li>polyline</li>\n<li>area</li>\n<li>height</li>\n<li>angle</li>\n<li>slope</li>\n<li>unit switching</li>\n<li>editable points</li>\n<li>clearer measurement sessions</li>\n</ul>\n\n<p>It does make it more useful for quick inspection.</p>\n\n\n\n\n<h2>\n  \n  \n  Scan intelligence: not just seeing the cloud\n</h2>\n\n<p>A point cloud viewer should do more than show dots.</p>\n\n<p>When I open a scan, I want quick answers:</p>\n\n<ul>\n<li>How many points are in this file?</li>\n<li>What is the extent?</li>\n<li>What is the approximate density?</li>\n<li>Does it have RGB?</li>\n<li>Does it have intensity?</li>\n<li>Does it have classification?</li>\n<li>Are there invalid coordinates?</li>\n<li>Are there suspicious outliers?</li>\n<li>Does the decoded data match what the file claims?</li>\n</ul>\n\n<p>That is why OpenLiDARViewer includes scan intelligence and validation modules.</p>\n\n<p>The goal is not to replace professional QA/QC.</p>\n\n<p>The goal is to give a useful first read before committing to deeper processing.</p>\n\n<p>Seeing the scan matters.</p>\n\n<p>Understanding whether the scan looks intact matters too.</p>\n\n\n\n\n<h2>\n  \n  \n  Why the interface is more game-like than GIS-like\n</h2>\n\n<p>A lot of spatial tools inherit GIS interaction patterns.</p>\n\n<p>That is fine for GIS users, but it can be intimidating for people who just want to move through a 3D scan.</p>\n\n<p>OpenLiDARViewer uses a more direct navigation model:</p>\n\n<ul>\n<li>Orbit mode</li>\n<li>Walk mode</li>\n<li>Fly mode</li>\n<li>WASD movement</li>\n<li>mouse-look</li>\n<li>touch support on mobile</li>\n<li>point inspection</li>\n<li>saved viewpoints</li>\n</ul>\n\n<p>The goal is to make the scan feel like a space you can enter, not just a dataset you loaded.</p>\n\n<p>Orbit works well for objects and small sites.<br>\nWalk makes more sense for interiors or street-level scans.<br>\nFly feels better for terrain, drone LiDAR, and wide-area data.</p>\n\n<p>This is one of the parts I care about most because good interaction design can make technical data much easier to understand.</p>\n\n\n\n\n<h2>\n  \n  \n  Mobile was not optional\n</h2>\n\n<p>At first, this kind of tool feels desktop-first.</p>\n\n<p>Then reality shows up.</p>\n\n<p>People want to open scans:</p>\n\n<ul>\n<li>on site</li>\n<li>on tablets</li>\n<li>on phones</li>\n<li>during a demo</li>\n<li>while moving between field and office workflows</li>\n</ul>\n\n<p>Mobile support creates its own set of problems:</p>\n\n<ul>\n<li>touch controls</li>\n<li>viewport behavior</li>\n<li>limited memory</li>\n<li>smaller screens</li>\n<li>browser gestures</li>\n<li>GPU differences</li>\n<li>file picker behavior</li>\n</ul>\n\n<p>A viewer that technically opens on mobile but feels terrible is not really mobile-friendly.</p>\n\n<p>So mobile support became part of the direction, especially for phone scan exports and quick review workflows.</p>\n\n\n\n\n<h2>\n  \n  \n  Feedback I am looking for\n</h2>\n\n<p>If you work with any of these areas, I would really value your feedback:</p>\n\n<ul>\n<li>LiDAR</li>\n<li>UAV mapping</li>\n<li>GIS</li>\n<li>photogrammetry</li>\n<li>3D scanning</li>\n<li>WebGL / WebGPU</li>\n<li>point-cloud visualization</li>\n<li>browser-based technical tools</li>\n</ul>\n\n<p>The most useful feedback would be:</p>\n\n<ul>\n<li>Does your file open?</li>\n<li>Does performance feel acceptable?</li>\n<li>Are the controls intuitive?</li>\n<li>Are the measurement tools useful?</li>\n<li>Does E57 behave correctly with your files?</li>\n<li>What format support is missing?</li>\n<li>What breaks?</li>\n<li>What would make this useful in a real workflow?</li>\n</ul>\n\n<p>Live demo: <a href=\"https://lidar.aurtech.mx/\" rel=\"noopener noreferrer\">https://lidar.aurtech.mx/</a><br>\nGitHub: <a href=\"https://github.com/Aurtechmx/openlidarviewer/\" rel=\"noopener noreferrer\">https://github.com/Aurtechmx/openlidarviewer/</a></p>\n\n<p>If the project is useful, a GitHub star helps. But real feedback from people who work with point-cloud data helps even more.</p>\n\n\n\n\n<h2>\n  \n  \n  Final thought\n</h2>\n\n<p>The browser is becoming a serious runtime for technical software.</p>\n\n<p>Not for everything.</p>\n\n<p>Not for every workflow.</p>\n\n<p>But for the first step — opening, inspecting, measuring, and understanding spatial data — it is starting to make a lot of sense.</p>\n\n<p>That is the direction OpenLiDARViewer is exploring.</p>","score":3},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"A Domain-Driven Notification Microservice — Patterns From Production","link":"https://dev.to/hammadxcm/a-domain-driven-notification-microservice-patterns-from-production-a05","pubDate":"Fri, 22 May 2026 21:06:57 +0000","description":"<p>Notifications start small. \"Send the user an email when their order ships.\" A function. A library. Done.</p>\n\n<p>A year later, you have email, SMS, push, in-app, Slack, and Microsoft Teams. You have user preferences per channel. You have quiet hours, batching, throttling, and a \"do not disturb\" mode. You have unsubscribe links and bounce handling. You have analytics on open rates and template-level metrics. You have multi-language templates and timezone-aware scheduling.</p>\n\n<p>What started as a function is now a system. If you keep it as a sprawling collection of <code>sendEmail</code> and <code>sendSlack</code> helpers across your codebase, that system will eat your engineering team alive.</p>\n\n<p>This is the shape of the notification microservice I built (and have rebuilt twice). The pattern isn't novel — it's domain-driven design applied to notifications — but the specifics matter.</p>\n\n<h2>\n  \n  \n  The core insight\n</h2>\n\n<p>A notification has three distinct concerns:</p>\n\n<ol>\n<li>\n<strong>What happened in the business</strong> — \"order placed,\" \"user mentioned,\" \"invoice overdue.\" This is a domain event.</li>\n<li>\n<strong>What kind of message to send</strong> — \"transactional email,\" \"high-urgency push,\" \"Slack mention.\" This is a delivery policy.</li>\n<li>\n<strong>How to actually send it</strong> — \"render this template, then call the email provider's API.\" This is a channel adapter.</li>\n</ol>\n\n<p>Most codebases collapse all three into one function:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nf\">sendOrderShippedEmail</span><span class=\"p\">(</span><span class=\"nx\">orderId</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"nx\">userId</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">user</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">getUser</span><span class=\"p\">(</span><span class=\"nx\">userId</span><span class=\"p\">)</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">order</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">getOrder</span><span class=\"p\">(</span><span class=\"nx\">orderId</span><span class=\"p\">)</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">html</span> <span class=\"o\">=</span> <span class=\"nf\">renderTemplate</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">order-shipped</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"nx\">user</span><span class=\"p\">,</span> <span class=\"nx\">order</span> <span class=\"p\">})</span>\n  <span class=\"k\">await</span> <span class=\"nx\">sendgrid</span><span class=\"p\">.</span><span class=\"nf\">send</span><span class=\"p\">({</span> <span class=\"na\">to</span><span class=\"p\">:</span> <span class=\"nx\">user</span><span class=\"p\">.</span><span class=\"nx\">email</span><span class=\"p\">,</span> <span class=\"na\">subject</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">Your order shipped</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">html</span> <span class=\"p\">})</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This function knows about the domain event, the message type, and the channel. Three concerns. One function. Each one will change for different reasons, and changes will ripple across all the call sites.</p>\n\n<p>The DDD-shaped version separates them.</p>\n\n<h2>\n  \n  \n  Domain events\n</h2>\n\n<p>The business code emits an event. It doesn't know or care how the notification gets delivered.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// In your order service</span>\n<span class=\"k\">await</span> <span class=\"nx\">eventBus</span><span class=\"p\">.</span><span class=\"nf\">publish</span><span class=\"p\">({</span>\n  <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">order.shipped</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">occurredAt</span><span class=\"p\">:</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">().</span><span class=\"nf\">toISOString</span><span class=\"p\">(),</span>\n  <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"na\">orderId</span><span class=\"p\">:</span> <span class=\"nx\">order</span><span class=\"p\">.</span><span class=\"nx\">id</span><span class=\"p\">,</span>\n    <span class=\"na\">userId</span><span class=\"p\">:</span> <span class=\"nx\">order</span><span class=\"p\">.</span><span class=\"nx\">userId</span><span class=\"p\">,</span>\n    <span class=\"na\">trackingNumber</span><span class=\"p\">:</span> <span class=\"nx\">order</span><span class=\"p\">.</span><span class=\"nx\">trackingNumber</span><span class=\"p\">,</span>\n  <span class=\"p\">},</span>\n<span class=\"p\">})</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This is a fire-and-record operation. The order service is done. It moves on. The event lands in your event bus (BullMQ, Kafka, NATS, whatever).</p>\n\n<p>The notification service consumes events. Its job is to translate \"order.shipped\" into \"a transactional email to the user with this template.\"</p>\n\n<h2>\n  \n  \n  Notification preferences\n</h2>\n\n<p>The user's preferences live in a separate domain. They might be stored in the notification service's database or in a profile service — doesn't matter, as long as they're queryable:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">type</span> <span class=\"nx\">UserNotificationPreferences</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">userId</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"na\">channels</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n    <span class=\"na\">email</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">enabled</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span><span class=\"p\">;</span> <span class=\"nl\">address</span><span class=\"p\">:</span> <span class=\"kr\">string</span> <span class=\"p\">}</span>\n    <span class=\"nl\">sms</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">enabled</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span><span class=\"p\">;</span> <span class=\"nl\">number</span><span class=\"p\">?:</span> <span class=\"kr\">string</span> <span class=\"p\">}</span>\n    <span class=\"nl\">push</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">enabled</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span><span class=\"p\">;</span> <span class=\"nl\">deviceTokens</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">[]</span> <span class=\"p\">}</span>\n    <span class=\"nl\">slack</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">enabled</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span><span class=\"p\">;</span> <span class=\"nl\">userId</span><span class=\"p\">?:</span> <span class=\"kr\">string</span> <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n  <span class=\"nl\">perEventType</span><span class=\"p\">:</span> <span class=\"nb\">Record</span><span class=\"o\">&lt;</span><span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n    <span class=\"na\">channels</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">sms</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">push</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">slack</span><span class=\"dl\">'</span><span class=\"p\">)[]</span>\n    <span class=\"na\">enabled</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span>\n  <span class=\"p\">}</span><span class=\"o\">&gt;</span>\n  <span class=\"na\">quietHours</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">start</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">;</span> <span class=\"nl\">end</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">;</span> <span class=\"nl\">tz</span><span class=\"p\">:</span> <span class=\"kr\">string</span> <span class=\"p\">}</span> <span class=\"o\">|</span> <span class=\"kc\">null</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>When the notification service receives <code>order.shipped</code>, it looks up the user's preferences. The user has email enabled, SMS enabled, push enabled — but for this event type (<code>order.shipped</code>), they've only chosen email. So the service sends one email.</p>\n\n<p>This decoupling is crucial. The business code emits one event. The notification service decides what to do with it based on user preferences. The user can change their preferences without anyone touching the business code.</p>\n\n<h2>\n  \n  \n  The dispatcher\n</h2>\n\n<p>The middle layer is a dispatcher that:</p>\n\n<ol>\n<li>Consumes an event.</li>\n<li>Looks up the user's preferences.</li>\n<li>Decides which channels to deliver on.</li>\n<li>For each channel, builds a delivery job and queues it.\n</li>\n</ol>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">type</span> <span class=\"nx\">Channel</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">sms</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">push</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">slack</span><span class=\"dl\">'</span>\n\n<span class=\"kd\">class</span> <span class=\"nc\">NotificationDispatcher</span> <span class=\"p\">{</span>\n  <span class=\"k\">async</span> <span class=\"nf\">handle</span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">:</span> <span class=\"nx\">DomainEvent</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">userId</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">data</span> <span class=\"k\">as</span> <span class=\"kr\">any</span><span class=\"p\">).</span><span class=\"nx\">userId</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">userId</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">prefs</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">prefsRepo</span><span class=\"p\">.</span><span class=\"nf\">findByUserId</span><span class=\"p\">(</span><span class=\"nx\">userId</span><span class=\"p\">)</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">prefs</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">eventPrefs</span> <span class=\"o\">=</span> <span class=\"nx\">prefs</span><span class=\"p\">.</span><span class=\"nx\">perEventType</span><span class=\"p\">[</span><span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"kd\">type</span><span class=\"p\">]</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">eventPrefs</span><span class=\"p\">?.</span><span class=\"nx\">enabled</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">now</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">()</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">channels</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">filterByQuietHours</span><span class=\"p\">(</span><span class=\"nx\">eventPrefs</span><span class=\"p\">.</span><span class=\"nx\">channels</span><span class=\"p\">,</span> <span class=\"nx\">prefs</span><span class=\"p\">,</span> <span class=\"nx\">now</span><span class=\"p\">)</span>\n\n    <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">channel</span> <span class=\"k\">of</span> <span class=\"nx\">channels</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">deliveryQueue</span><span class=\"p\">.</span><span class=\"nf\">add</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">deliver</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n        <span class=\"na\">eventType</span><span class=\"p\">:</span> <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"kd\">type</span><span class=\"p\">,</span>\n        <span class=\"nx\">userId</span><span class=\"p\">,</span>\n        <span class=\"nx\">channel</span><span class=\"p\">,</span>\n        <span class=\"na\">eventData</span><span class=\"p\">:</span> <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">,</span>\n        <span class=\"na\">scheduledAt</span><span class=\"p\">:</span> <span class=\"nx\">now</span><span class=\"p\">.</span><span class=\"nf\">toISOString</span><span class=\"p\">(),</span>\n      <span class=\"p\">})</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">private</span> <span class=\"nf\">filterByQuietHours</span><span class=\"p\">(</span>\n    <span class=\"nx\">requested</span><span class=\"p\">:</span> <span class=\"nx\">Channel</span><span class=\"p\">[],</span>\n    <span class=\"nx\">prefs</span><span class=\"p\">:</span> <span class=\"nx\">UserNotificationPreferences</span><span class=\"p\">,</span>\n    <span class=\"nx\">now</span><span class=\"p\">:</span> <span class=\"nb\">Date</span>\n  <span class=\"p\">):</span> <span class=\"nx\">Channel</span><span class=\"p\">[]</span> <span class=\"p\">{</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">prefs</span><span class=\"p\">.</span><span class=\"nx\">quietHours</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"nx\">requested</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">isQuiet</span> <span class=\"o\">=</span> <span class=\"nf\">isWithinQuietHours</span><span class=\"p\">(</span><span class=\"nx\">now</span><span class=\"p\">,</span> <span class=\"nx\">prefs</span><span class=\"p\">.</span><span class=\"nx\">quietHours</span><span class=\"p\">)</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">isQuiet</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"nx\">requested</span>\n    <span class=\"c1\">// During quiet hours, only allow non-disruptive channels (e.g., email/in-app)</span>\n    <span class=\"k\">return</span> <span class=\"nx\">requested</span><span class=\"p\">.</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"nx\">c</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">c</span> <span class=\"o\">===</span> <span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span> <span class=\"o\">||</span> <span class=\"nx\">c</span> <span class=\"o\">===</span> <span class=\"dl\">'</span><span class=\"s1\">in-app</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The dispatcher is pure routing logic. It doesn't render templates. It doesn't call any provider. It just figures out which channels to deliver on and queues delivery jobs.</p>\n\n<h2>\n  \n  \n  Channel adapters\n</h2>\n\n<p>Each channel is a separate worker that consumes jobs from the delivery queue and dispatches to its specific channel.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">class</span> <span class=\"nc\">EmailDeliveryWorker</span> <span class=\"p\">{</span>\n  <span class=\"k\">async</span> <span class=\"nf\">handle</span><span class=\"p\">(</span><span class=\"nx\">job</span><span class=\"p\">:</span> <span class=\"nx\">DeliveryJob</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">channel</span> <span class=\"o\">!==</span> <span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">user</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">userRepo</span><span class=\"p\">.</span><span class=\"nf\">findById</span><span class=\"p\">(</span><span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">userId</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">prefs</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">prefsRepo</span><span class=\"p\">.</span><span class=\"nf\">findByUserId</span><span class=\"p\">(</span><span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">userId</span><span class=\"p\">)</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">user</span> <span class=\"o\">||</span> <span class=\"o\">!</span><span class=\"nx\">prefs</span><span class=\"p\">?.</span><span class=\"nx\">channels</span><span class=\"p\">.</span><span class=\"nx\">email</span><span class=\"p\">.</span><span class=\"nx\">enabled</span><span class=\"p\">)</span> <span class=\"k\">return</span>\n\n    <span class=\"kd\">const</span> <span class=\"nx\">template</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">templateRepo</span><span class=\"p\">.</span><span class=\"nf\">find</span><span class=\"p\">(</span><span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">eventType</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">rendered</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">renderer</span><span class=\"p\">.</span><span class=\"nf\">render</span><span class=\"p\">(</span><span class=\"nx\">template</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n      <span class=\"nx\">user</span><span class=\"p\">,</span>\n      <span class=\"p\">...</span><span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">eventData</span><span class=\"p\">,</span>\n    <span class=\"p\">})</span>\n\n    <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">emailProvider</span><span class=\"p\">.</span><span class=\"nf\">send</span><span class=\"p\">({</span>\n      <span class=\"na\">to</span><span class=\"p\">:</span> <span class=\"nx\">prefs</span><span class=\"p\">.</span><span class=\"nx\">channels</span><span class=\"p\">.</span><span class=\"nx\">email</span><span class=\"p\">.</span><span class=\"nx\">address</span><span class=\"p\">,</span>\n      <span class=\"na\">from</span><span class=\"p\">:</span> <span class=\"nx\">rendered</span><span class=\"p\">.</span><span class=\"k\">from</span><span class=\"p\">,</span>\n      <span class=\"na\">subject</span><span class=\"p\">:</span> <span class=\"nx\">rendered</span><span class=\"p\">.</span><span class=\"nx\">subject</span><span class=\"p\">,</span>\n      <span class=\"na\">html</span><span class=\"p\">:</span> <span class=\"nx\">rendered</span><span class=\"p\">.</span><span class=\"nx\">html</span><span class=\"p\">,</span>\n      <span class=\"na\">text</span><span class=\"p\">:</span> <span class=\"nx\">rendered</span><span class=\"p\">.</span><span class=\"nx\">text</span><span class=\"p\">,</span>\n      <span class=\"na\">headers</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n        <span class=\"dl\">'</span><span class=\"s1\">X-Event-Type</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">eventType</span><span class=\"p\">,</span>\n        <span class=\"dl\">'</span><span class=\"s1\">X-User-Id</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">userId</span><span class=\"p\">,</span>\n        <span class=\"dl\">'</span><span class=\"s1\">List-Unsubscribe</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"s2\">`&lt;</span><span class=\"p\">${</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">unsubscribeUrl</span><span class=\"p\">(</span><span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">eventType</span><span class=\"p\">)}</span><span class=\"s2\">&gt;`</span><span class=\"p\">,</span>\n      <span class=\"p\">},</span>\n    <span class=\"p\">})</span>\n\n    <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">deliveryLog</span><span class=\"p\">.</span><span class=\"nf\">record</span><span class=\"p\">({</span>\n      <span class=\"na\">userId</span><span class=\"p\">:</span> <span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">userId</span><span class=\"p\">,</span>\n      <span class=\"na\">channel</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n      <span class=\"na\">eventType</span><span class=\"p\">:</span> <span class=\"nx\">job</span><span class=\"p\">.</span><span class=\"nx\">eventType</span><span class=\"p\">,</span>\n      <span class=\"na\">sentAt</span><span class=\"p\">:</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">().</span><span class=\"nf\">toISOString</span><span class=\"p\">(),</span>\n      <span class=\"na\">provider</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">emailProvider</span><span class=\"p\">.</span><span class=\"nx\">name</span><span class=\"p\">,</span>\n    <span class=\"p\">})</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The email worker knows about:</p>\n\n<ul>\n<li>The user (to get their address).</li>\n<li>The template store (to find the right template for this event type and channel).</li>\n<li>The renderer (to fill in the template).</li>\n<li>The email provider (to actually send).</li>\n</ul>\n\n<p>It doesn't know about Slack, SMS, push, or any business logic. If I want to add a new channel, I add a new worker. The dispatcher already knows how to route to it (the channel is just a string in the job).</p>\n\n<h2>\n  \n  \n  Templates as first-class entities\n</h2>\n\n<p>A template store is its own small domain. Each template has:</p>\n\n<ul>\n<li>An event type it's for (<code>order.shipped</code>).</li>\n<li>A channel it's for (<code>email</code>, <code>sms</code>).</li>\n<li>A language (<code>en</code>, <code>de</code>, <code>fr</code>).</li>\n<li>A version (so changes are auditable).</li>\n<li>The actual template content (HTML, plain text, Markdown).\n</li>\n</ul>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">type</span> <span class=\"nx\">Template</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n  <span class=\"na\">id</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"na\">eventType</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"na\">channel</span><span class=\"p\">:</span> <span class=\"nx\">Channel</span>\n  <span class=\"na\">language</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"na\">version</span><span class=\"p\">:</span> <span class=\"kr\">number</span>\n  <span class=\"nx\">subjectTemplate</span><span class=\"p\">?:</span> <span class=\"kr\">string</span>       <span class=\"c1\">// for email</span>\n  <span class=\"na\">bodyTemplate</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"na\">format</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">html</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">markdown</span><span class=\"dl\">'</span> <span class=\"o\">|</span> <span class=\"dl\">'</span><span class=\"s1\">plain</span><span class=\"dl\">'</span>\n  <span class=\"na\">createdAt</span><span class=\"p\">:</span> <span class=\"kr\">string</span>\n  <span class=\"na\">active</span><span class=\"p\">:</span> <span class=\"nx\">boolean</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The renderer takes a template and a context object and produces a rendered message. Use a templating library that has a sandboxed mode (Handlebars's strict mode, for example) — you do not want template authors writing arbitrary JS that gets executed during rendering.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">class</span> <span class=\"nc\">Renderer</span> <span class=\"p\">{</span>\n  <span class=\"k\">async</span> <span class=\"nf\">render</span><span class=\"p\">(</span><span class=\"nx\">template</span><span class=\"p\">:</span> <span class=\"nx\">Template</span><span class=\"p\">,</span> <span class=\"nx\">context</span><span class=\"p\">:</span> <span class=\"nb\">Record</span><span class=\"o\">&lt;</span><span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"kr\">any</span><span class=\"o\">&gt;</span><span class=\"p\">):</span> <span class=\"nb\">Promise</span><span class=\"o\">&lt;</span><span class=\"nx\">Rendered</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">compiled</span> <span class=\"o\">=</span> <span class=\"nx\">Handlebars</span><span class=\"p\">.</span><span class=\"nf\">compile</span><span class=\"p\">(</span><span class=\"nx\">template</span><span class=\"p\">.</span><span class=\"nx\">bodyTemplate</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">strict</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">})</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">body</span> <span class=\"o\">=</span> <span class=\"nf\">compiled</span><span class=\"p\">(</span><span class=\"nx\">context</span><span class=\"p\">)</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">subject</span> <span class=\"o\">=</span> <span class=\"nx\">template</span><span class=\"p\">.</span><span class=\"nx\">subjectTemplate</span>\n      <span class=\"p\">?</span> <span class=\"nx\">Handlebars</span><span class=\"p\">.</span><span class=\"nf\">compile</span><span class=\"p\">(</span><span class=\"nx\">template</span><span class=\"p\">.</span><span class=\"nx\">subjectTemplate</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">strict</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">})(</span><span class=\"nx\">context</span><span class=\"p\">)</span>\n      <span class=\"p\">:</span> <span class=\"kc\">undefined</span>\n    <span class=\"k\">return</span> <span class=\"p\">{</span> <span class=\"nx\">body</span><span class=\"p\">,</span> <span class=\"nx\">subject</span><span class=\"p\">,</span> <span class=\"na\">format</span><span class=\"p\">:</span> <span class=\"nx\">template</span><span class=\"p\">.</span><span class=\"nx\">format</span> <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Templates being stored in a database (not in code) means non-engineers can edit them. We had marketing folks editing email copy via an admin panel without ever touching a deploy.</p>\n\n<h2>\n  \n  \n  The unsubscribe surface\n</h2>\n\n<p>Every notification should be unsubscribable. The dispatcher checks <code>enabled</code> flags before queuing, but the user needs a way to flip those flags. Two patterns:</p>\n\n<ol>\n<li>\n<strong>A preferences page in your app.</strong> Standard. Each event type has a checkbox per channel.</li>\n<li>\n<strong>A one-click unsubscribe link in every notification.</strong> Required by law in many jurisdictions for email marketing, and good UX everywhere.</li>\n</ol>\n\n<p>The unsubscribe link encodes the user ID, the event type, and a signed token. Clicking it flips the preference:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"kd\">function</span> <span class=\"nf\">unsubscribeUrl</span><span class=\"p\">(</span><span class=\"nx\">userId</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"nx\">eventType</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">):</span> <span class=\"kr\">string</span> <span class=\"p\">{</span>\n  <span class=\"kd\">const</span> <span class=\"nx\">token</span> <span class=\"o\">=</span> <span class=\"nf\">sign</span><span class=\"p\">({</span> <span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"nx\">eventType</span><span class=\"p\">,</span> <span class=\"na\">action</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">unsubscribe</span><span class=\"dl\">'</span> <span class=\"p\">})</span>\n  <span class=\"k\">return</span> <span class=\"s2\">`https://app.example.com/api/notify/unsubscribe?token=</span><span class=\"p\">${</span><span class=\"nx\">token</span><span class=\"p\">}</span><span class=\"s2\">`</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The endpoint verifies the token, updates the preference, and shows a \"you're unsubscribed\" page. The same endpoint can power the <code>List-Unsubscribe</code> header for email clients that support one-click unsubscribe.</p>\n\n<h2>\n  \n  \n  Observability\n</h2>\n\n<p>Each delivery generates two records:</p>\n\n<ol>\n<li>\n<strong>Delivery log.</strong> \"We attempted to send X to user Y on channel Z at time T using provider P.\"</li>\n<li>\n<strong>Provider callback.</strong> \"The provider says message M was delivered (or bounced, or opened, or clicked).\"</li>\n</ol>\n\n<p>Both feed into the same table, keyed by a message ID. The observability story collapses into \"show me everything that happened for user Y this week,\" which is what support teams ask for.</p>\n\n<h2>\n  \n  \n  Throttling and batching\n</h2>\n\n<p>Two problems show up at scale:</p>\n\n<ol>\n<li>\n<strong>A user gets 50 notifications in 10 minutes</strong> because something noisy happened. You need to batch them into a digest.</li>\n<li>\n<strong>A celebrity user's actions trigger 10,000 notifications to followers</strong> in a burst. You need to throttle.</li>\n</ol>\n\n<p>The dispatcher is where this logic lives. Before queuing a delivery, check a sliding-window counter (Redis-backed):<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">async</span> <span class=\"nf\">handle</span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">:</span> <span class=\"nx\">DomainEvent</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n  <span class=\"c1\">// ...existing routing logic...</span>\n\n  <span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">const</span> <span class=\"nx\">channel</span> <span class=\"k\">of</span> <span class=\"nx\">channels</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nb\">window</span> <span class=\"o\">=</span> <span class=\"s2\">`notify:</span><span class=\"p\">${</span><span class=\"nx\">userId</span><span class=\"p\">}</span><span class=\"s2\">:</span><span class=\"p\">${</span><span class=\"nx\">channel</span><span class=\"p\">}</span><span class=\"s2\">:</span><span class=\"p\">${</span><span class=\"nx\">eventType</span><span class=\"p\">}</span><span class=\"s2\">`</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">count</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">redis</span><span class=\"p\">.</span><span class=\"nf\">incr</span><span class=\"p\">(</span><span class=\"nb\">window</span><span class=\"p\">)</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">count</span> <span class=\"o\">===</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">redis</span><span class=\"p\">.</span><span class=\"nf\">expire</span><span class=\"p\">(</span><span class=\"nb\">window</span><span class=\"p\">,</span> <span class=\"mi\">60</span><span class=\"p\">)</span>  <span class=\"c1\">// 1-minute window</span>\n\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">count</span> <span class=\"o\">&gt;</span> <span class=\"nx\">MAX_PER_MINUTE</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"c1\">// Throttled. Queue for batching instead.</span>\n      <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">batchQueue</span><span class=\"p\">.</span><span class=\"nf\">add</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">batch</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"nx\">userId</span><span class=\"p\">,</span> <span class=\"nx\">channel</span><span class=\"p\">,</span> <span class=\"nx\">eventType</span><span class=\"p\">,</span> <span class=\"na\">eventData</span><span class=\"p\">:</span> <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">data</span> <span class=\"p\">})</span>\n    <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n      <span class=\"k\">await</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">deliveryQueue</span><span class=\"p\">.</span><span class=\"nf\">add</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">deliver</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"cm\">/* ... */</span> <span class=\"p\">})</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The batch worker runs periodically (every 5 minutes or whatever the digest schedule is), collects everything in the batch queue for a user, renders a \"digest\" template, and sends one combined notification.</p>\n\n<h2>\n  \n  \n  What I'd warn the next team about\n</h2>\n\n<p><strong>Don't put rendering in the dispatcher.</strong> Keep the dispatcher routing-only. The dispatcher decides which channels to use; the workers decide how to render and send. Mixing them couples your routing logic to your template engine in ways that hurt later.</p>\n\n<p><strong>Use job retries with caution.</strong> If the email provider returns a 503, retry. If it returns a 400 with \"invalid recipient,\" do not retry — that's a permanent failure. Different error codes have different retry semantics; encode this in the worker.</p>\n\n<p><strong>Track template performance.</strong> Open rate per template per language is a real signal. Templates that score below 10% open rate are usually broken (bad subject line, bad timing, irrelevant content). Surface this in your admin UI.</p>\n\n<p><strong>Make the dead-letter queue visible.</strong> Failed deliveries should go somewhere humans can see them. We had three months of bounces piling up before anyone noticed; turned out a customer had typo'd their email and was getting nothing. A weekly dashboard of \"delivery failures by user\" caught it.</p>\n\n<h2>\n  \n  \n  The takeaway\n</h2>\n\n<p>A notification microservice is one of the highest-leverage extractions you can do. The business code becomes simple (emit events). User preferences become a first-class concept. Templates become editable without deploys. New channels become new workers, not new branches in shared functions.</p>\n\n<p>The pattern is more code than <code>sendgrid.send(...)</code>. It's also the difference between \"we have a notification feature\" and \"we have a notification platform.\" If you're shipping more than two notification types and you're tired of touching the same five functions every time product adds a new channel, this extraction pays for itself within a quarter.</p>","score":5},{"source":"https://dev.to/feed/tag/typescript","sourceHost":"dev.to","title":"🧩 Handling 1,000+ Inputs with Angular Reactive Forms: An Enterprise Architecture Breakdown","link":"https://dev.to/abdelaaziz_ouakala/handling-1000-inputs-with-angular-reactive-forms-an-enterprise-architecture-breakdown-47cg","pubDate":"Fri, 22 May 2026 20:13:19 +0000","description":"<blockquote>\n<p><strong>\"One recurring issue in enterprise Angular apps: forms that start simple… then become entire application platforms.\"</strong></p>\n</blockquote>\n\n<p>I've seen it across multiple production systems.</p>\n\n<p>A product configuration screen ships with 40 fields. Requirements evolve. Validations multiply. Dynamic sections get added. Conditional logic compounds.</p>\n\n<p>Twelve months later: 1,200+ controls. One <code>FormGroup</code>. Zero architectural boundaries.</p>\n\n<p>And the team wonders why scrolling feels sluggish.</p>\n\n<p>This is not a Reactive Forms limitation. It's what happens when form architecture doesn't keep pace with form complexity.</p>\n\n<p>In this post, I'll break down:</p>\n\n<ul>\n<li>Why large Angular forms degrade at scale</li>\n<li>Where rendering, validation, and state bottlenecks actually appear</li>\n<li>The production patterns that address each problem</li>\n<li>Concrete code examples you can apply today</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Table of Contents\n</h2>\n\n<ol>\n<li>The Core Problem: Forms That Outgrow Their Architecture</li>\n<li>Bottleneck #1 — Rendering Overhead</li>\n<li>Bottleneck #2 — Validation Complexity at Scale</li>\n<li>Bottleneck #3 — Subscription Sprawl</li>\n<li>The Scalable Architecture: Segment the Form</li>\n<li>Strategy 1 — Bounded FormGroups</li>\n<li>Strategy 2 — Deferred Section Rendering with @defer</li>\n<li>Strategy 3 — Isolated Subscription Management</li>\n<li>Strategy 4 — Scoped Validators</li>\n<li>Strategy 5 — Virtual Scrolling for Long Field Lists</li>\n<li>Strategy 6 — Signals Interoperability</li>\n<li>Before vs. After: The Full Architecture Comparison</li>\n<li>The Senior Engineer Framing</li>\n<li>Key Takeaways</li>\n</ol>\n\n\n\n\n<h2>\n  \n  \n  The Core Problem: Forms That Outgrow Their Architecture\n</h2>\n\n<p>Most Angular tutorials cover Reactive Forms at a comfortable scale. A login form. A registration screen. A checkout flow. At that scale, everything the framework provides is sufficient.</p>\n\n<p>The problems begin when forms are asked to do more than they were initially designed for.</p>\n\n<p>In enterprise applications, forms frequently evolve into workflow engines:</p>\n\n<ul>\n<li>A product configuration form grows to include conditional pricing logic, region-specific field sets, and real-time inventory validation</li>\n<li>An onboarding form expands into a multi-step process with dependent field sections, async validations against external APIs, and intermediate save states</li>\n<li>A data-entry form scales from 50 rows to 5,000 rows as the business grows</li>\n</ul>\n\n<p>The form didn't become complex overnight. It became complex incrementally — one field, one validator, one subscription at a time. And without intentional architectural boundaries, that incremental complexity accumulates into a system that is difficult to reason about, slow to render, and expensive to maintain.</p>\n\n<p><strong>The key insight:</strong> Large forms are not primarily UI problems. They are state-management and rendering-architecture problems that happen to manifest as UI degradation.</p>\n\n<p>Understanding this distinction changes how you approach the solution.</p>\n\n\n\n\n<h2>\n  \n  \n  Bottleneck #1 — Rendering Overhead\n</h2>\n\n<p>Angular's change detection is the first place large forms reveal their architectural debt.</p>\n\n<p>In Angular's default change detection strategy, a value change in <em>any</em> part of the component tree can trigger checks across the <em>entire</em> component tree. For a form with 1,000+ controls, this creates a predictable cascade:</p>\n\n<ol>\n<li>A user types in a single input field</li>\n<li>The <code>FormControl</code> emits a value change event</li>\n<li>Angular's change detection runs across all components in the form's subtree</li>\n<li>Every bound expression — including those in completely unrelated form sections — is evaluated</li>\n</ol>\n\n<p>This is not a bug in Angular. It's the default behaviour of zone.js-based change detection operating on a component tree without explicit boundaries.</p>\n\n<p>The profiler makes this visible. Open Angular DevTools on a large, unoptimised form, type a single character, and observe the flame chart. You'll see change detection running across components that have no logical relationship to the field you just edited.</p>\n\n<h3>\n  \n  \n  What compounds the problem\n</h3>\n\n<p>The issue scales non-linearly with form size. A form with 100 controls might have acceptable performance in Default change detection mode. At 500 controls, the detection overhead becomes noticeable. At 1,000+, it affects the perceived responsiveness of the form in ways that users notice and report.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// The problem: one FormGroup, no detection boundaries</span>\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-large-form</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"c1\">// Default change detection — entire subtree checked on every change</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;form [formGroup]=\"rootForm\"&gt;\n      &lt;!-- 1,200 controls in one flat tree --&gt;\n      &lt;input formControlName=\"field_1\" /&gt;\n      &lt;input formControlName=\"field_2\" /&gt;\n      &lt;!-- ... 1,198 more controls --&gt;\n    &lt;/form&gt;\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">LargeFormComponent</span> <span class=\"p\">{</span>\n  <span class=\"nx\">rootForm</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n    <span class=\"na\">field_1</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">],</span>\n    <span class=\"na\">field_2</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">],</span>\n    <span class=\"c1\">// ... 1,198 more controls</span>\n  <span class=\"p\">});</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Every change to <code>field_1</code> triggers evaluation of expressions bound to <code>field_1198</code>. That is the rendering overhead problem.</p>\n\n\n\n\n<h2>\n  \n  \n  Bottleneck #2 — Validation Complexity at Scale\n</h2>\n\n<p>Validation is the second compounding bottleneck.</p>\n\n<p>At the scale of individual forms, synchronous validators are fast and inconsequential. At the scale of hundreds of controls with cross-field dependencies, they become a measurable cost.</p>\n\n<h3>\n  \n  \n  Synchronous validator frequency\n</h3>\n\n<p>Angular's Reactive Forms run synchronous validators on every <code>valueChanges</code> emission. Every keystroke in every field dispatches a validation pass. For a root <code>FormGroup</code> with 1,000+ controls and a set of cross-field validators, this means:</p>\n\n<ul>\n<li>A user types a single character in a pricing field</li>\n<li>Angular runs all synchronous validators on the root group</li>\n<li>Cross-field validators that check relationships between <code>startDate</code> and <code>endDate</code>, <code>quantity</code> and <code>minimumOrderValue</code>, and <code>regionCode</code> and <code>availableRegions</code> — all fire</li>\n<li>The validation pass runs across controls the user hasn't touched and isn't currently viewing</li>\n</ul>\n\n<h3>\n  \n  \n  Async validator accumulation\n</h3>\n\n<p>Async validators compound this further. If multiple fields trigger HTTP validation requests, and those requests aren't properly debounced and scoped, a form can generate significant network traffic from normal user interaction.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// The problem: cross-field validators wired to root FormGroup</span>\n<span class=\"kd\">const</span> <span class=\"nx\">rootForm</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">(</span>\n  <span class=\"p\">{</span>\n    <span class=\"na\">startDate</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n    <span class=\"na\">endDate</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n    <span class=\"na\">region</span><span class=\"p\">:</span>    <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n    <span class=\"na\">quantity</span><span class=\"p\">:</span>  <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span>  <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)],</span>\n    <span class=\"c1\">// ... 996 more controls</span>\n  <span class=\"p\">},</span>\n  <span class=\"p\">{</span>\n    <span class=\"c1\">// This validator fires on EVERY change to ANY control in the root group</span>\n    <span class=\"na\">validators</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n      <span class=\"nx\">dateRangeValidator</span><span class=\"p\">,</span>\n      <span class=\"nx\">regionAvailabilityValidator</span><span class=\"p\">,</span>\n      <span class=\"nx\">minimumOrderValidator</span>\n    <span class=\"p\">]</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>When <code>dateRangeValidator</code>, <code>regionAvailabilityValidator</code>, and <code>minimumOrderValidator</code> are all wired to the root <code>FormGroup</code>, they execute on every change to every one of the 1,000 controls — including controls that have no logical relationship to the validation rules.</p>\n\n\n\n\n<h2>\n  \n  \n  Bottleneck #3 — Subscription Sprawl\n</h2>\n\n<p>Subscription management is the third bottleneck — and the most likely to manifest as a production issue rather than a development-time observation.</p>\n\n<p>Reactive Forms expose <code>valueChanges</code> and <code>statusChanges</code> observables on every <code>FormControl</code>, <code>FormGroup</code>, and <code>FormArray</code>. These are powerful tools. They're also easy to accumulate carelessly.</p>\n\n<p>In a large form component that has grown over time, it's common to find:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"nf\">ngOnInit</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n  <span class=\"c1\">// Subscription 1: react to section A changes</span>\n  <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">sectionA</span><span class=\"dl\">'</span><span class=\"p\">).</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">val</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">updateSectionBDefaults</span><span class=\"p\">(</span><span class=\"nx\">val</span><span class=\"p\">);</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"c1\">// Subscription 2: sync UI state</span>\n  <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nx\">statusChanges</span><span class=\"p\">.</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">status</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">isFormValid</span> <span class=\"o\">=</span> <span class=\"nx\">status</span> <span class=\"o\">===</span> <span class=\"dl\">'</span><span class=\"s1\">VALID</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"c1\">// Subscription 3: autosave</span>\n  <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span>\n    <span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">2000</span><span class=\"p\">)</span>\n  <span class=\"p\">).</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">val</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">autosaveService</span><span class=\"p\">.</span><span class=\"nf\">save</span><span class=\"p\">(</span><span class=\"nx\">val</span><span class=\"p\">);</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"c1\">// ... 6 more subscriptions added by different developers over 12 months</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>If these subscriptions are not explicitly destroyed when the component is destroyed — and in practice, many aren't — they create retained references that prevent garbage collection. In a single-page application where users navigate in and out of the form view, each navigation creates a new subscription set without cleaning up the previous one.</p>\n\n<p>The result: memory usage that grows monotonically with user navigation, and event handlers firing on components that no longer exist in the DOM.</p>\n\n\n\n\n<h2>\n  \n  \n  The Scalable Architecture: Segment the Form\n</h2>\n\n<p>The solution to all three bottlenecks is the same architectural decision: <strong>treat large forms as modular systems, not monolithic components.</strong></p>\n\n<p>Each logical section of the form becomes a bounded module with:</p>\n\n<ol>\n<li>Its own <code>FormGroup</code> and validation scope</li>\n<li>Its own Angular component with <code>OnPush</code> change detection</li>\n<li>Its own subscription lifecycle, scoped to component destruction</li>\n<li>A typed, explicit output interface to the parent form</li>\n</ol>\n\n<p>This is not over-engineering. It is the minimum architecture that allows large forms to remain maintainable as they grow.</p>\n\n<p>Here is the enterprise form structure we'll build toward:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>RootFormComponent (OnPush, orchestration only)\n├── PersonalInfoSection (OnPush, isolated FormGroup, scoped subscriptions)\n├── ConfigurationSection (OnPush, isolated FormGroup, scoped subscriptions)\n├── LineItemsSection (OnPush, FormArray, virtual scrolling)\n│   ├── LineItemRow × N (OnPush, minimal FormGroup per row)\n└── ReviewSection (OnPush, read-only derived state)\n</code></pre>\n\n</div>\n\n\n\n<p>Let's build each piece.</p>\n\n\n\n\n<h2>\n  \n  \n  Strategy 1 — Bounded FormGroups\n</h2>\n\n<p>The first and most impactful change is to replace one large flat <code>FormGroup</code> with a hierarchy of bounded sub-groups, each owned by its own component.</p>\n\n<h3>\n  \n  \n  Root form (orchestration only)\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// enterprise-form.component.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">Component</span><span class=\"p\">,</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">,</span> <span class=\"nx\">inject</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">FormBuilder</span><span class=\"p\">,</span> <span class=\"nx\">ReactiveFormsModule</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/forms</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-enterprise-form</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">standalone</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span>\n  <span class=\"na\">imports</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nx\">ReactiveFormsModule</span><span class=\"p\">],</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;form [formGroup]=\"rootForm\" (ngSubmit)=\"onSubmit()\"&gt;\n      &lt;app-personal-info-section\n        [formGroup]=\"personalInfoGroup\"&gt;\n      &lt;/app-personal-info-section&gt;\n\n      &lt;app-configuration-section\n        [formGroup]=\"configurationGroup\"&gt;\n      &lt;/app-configuration-section&gt;\n\n      &lt;app-line-items-section\n        [formArray]=\"lineItemsArray\"&gt;\n      &lt;/app-line-items-section&gt;\n    &lt;/form&gt;\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">EnterpriseFormComponent</span> <span class=\"p\">{</span>\n  <span class=\"k\">private</span> <span class=\"nx\">fb</span> <span class=\"o\">=</span> <span class=\"nf\">inject</span><span class=\"p\">(</span><span class=\"nx\">FormBuilder</span><span class=\"p\">);</span>\n\n  <span class=\"nx\">rootForm</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n    <span class=\"na\">personalInfo</span><span class=\"p\">:</span>  <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">buildPersonalInfoGroup</span><span class=\"p\">(),</span>\n    <span class=\"na\">configuration</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">buildConfigurationGroup</span><span class=\"p\">(),</span>\n    <span class=\"na\">lineItems</span><span class=\"p\">:</span>     <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">array</span><span class=\"p\">([]),</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"kd\">get</span> <span class=\"nf\">personalInfoGroup</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">personalInfo</span><span class=\"dl\">'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"nx\">FormGroup</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"kd\">get</span> <span class=\"nf\">configurationGroup</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">configuration</span><span class=\"dl\">'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"nx\">FormGroup</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"kd\">get</span> <span class=\"nf\">lineItemsArray</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">lineItems</span><span class=\"dl\">'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"nx\">FormArray</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">private</span> <span class=\"nf\">buildPersonalInfoGroup</span><span class=\"p\">():</span> <span class=\"nx\">FormGroup</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n      <span class=\"na\">firstName</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">minLength</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)]],</span>\n      <span class=\"na\">lastName</span><span class=\"p\">:</span>    <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">minLength</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)]],</span>\n      <span class=\"na\">email</span><span class=\"p\">:</span>       <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">email</span><span class=\"p\">]],</span>\n      <span class=\"na\">phoneNumber</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">pattern</span><span class=\"p\">(</span><span class=\"sr\">/^</span><span class=\"se\">\\+?[\\d\\s\\-</span><span class=\"sr\">()</span><span class=\"se\">]{10,}</span><span class=\"sr\">$/</span><span class=\"p\">)],</span>\n    <span class=\"p\">});</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">private</span> <span class=\"nf\">buildConfigurationGroup</span><span class=\"p\">():</span> <span class=\"nx\">FormGroup</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n      <span class=\"na\">region</span><span class=\"p\">:</span>     <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n      <span class=\"na\">currency</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">USD</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n      <span class=\"na\">planTier</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">standard</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n      <span class=\"na\">maxUsers</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)]],</span>\n    <span class=\"p\">});</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nf\">onSubmit</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nx\">valid</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n      <span class=\"c1\">// Handle submission</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Section component (isolated, OnPush)\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// personal-info-section.component.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span>\n  <span class=\"nx\">Component</span><span class=\"p\">,</span> <span class=\"nx\">Input</span><span class=\"p\">,</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">,</span> <span class=\"nx\">OnInit</span><span class=\"p\">,</span> <span class=\"nx\">OnDestroy</span><span class=\"p\">,</span> <span class=\"nx\">inject</span>\n<span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">FormGroup</span><span class=\"p\">,</span> <span class=\"nx\">ReactiveFormsModule</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/forms</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">Subject</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">rxjs</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">takeUntil</span><span class=\"p\">,</span> <span class=\"nx\">debounceTime</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">rxjs/operators</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-personal-info-section</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">standalone</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span>\n  <span class=\"na\">imports</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nx\">ReactiveFormsModule</span><span class=\"p\">],</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;section [formGroup]=\"formGroup\"&gt;\n      &lt;h3&gt;Personal Information&lt;/h3&gt;\n\n      &lt;div class=\"field-row\"&gt;\n        &lt;label for=\"firstName\"&gt;First Name&lt;/label&gt;\n        &lt;input id=\"firstName\" formControlName=\"firstName\" /&gt;\n        @if (formGroup.get('firstName')?.invalid &amp;&amp; formGroup.get('firstName')?.touched) {\n          &lt;span class=\"error\"&gt;First name is required&lt;/span&gt;\n        }\n      &lt;/div&gt;\n\n      &lt;div class=\"field-row\"&gt;\n        &lt;label for=\"email\"&gt;Email Address&lt;/label&gt;\n        &lt;input id=\"email\" type=\"email\" formControlName=\"email\" /&gt;\n      &lt;/div&gt;\n    &lt;/section&gt;\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">PersonalInfoSectionComponent</span> <span class=\"k\">implements</span> <span class=\"nx\">OnInit</span><span class=\"p\">,</span> <span class=\"nx\">OnDestroy</span> <span class=\"p\">{</span>\n  <span class=\"p\">@</span><span class=\"nd\">Input</span><span class=\"p\">({</span> <span class=\"na\">required</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">})</span> <span class=\"nx\">formGroup</span><span class=\"o\">!</span><span class=\"p\">:</span> <span class=\"nx\">FormGroup</span><span class=\"p\">;</span>\n\n  <span class=\"k\">private</span> <span class=\"nx\">destroy$</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nx\">Subject</span><span class=\"o\">&lt;</span><span class=\"k\">void</span><span class=\"o\">&gt;</span><span class=\"p\">();</span>\n\n  <span class=\"nf\">ngOnInit</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"c1\">// Subscriptions are scoped to THIS section's lifecycle</span>\n    <span class=\"c1\">// Not to the root form's lifecycle</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formGroup</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">email</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span>\n      <span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">400</span><span class=\"p\">),</span>\n      <span class=\"nf\">takeUntil</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">destroy$</span><span class=\"p\">)</span>\n    <span class=\"p\">).</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">email</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n      <span class=\"c1\">// Handle email-specific side effects in isolation</span>\n    <span class=\"p\">});</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nf\">ngOnDestroy</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">destroy$</span><span class=\"p\">.</span><span class=\"nf\">next</span><span class=\"p\">();</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">destroy$</span><span class=\"p\">.</span><span class=\"nf\">complete</span><span class=\"p\">();</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>What this achieves:</strong></p>\n\n<ul>\n<li>Change detection for the personal info section is contained to <code>PersonalInfoSectionComponent</code>. A value change in the configuration section does not trigger checks in this component.</li>\n<li>Subscriptions are destroyed when the section component is destroyed, not when the root form is destroyed.</li>\n<li>The section can be independently tested with a mock <code>FormGroup</code>.</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Strategy 2 — Deferred Section Rendering with <a class=\"mentioned-user\" href=\"https://dev.to/defer\">@defer</a>\n</h2>\n\n<p><code>OnPush</code> reduces the cost of change detection cycles. <code>@defer</code> reduces the cost of the initial render by mounting sections only when needed.</p>\n\n<p>Angular 17 introduced <code>@defer</code> as a first-class template syntax for deferred loading. For large forms, it provides two key benefits:</p>\n\n<ol>\n<li>Sections not initially visible are not rendered — and their <code>FormControl</code> instances are not included in the initial change detection scope</li>\n<li>Users see a responsive above-the-fold form while below-the-fold sections load progressively\n</li>\n</ol>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight html\"><code><span class=\"c\">&lt;!-- enterprise-form.template.html --&gt;</span>\n\n<span class=\"c\">&lt;!-- Section 1: Always rendered (above the fold) --&gt;</span>\n<span class=\"nt\">&lt;app-personal-info-section</span>\n  <span class=\"na\">[formGroup]=</span><span class=\"s\">\"personalInfoGroup\"</span><span class=\"nt\">&gt;</span>\n<span class=\"nt\">&lt;/app-personal-info-section&gt;</span>\n\n<span class=\"c\">&lt;!-- Section 2: Rendered when it enters the viewport --&gt;</span>\n@defer (on viewport) {\n  <span class=\"nt\">&lt;app-configuration-section</span>\n    <span class=\"na\">[formGroup]=</span><span class=\"s\">\"configurationGroup\"</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;/app-configuration-section&gt;</span>\n} @placeholder {\n  <span class=\"nt\">&lt;app-section-skeleton</span> <span class=\"na\">label=</span><span class=\"s\">\"Configuration\"</span> <span class=\"na\">fieldCount=</span><span class=\"s\">\"6\"</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;/app-section-skeleton&gt;</span>\n}\n\n<span class=\"c\">&lt;!-- Section 3: Line items — heavy section, deferred --&gt;</span>\n@defer (on interaction(lineItemsTrigger)) {\n  <span class=\"nt\">&lt;app-line-items-section</span>\n    <span class=\"na\">[formArray]=</span><span class=\"s\">\"lineItemsArray\"</span><span class=\"nt\">&gt;</span>\n  <span class=\"nt\">&lt;/app-line-items-section&gt;</span>\n} @loading (minimum 200ms) {\n  <span class=\"nt\">&lt;div</span> <span class=\"na\">class=</span><span class=\"s\">\"loading-indicator\"</span><span class=\"nt\">&gt;</span>Loading line items...<span class=\"nt\">&lt;/div&gt;</span>\n} @placeholder {\n  <span class=\"nt\">&lt;button</span> <span class=\"na\">#lineItemsTrigger</span> <span class=\"na\">type=</span><span class=\"s\">\"button\"</span> <span class=\"na\">class=</span><span class=\"s\">\"load-section-btn\"</span><span class=\"nt\">&gt;</span>\n    Load Line Items ({{ lineItemsArray.length }} items)\n  <span class=\"nt\">&lt;/button&gt;</span>\n}\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Skeleton component for UX continuity\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// section-skeleton.component.ts</span>\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-section-skeleton</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">standalone</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;div class=\"skeleton-section\"&gt;\n      &lt;div class=\"skeleton-title\"&gt;&lt;/div&gt;\n      @for (i of fields; track i) {\n        &lt;div class=\"skeleton-field\"&gt;\n          &lt;div class=\"skeleton-label\"&gt;&lt;/div&gt;\n          &lt;div class=\"skeleton-input\"&gt;&lt;/div&gt;\n        &lt;/div&gt;\n      }\n    &lt;/div&gt;\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">SectionSkeletonComponent</span> <span class=\"p\">{</span>\n  <span class=\"p\">@</span><span class=\"nd\">Input</span><span class=\"p\">()</span> <span class=\"nx\">fieldCount</span> <span class=\"o\">=</span> <span class=\"mi\">4</span><span class=\"p\">;</span>\n  <span class=\"kd\">get</span> <span class=\"nf\">fields</span><span class=\"p\">()</span> <span class=\"p\">{</span> <span class=\"k\">return</span> <span class=\"nc\">Array</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fieldCount</span><span class=\"p\">).</span><span class=\"nf\">fill</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span> <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>What this achieves:</strong></p>\n\n<ul>\n<li>Initial render cost scales with visible field count, not total field count</li>\n<li>Users interact with the form immediately while remaining sections load progressively</li>\n<li>The <code>@placeholder</code> state provides visual continuity without empty space</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Strategy 3 — Isolated Subscription Management\n</h2>\n\n<p>Angular 16 introduced <code>takeUntilDestroyed()</code> — a cleaner alternative to the <code>Subject</code>/<code>takeUntil</code> pattern for subscription cleanup.</p>\n\n<h3>\n  \n  \n  Using takeUntilDestroyed (Angular 16+)\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// configuration-section.component.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span>\n  <span class=\"nx\">Component</span><span class=\"p\">,</span> <span class=\"nx\">Input</span><span class=\"p\">,</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">,</span> <span class=\"nx\">OnInit</span><span class=\"p\">,</span>\n  <span class=\"nx\">inject</span><span class=\"p\">,</span> <span class=\"nx\">DestroyRef</span>\n<span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">takeUntilDestroyed</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core/rxjs-interop</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">FormGroup</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/forms</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">debounceTime</span><span class=\"p\">,</span> <span class=\"nx\">distinctUntilChanged</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">rxjs/operators</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-configuration-section</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">standalone</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">ConfigurationSectionComponent</span> <span class=\"k\">implements</span> <span class=\"nx\">OnInit</span> <span class=\"p\">{</span>\n  <span class=\"p\">@</span><span class=\"nd\">Input</span><span class=\"p\">({</span> <span class=\"na\">required</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">})</span> <span class=\"nx\">formGroup</span><span class=\"o\">!</span><span class=\"p\">:</span> <span class=\"nx\">FormGroup</span><span class=\"p\">;</span>\n\n  <span class=\"k\">private</span> <span class=\"nx\">destroyRef</span> <span class=\"o\">=</span> <span class=\"nf\">inject</span><span class=\"p\">(</span><span class=\"nx\">DestroyRef</span><span class=\"p\">);</span>\n\n  <span class=\"nf\">ngOnInit</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"c1\">// Automatically unsubscribes when component is destroyed</span>\n    <span class=\"c1\">// No manual ngOnDestroy required</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formGroup</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">planTier</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span>\n      <span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">300</span><span class=\"p\">),</span>\n      <span class=\"nf\">distinctUntilChanged</span><span class=\"p\">(),</span>\n      <span class=\"nf\">takeUntilDestroyed</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">destroyRef</span><span class=\"p\">)</span>\n    <span class=\"p\">).</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">tier</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n      <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">adjustMaxUsersForTier</span><span class=\"p\">(</span><span class=\"nx\">tier</span><span class=\"p\">);</span>\n    <span class=\"p\">});</span>\n\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formGroup</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">region</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span>\n      <span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">200</span><span class=\"p\">),</span>\n      <span class=\"nf\">distinctUntilChanged</span><span class=\"p\">(),</span>\n      <span class=\"nf\">takeUntilDestroyed</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">destroyRef</span><span class=\"p\">)</span>\n    <span class=\"p\">).</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">region</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n      <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">updateCurrencyForRegion</span><span class=\"p\">(</span><span class=\"nx\">region</span><span class=\"p\">);</span>\n    <span class=\"p\">});</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">private</span> <span class=\"nf\">adjustMaxUsersForTier</span><span class=\"p\">(</span><span class=\"nx\">tier</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">maxUsersControl</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formGroup</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">maxUsers</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">limits</span><span class=\"p\">:</span> <span class=\"nb\">Record</span><span class=\"o\">&lt;</span><span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"kr\">number</span><span class=\"o\">&gt;</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n      <span class=\"na\">starter</span><span class=\"p\">:</span> <span class=\"mi\">5</span><span class=\"p\">,</span>\n      <span class=\"na\">standard</span><span class=\"p\">:</span> <span class=\"mi\">50</span><span class=\"p\">,</span>\n      <span class=\"na\">enterprise</span><span class=\"p\">:</span> <span class=\"mi\">500</span>\n    <span class=\"p\">};</span>\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">limits</span><span class=\"p\">[</span><span class=\"nx\">tier</span><span class=\"p\">])</span> <span class=\"p\">{</span>\n      <span class=\"nx\">maxUsersControl</span><span class=\"p\">?.</span><span class=\"nf\">setValue</span><span class=\"p\">(</span><span class=\"nx\">limits</span><span class=\"p\">[</span><span class=\"nx\">tier</span><span class=\"p\">],</span> <span class=\"p\">{</span> <span class=\"na\">emitEvent</span><span class=\"p\">:</span> <span class=\"kc\">false</span> <span class=\"p\">});</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"k\">private</span> <span class=\"nf\">updateCurrencyForRegion</span><span class=\"p\">(</span><span class=\"nx\">region</span><span class=\"p\">:</span> <span class=\"kr\">string</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">currencyMap</span><span class=\"p\">:</span> <span class=\"nb\">Record</span><span class=\"o\">&lt;</span><span class=\"kr\">string</span><span class=\"p\">,</span> <span class=\"kr\">string</span><span class=\"o\">&gt;</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n      <span class=\"dl\">'</span><span class=\"s1\">EU</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">EUR</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n      <span class=\"dl\">'</span><span class=\"s1\">UK</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">GBP</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n      <span class=\"dl\">'</span><span class=\"s1\">US</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">USD</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n    <span class=\"p\">};</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">currency</span> <span class=\"o\">=</span> <span class=\"nx\">currencyMap</span><span class=\"p\">[</span><span class=\"nx\">region</span><span class=\"p\">]</span> <span class=\"o\">??</span> <span class=\"dl\">'</span><span class=\"s1\">USD</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formGroup</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">currency</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nf\">setValue</span><span class=\"p\">(</span><span class=\"nx\">currency</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">emitEvent</span><span class=\"p\">:</span> <span class=\"kc\">false</span> <span class=\"p\">});</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  { emitEvent: false } — a critical detail\n</h3>\n\n<p>Notice <code>{ emitEvent: false }</code> in the <code>setValue</code> calls above. When you programmatically update a control value in response to another control's change, omitting this option creates a feedback loop:</p>\n\n<ol>\n<li>User changes <code>region</code> → subscription fires → <code>currency</code> is updated</li>\n<li>\n<code>currency.valueChanges</code> emits → any subscriber to currency fires</li>\n<li>If that subscriber updates another control, the cascade continues</li>\n</ol>\n\n<p><code>{ emitEvent: false }</code> breaks this cycle. It updates the control value without emitting a <code>valueChanges</code> event — which is the correct behaviour for programmatic, reactive updates.</p>\n\n\n\n\n<h2>\n  \n  \n  Strategy 4 — Scoped Validators\n</h2>\n\n<p>Cross-field validators should be scoped to the smallest <code>FormGroup</code> that contains all the fields they need to read. They should never be placed on a parent group to validate children they don't need.</p>\n\n<h3>\n  \n  \n  The rule\n</h3>\n\n<blockquote>\n<p>A validator belongs on the lowest <code>FormGroup</code> that contains all of its required controls.<br>\n</p>\n</blockquote>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// isolated-validators.ts</span>\n\n<span class=\"cm\">/**\n * Validates that endDate is not before startDate.\n * Scoped to a FormGroup containing only startDate and endDate.\n */</span>\n<span class=\"k\">export</span> <span class=\"kd\">function</span> <span class=\"nf\">dateRangeValidator</span><span class=\"p\">(</span>\n  <span class=\"nx\">startKey</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">startDate</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"nx\">endKey</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">endDate</span><span class=\"dl\">'</span>\n<span class=\"p\">):</span> <span class=\"nx\">ValidatorFn</span> <span class=\"p\">{</span>\n  <span class=\"k\">return </span><span class=\"p\">(</span><span class=\"nx\">group</span><span class=\"p\">:</span> <span class=\"nx\">AbstractControl</span><span class=\"p\">):</span> <span class=\"nx\">ValidationErrors</span> <span class=\"o\">|</span> <span class=\"kc\">null</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">start</span> <span class=\"o\">=</span> <span class=\"nx\">group</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"nx\">startKey</span><span class=\"p\">)?.</span><span class=\"nx\">value</span><span class=\"p\">;</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">end</span>   <span class=\"o\">=</span> <span class=\"nx\">group</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"nx\">endKey</span><span class=\"p\">)?.</span><span class=\"nx\">value</span><span class=\"p\">;</span>\n\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">start</span> <span class=\"o\">||</span> <span class=\"o\">!</span><span class=\"nx\">end</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"kc\">null</span><span class=\"p\">;</span>\n\n    <span class=\"k\">return</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">(</span><span class=\"nx\">start</span><span class=\"p\">)</span> <span class=\"o\">&gt;</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">(</span><span class=\"nx\">end</span><span class=\"p\">)</span>\n      <span class=\"p\">?</span> <span class=\"p\">{</span> <span class=\"na\">dateRange</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"nx\">start</span><span class=\"p\">,</span> <span class=\"nx\">end</span><span class=\"p\">,</span> <span class=\"na\">message</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">End date must be after start date</span><span class=\"dl\">'</span> <span class=\"p\">}</span> <span class=\"p\">}</span>\n      <span class=\"p\">:</span> <span class=\"kc\">null</span><span class=\"p\">;</span>\n  <span class=\"p\">};</span>\n<span class=\"p\">}</span>\n\n<span class=\"cm\">/**\n * Validates that quantity does not exceed available inventory.\n * Scoped to a FormGroup containing quantity and productId.\n * Async — hits inventory API only for that sub-group.\n */</span>\n<span class=\"k\">export</span> <span class=\"kd\">function</span> <span class=\"nf\">inventoryAvailabilityValidator</span><span class=\"p\">(</span>\n  <span class=\"nx\">inventoryService</span><span class=\"p\">:</span> <span class=\"nx\">InventoryService</span>\n<span class=\"p\">):</span> <span class=\"nx\">AsyncValidatorFn</span> <span class=\"p\">{</span>\n  <span class=\"k\">return </span><span class=\"p\">(</span><span class=\"nx\">group</span><span class=\"p\">:</span> <span class=\"nx\">AbstractControl</span><span class=\"p\">):</span> <span class=\"nx\">Observable</span><span class=\"o\">&lt;</span><span class=\"nx\">ValidationErrors</span> <span class=\"o\">|</span> <span class=\"kc\">null</span><span class=\"o\">&gt;</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">productId</span> <span class=\"o\">=</span> <span class=\"nx\">group</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">productId</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nx\">value</span><span class=\"p\">;</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">quantity</span>  <span class=\"o\">=</span> <span class=\"nx\">group</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">quantity</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nx\">value</span><span class=\"p\">;</span>\n\n    <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">productId</span> <span class=\"o\">||</span> <span class=\"o\">!</span><span class=\"nx\">quantity</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"k\">of</span><span class=\"p\">(</span><span class=\"kc\">null</span><span class=\"p\">);</span>\n\n    <span class=\"k\">return</span> <span class=\"nx\">inventoryService</span><span class=\"p\">.</span><span class=\"nf\">checkAvailability</span><span class=\"p\">(</span><span class=\"nx\">productId</span><span class=\"p\">,</span> <span class=\"nx\">quantity</span><span class=\"p\">).</span><span class=\"nf\">pipe</span><span class=\"p\">(</span>\n      <span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">400</span><span class=\"p\">),</span>\n      <span class=\"nf\">map</span><span class=\"p\">(</span><span class=\"nx\">available</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">available</span> <span class=\"p\">?</span> <span class=\"kc\">null</span> <span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">insufficientInventory</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"nx\">productId</span><span class=\"p\">,</span> <span class=\"nx\">quantity</span> <span class=\"p\">}</span> <span class=\"p\">}),</span>\n      <span class=\"nf\">catchError</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"k\">of</span><span class=\"p\">(</span><span class=\"kc\">null</span><span class=\"p\">))</span>\n    <span class=\"p\">);</span>\n  <span class=\"p\">};</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Applying validators to the correct scope\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// line-item-row.component.ts — validator on sub-group, not root</span>\n<span class=\"k\">private</span> <span class=\"nf\">buildLineItemGroup</span><span class=\"p\">():</span> <span class=\"nx\">FormGroup</span> <span class=\"p\">{</span>\n  <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">(</span>\n    <span class=\"p\">{</span>\n      <span class=\"na\">productId</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n      <span class=\"na\">quantity</span><span class=\"p\">:</span>  <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span>  <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)]],</span>\n      <span class=\"na\">startDate</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n      <span class=\"na\">endDate</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n      <span class=\"na\">unitPrice</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span>  <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)]],</span>\n    <span class=\"p\">},</span>\n    <span class=\"p\">{</span>\n      <span class=\"na\">validators</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nf\">dateRangeValidator</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">startDate</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">endDate</span><span class=\"dl\">'</span><span class=\"p\">)],</span>\n      <span class=\"na\">asyncValidators</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nf\">inventoryAvailabilityValidator</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">inventoryService</span><span class=\"p\">)],</span>\n      <span class=\"na\">updateOn</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">blur</span><span class=\"dl\">'</span> <span class=\"c1\">// Reduces async validator frequency significantly</span>\n    <span class=\"p\">}</span>\n  <span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><code>updateOn: 'blur'</code> on the group level is another important lever. For groups with async validators, changing the update strategy from <code>change</code> (default) to <code>blur</code> reduces API calls from \"one per keystroke\" to \"one per field exit.\"</p>\n\n\n\n\n<h2>\n  \n  \n  Strategy 5 — Virtual Scrolling for Long Field Lists\n</h2>\n\n<p>When a form contains a repeating list of rows — line items, user entries, product configurations — the CDK <code>VirtualScrollViewport</code> provides consistent rendering performance regardless of list length.</p>\n\n<h3>\n  \n  \n  Setting up the CDK virtual scroller\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>ng add @angular/cdk\n</code></pre>\n\n</div>\n\n\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// line-items-section.component.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span>\n  <span class=\"nx\">Component</span><span class=\"p\">,</span> <span class=\"nx\">Input</span><span class=\"p\">,</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">,</span> <span class=\"nx\">inject</span>\n<span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">FormArray</span><span class=\"p\">,</span> <span class=\"nx\">FormBuilder</span><span class=\"p\">,</span> <span class=\"nx\">ReactiveFormsModule</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/forms</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">ScrollingModule</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/cdk/scrolling</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-line-items-section</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">standalone</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span>\n  <span class=\"na\">imports</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nx\">ReactiveFormsModule</span><span class=\"p\">,</span> <span class=\"nx\">ScrollingModule</span><span class=\"p\">],</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;div class=\"line-items-header\"&gt;\n      &lt;h3&gt;Line Items ({{ formArray.length }})&lt;/h3&gt;\n      &lt;button type=\"button\" (click)=\"addLineItem()\"&gt;Add Item&lt;/button&gt;\n    &lt;/div&gt;\n\n    &lt;!--\n      itemSize: estimated height of each row in px\n      height must be set explicitly on the viewport\n    --&gt;\n    &lt;cdk-virtual-scroll-viewport\n      itemSize=\"64\"\n      style=\"height: 480px; overflow-y: auto;\"\n      class=\"line-items-viewport\"&gt;\n\n      &lt;div\n        *cdkVirtualFor=\"let ctrl of lineItemControls; trackBy: trackByIndex\"\n        class=\"line-item-row\"&gt;\n        &lt;app-line-item-row\n          [formGroup]=\"asFormGroup(ctrl)\"\n          (remove)=\"removeLineItem($index)\"&gt;\n        &lt;/app-line-item-row&gt;\n      &lt;/div&gt;\n\n    &lt;/cdk-virtual-scroll-viewport&gt;\n\n    &lt;div class=\"line-items-footer\"&gt;\n      &lt;span&gt;Total: {{ lineItemTotal | currency }}&lt;/span&gt;\n    &lt;/div&gt;\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">LineItemsSectionComponent</span> <span class=\"p\">{</span>\n  <span class=\"p\">@</span><span class=\"nd\">Input</span><span class=\"p\">({</span> <span class=\"na\">required</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">})</span> <span class=\"nx\">formArray</span><span class=\"o\">!</span><span class=\"p\">:</span> <span class=\"nx\">FormArray</span><span class=\"p\">;</span>\n\n  <span class=\"k\">private</span> <span class=\"nx\">fb</span> <span class=\"o\">=</span> <span class=\"nf\">inject</span><span class=\"p\">(</span><span class=\"nx\">FormBuilder</span><span class=\"p\">);</span>\n\n  <span class=\"kd\">get</span> <span class=\"nf\">lineItemControls</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formArray</span><span class=\"p\">.</span><span class=\"nx\">controls</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"kd\">get</span> <span class=\"nf\">lineItemTotal</span><span class=\"p\">():</span> <span class=\"kr\">number</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formArray</span><span class=\"p\">.</span><span class=\"nx\">value</span><span class=\"p\">.</span><span class=\"nf\">reduce</span><span class=\"p\">(</span>\n      <span class=\"p\">(</span><span class=\"nx\">sum</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">,</span> <span class=\"nx\">item</span><span class=\"p\">:</span> <span class=\"kr\">any</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">sum</span> <span class=\"o\">+</span> <span class=\"p\">((</span><span class=\"nx\">item</span><span class=\"p\">.</span><span class=\"nx\">quantity</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">item</span><span class=\"p\">.</span><span class=\"nx\">unitPrice</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)),</span>\n      <span class=\"mi\">0</span>\n    <span class=\"p\">);</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nf\">addLineItem</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formArray</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span>\n      <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n        <span class=\"na\">productId</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n        <span class=\"na\">quantity</span><span class=\"p\">:</span>  <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span>  <span class=\"p\">[</span><span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)]],</span>\n        <span class=\"na\">unitPrice</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span>  <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)],</span>\n        <span class=\"na\">startDate</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">],</span>\n        <span class=\"na\">endDate</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">],</span>\n      <span class=\"p\">})</span>\n    <span class=\"p\">);</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nf\">removeLineItem</span><span class=\"p\">(</span><span class=\"nx\">index</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formArray</span><span class=\"p\">.</span><span class=\"nf\">removeAt</span><span class=\"p\">(</span><span class=\"nx\">index</span><span class=\"p\">);</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nf\">trackByIndex</span><span class=\"p\">(</span><span class=\"nx\">index</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"nx\">index</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"nf\">asFormGroup</span><span class=\"p\">(</span><span class=\"nx\">ctrl</span><span class=\"p\">:</span> <span class=\"nx\">AbstractControl</span><span class=\"p\">):</span> <span class=\"nx\">FormGroup</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"nx\">ctrl</span> <span class=\"k\">as</span> <span class=\"nx\">FormGroup</span><span class=\"p\">;</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  What the CDK virtual scroller does\n</h3>\n\n<p>The virtual scroll viewport renders only the rows currently visible in the scrollable area — typically 10–15 rows at a time — regardless of how many rows exist in the <code>FormArray</code>. As the user scrolls, DOM nodes are recycled and reused with new data.</p>\n\n<p>The practical effect: a <code>FormArray</code> with 2,000 line items renders with the same DOM complexity as one with 20 visible rows.</p>\n\n\n\n\n<h2>\n  \n  \n  Strategy 6 — Signals Interoperability\n</h2>\n\n<p>Angular 16+ introduced Signals, and Angular 17+ provides stable <code>toSignal</code> and <code>toObservable</code> bridges for reactive interop. For form-heavy components, the <code>toSignal</code> bridge provides a clean way to derive computed state from form values without additional subscriptions.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// form-signals.component.ts</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span>\n  <span class=\"nx\">Component</span><span class=\"p\">,</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">,</span> <span class=\"nx\">inject</span><span class=\"p\">,</span> <span class=\"nx\">computed</span>\n<span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">FormBuilder</span><span class=\"p\">,</span> <span class=\"nx\">ReactiveFormsModule</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/forms</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">toSignal</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">@angular/core/rxjs-interop</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n<span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">debounceTime</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">rxjs/operators</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">selector</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">app-order-form</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n  <span class=\"na\">standalone</span><span class=\"p\">:</span> <span class=\"kc\">true</span><span class=\"p\">,</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span>\n  <span class=\"na\">imports</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nx\">ReactiveFormsModule</span><span class=\"p\">],</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;form [formGroup]=\"orderForm\"&gt;\n      &lt;!-- form fields --&gt;\n    &lt;/form&gt;\n\n    &lt;!-- Computed state derived from signals — no subscription needed --&gt;\n    &lt;div class=\"order-summary\"&gt;\n      &lt;p&gt;Subtotal: {{ subtotal() | currency }}&lt;/p&gt;\n      &lt;p&gt;Tax ({{ taxRate() }}%): {{ taxAmount() | currency }}&lt;/p&gt;\n      &lt;p&gt;Total: {{ orderTotal() | currency }}&lt;/p&gt;\n    &lt;/div&gt;\n\n    @if (isOrderValid()) {\n      &lt;button type=\"submit\"&gt;Place Order&lt;/button&gt;\n    }\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">OrderFormComponent</span> <span class=\"p\">{</span>\n  <span class=\"k\">private</span> <span class=\"nx\">fb</span> <span class=\"o\">=</span> <span class=\"nf\">inject</span><span class=\"p\">(</span><span class=\"nx\">FormBuilder</span><span class=\"p\">);</span>\n\n  <span class=\"nx\">orderForm</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n    <span class=\"na\">lineItems</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">array</span><span class=\"p\">([]),</span>\n    <span class=\"na\">taxRate</span><span class=\"p\">:</span>   <span class=\"p\">[</span><span class=\"mf\">0.1</span><span class=\"p\">],</span>\n    <span class=\"na\">discount</span><span class=\"p\">:</span>  <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"c1\">// Bridge form value changes into the signal graph</span>\n  <span class=\"c1\">// debounceTime reduces the frequency of signal emissions</span>\n  <span class=\"k\">private</span> <span class=\"nx\">formValue</span> <span class=\"o\">=</span> <span class=\"nf\">toSignal</span><span class=\"p\">(</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">orderForm</span><span class=\"p\">.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span><span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">)),</span>\n    <span class=\"p\">{</span> <span class=\"na\">initialValue</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">orderForm</span><span class=\"p\">.</span><span class=\"nx\">value</span> <span class=\"p\">}</span>\n  <span class=\"p\">);</span>\n\n  <span class=\"k\">private</span> <span class=\"nx\">formStatus</span> <span class=\"o\">=</span> <span class=\"nf\">toSignal</span><span class=\"p\">(</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">orderForm</span><span class=\"p\">.</span><span class=\"nx\">statusChanges</span><span class=\"p\">,</span>\n    <span class=\"p\">{</span> <span class=\"na\">initialValue</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">orderForm</span><span class=\"p\">.</span><span class=\"nx\">status</span> <span class=\"p\">}</span>\n  <span class=\"p\">);</span>\n\n  <span class=\"c1\">// Derived state computed from signals — no subscriptions, no ngOnDestroy</span>\n  <span class=\"nx\">subtotal</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">formValue</span><span class=\"p\">()?.</span><span class=\"nx\">lineItems</span><span class=\"p\">?.</span><span class=\"nf\">reduce</span><span class=\"p\">(</span>\n      <span class=\"p\">(</span><span class=\"na\">sum</span><span class=\"p\">:</span> <span class=\"kr\">number</span><span class=\"p\">,</span> <span class=\"na\">item</span><span class=\"p\">:</span> <span class=\"kr\">any</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">sum</span> <span class=\"o\">+</span> <span class=\"p\">((</span><span class=\"nx\">item</span><span class=\"p\">.</span><span class=\"nx\">quantity</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"nx\">item</span><span class=\"p\">.</span><span class=\"nx\">unitPrice</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)),</span>\n      <span class=\"mi\">0</span>\n    <span class=\"p\">)</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"nx\">taxRate</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span>\n    <span class=\"p\">((</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">formValue</span><span class=\"p\">()?.</span><span class=\"nx\">taxRate</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mi\">100</span><span class=\"p\">).</span><span class=\"nf\">toFixed</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n  <span class=\"p\">);</span>\n\n  <span class=\"nx\">taxAmount</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">subtotal</span><span class=\"p\">()</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">formValue</span><span class=\"p\">()?.</span><span class=\"nx\">taxRate</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n  <span class=\"p\">);</span>\n\n  <span class=\"nx\">orderTotal</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">subtotal</span><span class=\"p\">()</span> <span class=\"o\">+</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">taxAmount</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">formValue</span><span class=\"p\">()?.</span><span class=\"nx\">discount</span> <span class=\"o\">??</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n  <span class=\"p\">);</span>\n\n  <span class=\"nx\">isOrderValid</span> <span class=\"o\">=</span> <span class=\"nf\">computed</span><span class=\"p\">(()</span> <span class=\"o\">=&gt;</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">formStatus</span><span class=\"p\">()</span> <span class=\"o\">===</span> <span class=\"dl\">'</span><span class=\"s1\">VALID</span><span class=\"dl\">'</span>\n  <span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>What this achieves:</strong></p>\n\n<ul>\n<li>\n<code>computed</code> signals are lazy — they only recalculate when their dependencies change</li>\n<li>No <code>ngOnDestroy</code> needed for the derived state — signals are garbage collected with their owning component</li>\n<li>The template reads synchronously from signals — no async pipe, no null checks for loading states</li>\n<li>The signal graph is explicit and traceable — you can see exactly what <code>orderTotal</code> depends on</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Before vs. After: The Full Architecture Comparison\n</h2>\n\n<h3>\n  \n  \n  Before: Monolithic FormGroup\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// ❌ Anti-pattern: everything in one place</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"c1\">// No OnPush — default change detection</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`&lt;form [formGroup]=\"rootForm\"&gt;...&lt;/form&gt;`</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">MonolithicFormComponent</span> <span class=\"k\">implements</span> <span class=\"nx\">OnInit</span> <span class=\"p\">{</span>\n  <span class=\"nx\">rootForm</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n    <span class=\"c1\">// 1,200 controls in one flat tree</span>\n    <span class=\"na\">firstName</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">''</span><span class=\"p\">,</span> <span class=\"nx\">Validators</span><span class=\"p\">.</span><span class=\"nx\">required</span><span class=\"p\">],</span>\n    <span class=\"c1\">// ... 1,199 more</span>\n  <span class=\"p\">},</span> <span class=\"p\">{</span>\n    <span class=\"c1\">// Cross-field validators on the root group</span>\n    <span class=\"na\">validators</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"nx\">dateRangeValidator</span><span class=\"p\">,</span> <span class=\"nx\">regionValidator</span><span class=\"p\">,</span> <span class=\"nx\">inventoryValidator</span><span class=\"p\">]</span>\n  <span class=\"p\">});</span>\n\n  <span class=\"nf\">ngOnInit</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"c1\">// Subscriptions on the root form — never explicitly destroyed</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">v</span> <span class=\"o\">=&gt;</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">autosave</span><span class=\"p\">(</span><span class=\"nx\">v</span><span class=\"p\">));</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">rootForm</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">region</span><span class=\"dl\">'</span><span class=\"p\">).</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">r</span> <span class=\"o\">=&gt;</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">updateCurrency</span><span class=\"p\">(</span><span class=\"nx\">r</span><span class=\"p\">));</span>\n    <span class=\"c1\">// ... 8 more subscriptions added over 12 months</span>\n  <span class=\"p\">}</span>\n\n  <span class=\"c1\">// No ngOnDestroy — subscriptions are never cleaned up</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>Problems:</strong></p>\n\n<ul>\n<li>Default change detection: every keystroke checks all 1,200 controls</li>\n<li>Root-level validators: fire on every change to any control</li>\n<li>Unmanaged subscriptions: accumulate with each component creation</li>\n<li>Flat structure: impossible to test sections in isolation</li>\n<li>Team scalability: every developer must understand the entire form</li>\n</ul>\n\n<h3>\n  \n  \n  After: Modular Architecture\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"c1\">// ✅ Scalable pattern: bounded modules</span>\n\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span>\n  <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span><span class=\"p\">,</span> <span class=\"c1\">// OnPush at root</span>\n  <span class=\"na\">template</span><span class=\"p\">:</span> <span class=\"s2\">`\n    &lt;form [formGroup]=\"rootForm\"&gt;\n      &lt;!-- Section 1: always visible --&gt;\n      &lt;app-personal-info-section [formGroup]=\"personalInfoGroup\" /&gt;\n\n      &lt;!-- Section 2: deferred until viewport --&gt;\n      @defer (on viewport) {\n        &lt;app-configuration-section [formGroup]=\"configurationGroup\" /&gt;\n      } @placeholder {\n        &lt;app-section-skeleton /&gt;\n      }\n\n      &lt;!-- Section 3: line items with virtual scroll --&gt;\n      @defer (on interaction(trigger)) {\n        &lt;app-line-items-section [formArray]=\"lineItemsArray\" /&gt;\n      } @placeholder {\n        &lt;button #trigger&gt;Load Line Items&lt;/button&gt;\n      }\n    &lt;/form&gt;\n  `</span>\n<span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">ModularFormComponent</span> <span class=\"p\">{</span>\n  <span class=\"nx\">rootForm</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">group</span><span class=\"p\">({</span>\n    <span class=\"na\">personalInfo</span><span class=\"p\">:</span>  <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">buildPersonalInfoGroup</span><span class=\"p\">(),</span>   <span class=\"c1\">// Bounded group</span>\n    <span class=\"na\">configuration</span><span class=\"p\">:</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">buildConfigurationGroup</span><span class=\"p\">(),</span>  <span class=\"c1\">// Bounded group</span>\n    <span class=\"na\">lineItems</span><span class=\"p\">:</span>     <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">fb</span><span class=\"p\">.</span><span class=\"nf\">array</span><span class=\"p\">([]),</span>               <span class=\"c1\">// FormArray, virtually scrolled</span>\n  <span class=\"p\">});</span>\n<span class=\"p\">}</span>\n\n<span class=\"c1\">// Each section: OnPush, scoped subscriptions, isolated validators</span>\n<span class=\"p\">@</span><span class=\"nd\">Component</span><span class=\"p\">({</span> <span class=\"na\">changeDetection</span><span class=\"p\">:</span> <span class=\"nx\">ChangeDetectionStrategy</span><span class=\"p\">.</span><span class=\"nx\">OnPush</span> <span class=\"p\">})</span>\n<span class=\"k\">export</span> <span class=\"kd\">class</span> <span class=\"nc\">ConfigurationSectionComponent</span> <span class=\"k\">implements</span> <span class=\"nx\">OnInit</span> <span class=\"p\">{</span>\n  <span class=\"p\">@</span><span class=\"nd\">Input</span><span class=\"p\">({</span> <span class=\"na\">required</span><span class=\"p\">:</span> <span class=\"kc\">true</span> <span class=\"p\">})</span> <span class=\"nx\">formGroup</span><span class=\"o\">!</span><span class=\"p\">:</span> <span class=\"nx\">FormGroup</span><span class=\"p\">;</span>\n  <span class=\"k\">private</span> <span class=\"nx\">destroyRef</span> <span class=\"o\">=</span> <span class=\"nf\">inject</span><span class=\"p\">(</span><span class=\"nx\">DestroyRef</span><span class=\"p\">);</span>\n\n  <span class=\"nf\">ngOnInit</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n    <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">formGroup</span><span class=\"p\">.</span><span class=\"nf\">get</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">planTier</span><span class=\"dl\">'</span><span class=\"p\">)?.</span><span class=\"nx\">valueChanges</span><span class=\"p\">.</span><span class=\"nf\">pipe</span><span class=\"p\">(</span>\n      <span class=\"nf\">debounceTime</span><span class=\"p\">(</span><span class=\"mi\">300</span><span class=\"p\">),</span>\n      <span class=\"nf\">takeUntilDestroyed</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">destroyRef</span><span class=\"p\">)</span> <span class=\"c1\">// Auto-cleanup</span>\n    <span class=\"p\">).</span><span class=\"nf\">subscribe</span><span class=\"p\">(</span><span class=\"nx\">tier</span> <span class=\"o\">=&gt;</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">adjustMaxUsers</span><span class=\"p\">(</span><span class=\"nx\">tier</span><span class=\"p\">));</span>\n  <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><strong>What changed:</strong></p>\n\n<ul>\n<li>OnPush at every level: change detection is contained to each section boundary</li>\n<li>Deferred rendering: initial render cost is proportional to visible sections, not total sections</li>\n<li>Scoped subscriptions: each section owns and cleans up its own subscriptions</li>\n<li>Isolated validators: cross-field validation is scoped to the minimum containing group</li>\n<li>Team scalability: sections are independently developable, testable, and deployable</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  The Senior Engineer Framing\n</h2>\n\n<p>The patterns in this post are not Angular-specific optimisations in the narrow sense. They are the application of standard software engineering principles — bounded contexts, separation of concerns, explicit ownership — to the domain of reactive forms.</p>\n\n<p>A <code>FormGroup</code> without explicit boundaries is a module without explicit dependencies. A validator on a root group is a global function with implicit inputs. A subscription without explicit cleanup is a resource without an owner.</p>\n\n<p>The performance improvements that result from applying these patterns are real and measurable. But the more durable benefit is <strong>architectural</strong>: forms that are segmented, isolated, and scoped are easier to reason about, easier to test, easier to maintain, and easier to hand off to another developer.</p>\n\n<blockquote>\n<p><strong>\"Large forms should behave like modular systems — not giant components.\"</strong></p>\n</blockquote>\n\n<p>The forms in your enterprise applications will grow. The requirements will change. The team will turn over. The architecture you establish in week one determines whether those changes are routine or painful.</p>\n\n\n\n\n<h2>\n  \n  \n  Key Takeaways\n</h2>\n\n<p><strong>On rendering:</strong></p>\n\n<ul>\n<li>Apply <code>OnPush</code> change detection to every form section component</li>\n<li>This contains change detection cycles to the component boundary — changes in other sections don't trigger unnecessary checks</li>\n</ul>\n\n<p><strong>On validation:</strong></p>\n\n<ul>\n<li>Scope validators to the lowest <code>FormGroup</code> that contains all required controls</li>\n<li>Use <code>updateOn: 'blur'</code> for groups with async validators to reduce API call frequency</li>\n<li>Extract validator logic into standalone, named functions that can be unit tested in isolation</li>\n</ul>\n\n<p><strong>On subscriptions:</strong></p>\n\n<ul>\n<li>Subscribe at the sub-form component level, not at the root form level</li>\n<li>Use <code>takeUntilDestroyed(this.destroyRef)</code> for automatic cleanup (Angular 16+)</li>\n<li>Use <code>{ emitEvent: false }</code> when programmatically updating controls in response to other controls</li>\n</ul>\n\n<p><strong>On rendering performance:</strong></p>\n\n<ul>\n<li>Use <code>@defer (on viewport)</code> to mount sections only when they enter the viewport</li>\n<li>Use <code>CdkVirtualScrollViewport</code> for repeating row lists with more than ~50 rows</li>\n<li>Render only what the user can currently see or interact with</li>\n</ul>\n\n<p><strong>On state management:</strong></p>\n\n<ul>\n<li>Use <code>toSignal</code> to bridge form observables into the signal graph for derived state</li>\n<li>Prefer <code>computed</code> signals over <code>subscribe</code> for derived values — they're lazy and self-cleaning</li>\n</ul>\n\n<p><strong>On team scalability:</strong></p>\n\n<ul>\n<li>Treat each form section as a bounded module with an explicit interface</li>\n<li>Sections that can be independently tested can be independently developed</li>\n<li>The architecture that handles 1,000 fields also handles the team adding the next 200</li>\n</ul>\n\n\n\n\n<h2>\n  \n  \n  Wrapping Up\n</h2>\n\n<p>Enterprise Angular forms become difficult to manage not because Reactive Forms is insufficient, but because the architectural patterns that work at small scale don't hold at large scale.</p>\n\n<p>The shift is conceptual: stop thinking about a large form as a <code>FormGroup</code> with many controls, and start thinking about it as a system of bounded modules that each own their rendering, validation, and state responsibilities.</p>\n\n<p>The Angular tooling to support this architecture is all present and stable: <code>OnPush</code>, <code>@defer</code>, <code>FormArray</code>, <code>CdkVirtualScrollViewport</code>, <code>takeUntilDestroyed</code>, <code>toSignal</code>, and standalone components. The decisions about how to apply them are yours.</p>\n\n\n\n\n<p><em>Have you hit performance issues with large Angular forms in production? What patterns worked for your team? Drop a comment below — I read every one.</em></p>\n\n\n\n\n<p>📌 <strong>More From Me</strong><br>\nI share daily insights on web development, architecture, and frontend ecosystems.<br>\nFollow me here on Dev.to, and connect on LinkedIn for professional discussions.</p>\n\n<p><strong>🌐 Connect With Me</strong><br>\nIf you enjoyed this post and want more insights on scalable frontend systems, follow my work across platforms:</p>\n\n<p>🔗 <a href=\"https://www.linkedin.com/in/abdelaaziz-ouakala/\" rel=\"noopener noreferrer\">LinkedIn </a>— Professional discussions, architecture breakdowns, and engineering insights.<br>\n📸 <a href=\"https://www.instagram.com/ouakala_abdelaaziz/\" rel=\"noopener noreferrer\">Instagram </a>— Visuals, carousels, and design‑driven posts under the Terminal Elite aesthetic.<br>\n🧠 <a href=\"https://ouakala-abdelaaziz.epizy.com/\" rel=\"noopener noreferrer\">Website </a>— Articles, tutorials, and project showcases.<br>\n🎥 <a href=\"https://www.youtube.com/@ProgrammingMasteryAcademy\" rel=\"noopener noreferrer\">YouTube </a>— Deep‑dive videos and live coding sessions.</p>\n\n\n\n\n\n\n\n<p><strong>Tags:</strong> <code>#angular</code> <code>#webdev</code> <code>#typescript</code> <code>#frontend</code></p>","score":7},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"We built credential isolation and automated closed-loop response into an API security SDK — here is why and how","link":"https://dev.to/ndegwaduncan/we-built-credential-isolation-and-automated-closed-loop-response-into-an-api-security-sdk-here-is-49c2","pubDate":"Fri, 22 May 2026 16:59:31 +0000","description":"<p>Security monitoring tools store your real session tokens.</p>\n\n<p>Every JWT. Every credential. For every user, across every application they protect — sitting in a third-party database. If that vendor is breached, every application they monitor is compromised. This is not a theoretical risk. It is the architecture of almost every API security platform on the market today.</p>\n\n<p>We spent 18 months building a different approach. DevFortress launches today.</p>\n\n\n\n\n<h2>\n  \n  \n  The Core Problem: Credential Accumulation\n</h2>\n\n<p>When you integrate a traditional API security monitor, your SDK sends real session tokens to the monitoring platform for analysis. The platform logs them, correlates them, runs ML models on them. The platform needs the real token to do its job.</p>\n\n<p>This means the security tool designed to protect you is also the largest single point of credential exposure in your infrastructure.</p>\n\n<h2>\n  \n  \n  The Fix: Credential Isolation\n</h2>\n\n<p>When the DevFortress SDK intercepts a session token, it generates a completely random alias — no mathematical relationship to the real token — and sends only that alias to the platform. The real token never leaves your application boundary.</p>\n\n<p>If DevFortress is breached tomorrow: attackers get a database of random strings that authenticate nothing, anywhere.</p>\n\n<h2>\n  \n  \n  The Second Problem: Alert Fatigue\n</h2>\n\n<p>Traditional security tools detect a threat and send you an alert. The attacker's session stays active while you wake up, read the alert, triage it, and decide what to do. Average human response time: hours. Time an attacker needs to exfiltrate data: seconds.</p>\n\n<p>DevFortress closes the loop automatically. When a threat is detected, the platform fires a signed HMAC-SHA256 webhook to your application. Your application revokes the session, blocks the IP, and confirms back to the platform. The entire cycle completes in under 2 seconds. No human in the loop.</p>\n\n<h2>\n  \n  \n  AI Agent Security\n</h2>\n\n<p>AI agents introduced a new attack surface. A LangChain or AutoGen agent running in production holds real API keys with broad scope. One successful prompt injection and an attacker has those credentials.</p>\n\n<p>DevFortress for agents:</p>\n\n<ul>\n<li>\n<strong>AgentScopeEnforcer</strong> — define a tool allowlist per agent; block any unsanctioned tool call before execution</li>\n<li>\n<strong>Token aliasing for agents</strong> — master API keys never exposed; the agent operates on scoped aliases</li>\n<li>\n<strong>Auto-quarantine</strong> — compromised agent is isolated with full tool-call sequence preserved as evidence</li>\n</ul>\n\n<h2>\n  \n  \n  What We Validated\n</h2>\n\n<ul>\n<li>133/133 attack events blocked</li>\n<li>26 distinct attack scenarios</li>\n<li>7 reference applications</li>\n<li>100% SDK pass rate</li>\n<li>Sub-millisecond internal blocking (&lt;1ms)</li>\n<li>4 patent filings, 34 inventions</li>\n<li>Patent Pending — KIPI KE/P/2026/005970–005973</li>\n</ul>\n\n<h2>\n  \n  \n  Three Ways to Start\n</h2>\n\n<p><strong>Install the SDK (free):</strong><br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>npm <span class=\"nb\">install </span>devfortress-sdk@4.8.0\n</code></pre>\n\n</div>\n\n\n\n<p><strong>Read the full architecture writeup:</strong><br>\n<a href=\"https://www.devfortress.net/blog/devfortress-launch\" rel=\"noopener noreferrer\">https://www.devfortress.net/blog/devfortress-launch</a></p>\n\n<p><strong>Get the Master Edition textbook</strong> (full architecture deep-dive + interactive demos):<br>\n<a href=\"https://devfortress.gumroad.com/l/master-edition\" rel=\"noopener noreferrer\">https://devfortress.gumroad.com/l/master-edition</a></p>\n\n<p><strong>Subscribe to the weekly security journal:</strong><br>\n<a href=\"https://devfortress.substack.com\" rel=\"noopener noreferrer\">https://devfortress.substack.com</a></p>\n\n\n\n\n<p>Happy to go deep on any of the inventions in the comments — especially the credential isolation mechanism, the closed-loop webhook architecture, or the AI agent scope enforcement.</p>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"GitHub recognized as a Leader in the Gartner® Magic Quadrant™ for Enterprise AI Coding Agents for the third year in a row","link":"https://github.blog/ai-and-ml/github-copilot/github-recognized-as-a-leader-in-the-gartner-magic-quadrant-for-enterprise-ai-coding-agents-for-the-third-year-in-a-row/","pubDate":"Fri, 22 May 2026 16:10:21 +0000","description":"<p>We are committed to empowering every developer by building an open, secure, and AI-powered platform that defines the future of software development.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/github-recognized-as-a-leader-in-the-gartner-magic-quadrant-for-enterprise-ai-coding-agents-for-the-third-year-in-a-row/\">GitHub recognized as a Leader in the Gartner® Magic Quadrant™ for Enterprise AI Coding Agents for the third year in a row</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://medium.com/feed/tag/typescript","sourceHost":"medium.com","title":"Advanced TypeScript Patterns Every Senior Developer Uses in 2026","link":"https://medium.com/@tarxemo/advanced-typescript-patterns-every-senior-developer-uses-in-2026-68c5a4d51943?source=rss------typescript-5","pubDate":"Fri, 22 May 2026 15:31:20 GMT","description":"<div class=\"medium-feed-item\"><p class=\"medium-feed-image\"><a href=\"https://medium.com/@tarxemo/advanced-typescript-patterns-every-senior-developer-uses-in-2026-68c5a4d51943?source=rss------typescript-5\"><img src=\"https://cdn-images-1.medium.com/max/1370/1*hzzDUeHxfmK853xWQFTxxA.png\" width=\"1370\"></a></p><p class=\"medium-feed-snippet\">Advanced TypeScript Patterns Every Senior Developer Uses in 2026</p><p class=\"medium-feed-link\"><a href=\"https://medium.com/@tarxemo/advanced-typescript-patterns-every-senior-developer-uses-in-2026-68c5a4d51943?source=rss------typescript-5\">Continue reading on Medium »</a></p></div>","score":3},{"source":"https://www.smashingmagazine.com/feed","sourceHost":"smashingmagazine.com","title":"Four Levels Of Customer Understanding","link":"https://smashingmagazine.com/2026/05/four-levels-customer-understanding/","pubDate":"Fri, 22 May 2026 13:00:00 GMT","description":"What people say, feel, think, and do are often very different things. To understand the underlying reasons for user behavior, it helps to look beyond the surface and explore hidden motivations, root causes, and the different layers of reality that shape how people act. Brought to you by <a href=\"https://measure-ux.com/\">Measuring UX Impact</a>, **friendly video course on UX** and design patterns by Vitaly.","score":2},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"Bundle size creep goes unnoticed until PR review — so I built `buildwatch`","link":"https://dev.to/mumicrotools/bundle-size-creep-goes-unnoticed-until-pr-review-so-i-built-buildwatch-52im","pubDate":"Fri, 22 May 2026 12:45:45 +0000","description":"<h2>\n  \n  \n  The problem\n</h2>\n\n<p>During development, bundle size creep goes unnoticed — there is no fast way to see that a code change just added 50 KB to your dist folder until it surfaces in a PR review or a production performance regression.</p>\n\n<p>If you've hit this before, you know how it goes — you end up finding out during a PR review, or worse, after a production deploy.</p>\n\n<h2>\n  \n  \n  As a solution, I created <code>buildwatch</code>\n</h2>\n\n<p>Watch your build output directory and report file size changes on every rebuild</p>\n\n<p>It's zero-dependency Node.js, so you can run it immediately without installing anything:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>npx buildwatch ./dist\n</code></pre>\n\n</div>\n\n\n\n<p>Output:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight console\"><code><span class=\"go\">buildwatch v1.0.0\nWatching /projects/myapp/dist\nInitial state: 3 files, 1.0 MB total\n\n[10:23:41] 2 files changed\n\n  main.js          136.4 KB  +12.1 KB (+9.7%)\n  main.css           8.3 KB  +0.1 KB (+1.2%)\n  vendor.js        892.1 KB  no change\n\n  Total: 1,036.8 KB  +12.2 KB (+1.2%)\n</span></code></pre>\n\n</div>\n\n\n\n<h2>\n  \n  \n  How it works\n</h2>\n\n<p>Pure Node.js using fs.watch() for directory monitoring and fs.stat() for file sizes, with 300ms debouncing to handle rapid build events, printing ANSI-colored per-file and total size deltas.</p>\n\n<h2>\n  \n  \n  Why I built it\n</h2>\n\n<p>Found repeated complaints in r/webdev and r/node about not noticing bundle size regressions until PR review or production. Existing tools like bundlesize and size-limit require CI config changes or build plugin integration. No popular zero-dep tool gives you instant terminal feedback during local development — a file watcher that just runs alongside your dev server fills this gap cleanly.</p>\n\n<h2>\n  \n  \n  Try it\n</h2>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>npx buildwatch <span class=\"nt\">--help</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n\n<p>Part of <a href=\"https://anishpunati.github.io/mumicro/\" rel=\"noopener noreferrer\">µ micro</a> — one new developer tool, shipped every day. All tools are zero-dependency Node.js and run instantly with <code>npx</code>.</p>","score":3},{"source":"https://dev.to/feed/tag/node","sourceHost":"dev.to","title":"How I Built a Drop-In Proxy to Slash My OpenAI Bills by 20%+ Automatically","link":"https://dev.to/buddyhenderson/how-i-built-a-drop-in-proxy-to-slash-my-openai-bills-by-20-automatically-3kdn","pubDate":"Fri, 22 May 2026 11:27:40 +0000","description":"<p>Every developer building with Large Language Models eventually hits the same painful reality: the API bill always catches up to you. Between massive system instructions, multi-turn chat histories, and heavy Retrieval-Augmented Generation (RAG) contexts, prompt sizes explode fast. And since LLM providers charge you per token for every single request, you are constantly paying a premium for linguistic filler words (the, is, and, available) that the AI models don't even need to understand your intent.</p>\n\n<p>I wanted a way to automatically strip out prompt waste and cut my API costs without rewriting my entire application logic.</p>\n\n<p>So, I built and shipped llm-cost-optimizer-node—a zero-config, drop-in client wrapper that intercepts outgoing messages, optimizes them in the cloud, and pipes them seamlessly to your LLM provider.</p>\n\n<h2>\n  \n  \n  The Architecture: How it Works Under the Hood\n</h2>\n\n<p>The entire philosophy of this tool is zero structural friction. Instead of forcing you to manually pass every string through an optimization utility before a fetch request, it acts as a local proxy wrapper around your initialized client instance.</p>\n\n<ol>\n<li><p><strong>Intercept:</strong> The wrapper captures the outgoing payload right as chat.completions.create is fired.</p></li>\n<li><p><strong>Optimize:</strong> It securely runs the text blocks through an engine to handle minification, stop-word stripping, or stemming.</p></li>\n<li><p><strong>Log &amp; Pipe:</strong> It prints the exact token savings straight to your development terminal and forwards the lean prompt to the LLM. </p></li>\n</ol>\n\n<h2>\n  \n  \n  Show Me the Code\n</h2>\n\n<p>Integrating it takes exactly three lines of code. You wrap your native client instance once, and leave the rest of your codebase completely untouched.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</span> <span class=\"p\">{</span> <span class=\"nx\">OpenAI</span> <span class=\"p\">}</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">openai</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n<span class=\"kd\">const</span> <span class=\"p\">{</span> <span class=\"nx\">wrapClient</span> <span class=\"p\">}</span> <span class=\"o\">=</span> <span class=\"nf\">require</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">llm-cost-optimizer-node</span><span class=\"dl\">'</span><span class=\"p\">);</span>\n\n<span class=\"c1\">// 1. Initialize and wrap your standard client instance</span>\n<span class=\"kd\">const</span> <span class=\"nx\">openai</span> <span class=\"o\">=</span> <span class=\"nf\">wrapClient</span><span class=\"p\">(</span><span class=\"k\">new</span> <span class=\"nc\">OpenAI</span><span class=\"p\">({</span> <span class=\"na\">apiKey</span><span class=\"p\">:</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">OPENAI_API_KEY</span> <span class=\"p\">}),</span> <span class=\"p\">{</span>\n    <span class=\"na\">rapidApiKey</span><span class=\"p\">:</span> <span class=\"nx\">process</span><span class=\"p\">.</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">RAPID_API_KEY</span><span class=\"p\">,</span>\n    <span class=\"na\">strategy</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"dl\">\"</span><span class=\"s2\">minify</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"dl\">\"</span><span class=\"s2\">strip_stopwords</span><span class=\"dl\">\"</span><span class=\"p\">]</span> \n<span class=\"p\">});</span>\n\n<span class=\"c1\">// 2. Run your existing production code exactly as before!</span>\n<span class=\"kd\">const</span> <span class=\"nx\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">openai</span><span class=\"p\">.</span><span class=\"nx\">chat</span><span class=\"p\">.</span><span class=\"nx\">completions</span><span class=\"p\">.</span><span class=\"nf\">create</span><span class=\"p\">({</span>\n    <span class=\"na\">model</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">gpt-4o</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n    <span class=\"na\">messages</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n        <span class=\"p\">{</span> <span class=\"na\">role</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">system</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">content</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">You are a warehouse assistant.</span><span class=\"dl\">\"</span> <span class=\"p\">},</span>\n        <span class=\"p\">{</span> <span class=\"na\">role</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">user</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"na\">content</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">The ergonomic office chair is highly accessible and available in warehouse-4 right now.</span><span class=\"dl\">\"</span> <span class=\"p\">}</span>\n    <span class=\"p\">]</span>\n<span class=\"p\">});</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  🟢 The Terminal Output\n</h3>\n\n<p>The moment that request executes, your console streams live telemetry showing you exactly how much money and context window you just saved:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>--- [Optimizer Proxy] Intercepting Outgoing Messages... ---\n🟢 [Metrics] Msg 0 | Slashed: 35 -&gt; 28 tokens (20.00% Saved)\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Engineering for Production: Fail-Safe Execution\n</h3>\n\n<p>When building developer infrastructure, application uptime is non-negotiable. I didn't want a network hiccup or an expired API key to crash a production system.</p>\n\n<p>To solve this, the SDK is built with a strict fail-safe guardrail loop:<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"k\">try</span> <span class=\"p\">{</span>\n    <span class=\"kd\">const</span> <span class=\"nx\">compressed</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nf\">callOptimizationEngine</span><span class=\"p\">(</span><span class=\"nx\">text</span><span class=\"p\">);</span>\n    <span class=\"k\">return</span> <span class=\"nx\">compressed</span><span class=\"p\">;</span>\n<span class=\"p\">}</span> <span class=\"k\">catch </span><span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">warn</span><span class=\"p\">(</span><span class=\"s2\">`⚠️ [Optimizer Proxy Warning] Compression failed: </span><span class=\"p\">${</span><span class=\"nx\">error</span><span class=\"p\">.</span><span class=\"nx\">message</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">);</span>\n    <span class=\"k\">return</span> <span class=\"nx\">originalText</span><span class=\"p\">;</span> <span class=\"c1\">// Transparent fallback fallback execution</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>If your network goes down or the gateway API hits a rate limit, the client wrapper instantly catches the exception, prints a subtle warning to your server logs, and <strong>safely drops back to forwarding your original untouched prompt to your LLM provider.</strong> Your application production uptime remains completely bulletproof.</p>\n\n<h3>\n  \n  \n  Try It Out!\n</h3>\n\n<p>The package is fully open-source and live on the global npm registry right now.</p>\n\n<ul>\n<li>NPM: npm install llm-cost-optimizer-node</li>\n<li>GitHub:\n<a href=\"https://github.com/Buddy-Henderson/llm-cost-optimizer-node\" rel=\"noopener noreferrer\">https://github.com/Buddy-Henderson/llm-cost-optimizer-node</a>\n</li>\n</ul>\n\n<p>I'm currently working on adding specialized optimization profiles for heavy <strong>RAG workflows</strong> and <strong>complex Agent state loops.</strong></p>\n\n<p>I'd love to hear your thoughts! What optimization strategies are you using to keep your production LLM bills under control? Drop a comment below!</p>","score":5},{"source":"https://medium.com/feed/tag/typescript","sourceHost":"medium.com","title":"Richfolio, three months in: AI architecture in production","link":"https://furic.medium.com/richfolio-three-months-in-ai-architecture-in-production-bcdadf200851?source=rss------typescript-5","pubDate":"Fri, 22 May 2026 10:20:58 GMT","description":"<div class=\"medium-feed-item\"><p class=\"medium-feed-image\"><a href=\"https://furic.medium.com/richfolio-three-months-in-ai-architecture-in-production-bcdadf200851?source=rss------typescript-5\"><img src=\"https://cdn-images-1.medium.com/max/1693/1*b6A_j2U4ZDmP7_DglaJQOQ.png\" width=\"1693\"></a></p><p class=\"medium-feed-snippet\">From v1.0 to v1.6 &#x2014; what I rebuilt, what I borrowed, and what&#x2019;s running in production for $0/month</p><p class=\"medium-feed-link\"><a href=\"https://furic.medium.com/richfolio-three-months-in-ai-architecture-in-production-bcdadf200851?source=rss------typescript-5\">Continue reading on Medium »</a></p></div>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Beyond the engine: 10 open source projects shaping how games actually get made","link":"https://github.blog/open-source/gaming/beyond-the-engine-10-open-source-projects-shaping-how-games-actually-get-made/","pubDate":"Thu, 21 May 2026 18:00:00 +0000","description":"<p>Check out these 10 open source tools that help game developers create art, animation, levels, audio, dialogue, debug UIs, and engine-ready assets.</p>\n<p>The post <a href=\"https://github.blog/open-source/gaming/beyond-the-engine-10-open-source-projects-shaping-how-games-actually-get-made/\">Beyond the engine: 10 open source projects shaping how games actually get made</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://dev.to/feed/tag/vue","sourceHost":"dev.to","title":"How to Make Large Time-Series Charts Smooth in Vue.js + ApexCharts (and fix Zoom & Scroll behavior issues)","link":"https://dev.to/aaronestrada/how-to-make-large-time-series-charts-smooth-in-vuejs-apexcharts-and-fix-zoom-scroll-behavior-3k6n","pubDate":"Thu, 21 May 2026 17:14:49 +0000","description":"<p>Over the last few weeks, I've been working on a front-end application project built with <a href=\"https://vuejs.org\" rel=\"noopener noreferrer\">Vue.js</a> and <a href=\"https://apexcharts.com\" rel=\"noopener noreferrer\">ApexCharts</a> that displays time-series data about different sensors installed in different rooms and buildings.</p>\n\n<p><a href=\"https://apexcharts.com\" rel=\"noopener noreferrer\">ApexCharts</a> is an excellent library for creating interactive charts, and integrating it in <a href=\"https://vuejs.org\" rel=\"noopener noreferrer\">Vue.js</a> is really a piece of cake. However, when it comes to displaying a time-series chart with thousands of points, the performance can suffer, sometimes causing the page to freeze during the rendering or when the user zooms or navigates through the data.</p>\n\n<h2>\n  \n  \n  Downsampling data\n</h2>\n\n<p>To solve this issue, it is recommended to downsample the data to reduce the number of displayed points, especially when the selected time range is large enough that highly granular data is unnecessary. If the user later needs to inspect detailed data, the application can switch back to raw data after zooming in.</p>\n\n<p>The criteria for switching between downsampled and raw data depends on the use case. It can be based on the zoom level, the selected date range, or the number of points to display.</p>\n\n<h2>\n  \n  \n  The problem\n</h2>\n\n<p>Switching between raw and downsampled data also requires filtering the points to display according to the selected time range. And this is where the problem appears: once you filter data, the zooming and scrolling events of the chart stop working properly when using the mouse scroll. In the era of AI and agent-based coding, solving the issue might seem trivial. But after several attempts and different interactions, I could not find a complete working example that fully<br>\nsolved the problem.</p>\n<h2>\n  \n  \n  The solution\n</h2>\n\n<p>Here, I would like to propose a solution that includes the use of downsampled data when the number of displayed points is high enough to avoid performance issues with a line chart and switches to raw data otherwise. The solution is based on the <code>zoomed</code>, <code>scrolled</code> and <code>beforeResetZoom</code> chart events while still relying on the reactive state of the Vue.js component.</p>\n\n<p>To make the solution work correctly, it is also necessary to include a hidden dataset containing the minimum and maximum dates of the complete time series, both with null values. This hidden dataset allows the chart to zoom out completely and pan across the entire x-axis using both the chart controls and the mouse wheel.</p>\n\n<p>Without this additional dataset, these interactions only work correctly when using the built-in chart buttons.</p>\n<h2>\n  \n  \n  How to do it?\n</h2>\n<h3>\n  \n  \n  The setup\n</h3>\n\n<p>In a real application, the data usually comes from an API or another external source. After loading the data, each dataset can be downsampled and stored in the component’s reactive state.</p>\n\n<p>Alternatively, downsampling could also be performed in the backend before sending the data to the frontend, depending on how much control you have over the application architecture.</p>\n\n<p>For this example, I generate three random temperature datasets with 10,000 points each and downsample them using the Largest-Triangle Three-Buckets (LTTB) algorithm provided by the downsample package, reducing each dataset to 700 points.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"k\">import</span> <span class=\"p\">{</span><span class=\"nx\">LTTB</span><span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">'</span><span class=\"s1\">downsample/methods/LTTB</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n\n<span class=\"kd\">let</span> <span class=\"nx\">northStationData</span> <span class=\"o\">=</span> <span class=\"nf\">generateTemperaturePoints</span><span class=\"p\">({</span>\n    <span class=\"na\">startDate</span><span class=\"p\">:</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2026-01-01T00:00:00</span><span class=\"dl\">'</span><span class=\"p\">),</span>\n    <span class=\"na\">count</span><span class=\"p\">:</span> <span class=\"mi\">10000</span><span class=\"p\">,</span>\n    <span class=\"na\">intervalMs</span><span class=\"p\">:</span> <span class=\"mi\">60</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">,</span>\n    <span class=\"na\">baseTemp</span><span class=\"p\">:</span> <span class=\"mi\">15</span><span class=\"p\">,</span>\n    <span class=\"na\">dailyAmplitude</span><span class=\"p\">:</span> <span class=\"mf\">6.5</span><span class=\"p\">,</span>\n    <span class=\"na\">noise</span><span class=\"p\">:</span> <span class=\"mf\">1.1</span><span class=\"p\">,</span>\n    <span class=\"na\">warmingTrendPerHour</span><span class=\"p\">:</span> <span class=\"mf\">0.0021</span>\n<span class=\"p\">});</span>\n\n<span class=\"kd\">let</span> <span class=\"nx\">northStationLTTB</span> <span class=\"o\">=</span> <span class=\"nc\">LTTB</span><span class=\"p\">(</span><span class=\"nx\">northStationData</span><span class=\"p\">,</span> <span class=\"mi\">700</span><span class=\"p\">);</span>\n\n<span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n   <span class=\"na\">northStation</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n     <span class=\"na\">raw</span><span class=\"p\">:</span> <span class=\"nx\">northStationData</span><span class=\"p\">,</span>\n     <span class=\"na\">downsample</span><span class=\"p\">:</span> <span class=\"nx\">northStationLTTB</span>\n   <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>We also need to store the minimum and maximum dates across all datasets, since they will later be used to create the hidden dataset.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</span> <span class=\"nx\">allX</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"nx\">northStationData</span><span class=\"p\">,</span> <span class=\"nx\">cityCenterData</span><span class=\"p\">,</span> <span class=\"nx\">southStationData</span><span class=\"p\">].</span><span class=\"nf\">flatMap</span><span class=\"p\">(</span><span class=\"nx\">s</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">s</span><span class=\"p\">.</span><span class=\"nf\">map</span><span class=\"p\">(</span><span class=\"nx\">p</span> <span class=\"o\">=&gt;</span> <span class=\"nx\">p</span><span class=\"p\">.</span><span class=\"nx\">x</span><span class=\"p\">));</span>\n\n<span class=\"kd\">let</span> <span class=\"nx\">minimumDate</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">min</span><span class=\"p\">(...</span><span class=\"nx\">allX</span><span class=\"p\">);</span>\n<span class=\"kd\">let</span> <span class=\"nx\">maximumDate</span> <span class=\"o\">=</span> <span class=\"nb\">Math</span><span class=\"p\">.</span><span class=\"nf\">max</span><span class=\"p\">(...</span><span class=\"nx\">allX</span><span class=\"p\">);</span>\n\n<span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">ranges</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n    <span class=\"na\">minimumDate</span><span class=\"p\">:</span> <span class=\"nx\">minimumDate</span><span class=\"p\">,</span>\n    <span class=\"na\">maximumDate</span><span class=\"p\">:</span> <span class=\"nx\">maximumDate</span><span class=\"p\">,</span>\n<span class=\"p\">};</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Computed properties\n</h3>\n\n<h4>\n  \n  \n  Dataset with minimum and maximum dates\n</h4>\n\n<p>Once the minimum and maximum dates are stored in the reactive state, we can create a computed property that returns a dataset containing only those boundary dates.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nf\">plotMinMaxDataset</span><span class=\"p\">()</span>\n<span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"p\">{</span>\n        <span class=\"na\">name</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">rangeMinMaxInvisible</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n        <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n            <span class=\"p\">{</span><span class=\"na\">x</span><span class=\"p\">:</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">ranges</span><span class=\"p\">.</span><span class=\"nx\">minimumDate</span><span class=\"p\">).</span><span class=\"nf\">getTime</span><span class=\"p\">(),</span> <span class=\"na\">y</span><span class=\"p\">:</span> <span class=\"kc\">null</span><span class=\"p\">},</span>\n            <span class=\"p\">{</span><span class=\"na\">x</span><span class=\"p\">:</span> <span class=\"k\">new</span> <span class=\"nc\">Date</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">ranges</span><span class=\"p\">.</span><span class=\"nx\">maximumDate</span><span class=\"p\">).</span><span class=\"nf\">getTime</span><span class=\"p\">(),</span> <span class=\"na\">y</span><span class=\"p\">:</span> <span class=\"kc\">null</span><span class=\"p\">},</span>\n        <span class=\"p\">],</span>\n        <span class=\"na\">showInLegend</span><span class=\"p\">:</span> <span class=\"kc\">false</span><span class=\"p\">,</span>\n    <span class=\"p\">};</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h4>\n  \n  \n  Initial datasets\n</h4>\n\n<p>When displaying the chart for the first time, we can create another computed property that returns the downsampled datasets together with the hidden boundary dataset.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nf\">plotSeries</span><span class=\"p\">()</span>\n<span class=\"p\">{</span>\n    <span class=\"kd\">let</span> <span class=\"nx\">seriesData</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n    <span class=\"nb\">Object</span><span class=\"p\">.</span><span class=\"nf\">keys</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">).</span><span class=\"nf\">forEach</span><span class=\"p\">(</span><span class=\"nx\">key</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n        <span class=\"nx\">seriesData</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">({</span>\n            <span class=\"na\">name</span><span class=\"p\">:</span> <span class=\"s2\">`</span><span class=\"p\">${</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">key</span><span class=\"p\">].</span><span class=\"nx\">label</span><span class=\"p\">}</span><span class=\"s2\"> (downsampled)`</span><span class=\"p\">,</span>\n            <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"nf\">toRaw</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">key</span><span class=\"p\">].</span><span class=\"nx\">downsample</span><span class=\"p\">),</span>\n        <span class=\"p\">})</span>\n    <span class=\"p\">});</span>\n\n    <span class=\"cm\">/**\n     * Add the minimum and maximum points available in the datasets\n     * to avoid zooming and scrolling to lose the minimum and maximum ranges.\n     */</span>\n    <span class=\"nx\">seriesData</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nf\">toRaw</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">plotMinMaxDataset</span><span class=\"p\">));</span>\n    <span class=\"k\">return</span> <span class=\"nx\">seriesData</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Methods\n</h3>\n\n<h4>\n  \n  \n  Switching between raw and downsampled data\n</h4>\n\n<p>This method is called whenever the user zooms or scrolls through the chart. It filters the data according to the selected range and switches between raw and downsampled datasets based on a defined criterion. At the end, it also includes the dataset with the minimum and maximum dates to avoid zooming and scrolling to lose the original boundaries.</p>\n\n<p>In this example, the criterion is the number of points available in the filtered raw dataset.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nf\">plotRangeOnChange</span><span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">,</span> <span class=\"nx\">minDate</span><span class=\"p\">,</span> <span class=\"nx\">maxDate</span><span class=\"p\">)</span>\n<span class=\"p\">{</span>\n    <span class=\"kd\">let</span> <span class=\"nx\">dataUpdate</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n\n    <span class=\"nb\">Object</span><span class=\"p\">.</span><span class=\"nf\">keys</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">).</span><span class=\"nf\">forEach</span><span class=\"p\">(</span><span class=\"nx\">key</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n        <span class=\"kd\">let</span> <span class=\"nx\">dataTypeName</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">raw</span><span class=\"dl\">'</span>\n        <span class=\"kd\">let</span> <span class=\"nx\">dataRaw</span> <span class=\"o\">=</span> <span class=\"nf\">pointFilter</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">key</span><span class=\"p\">].</span><span class=\"nx\">raw</span><span class=\"p\">,</span> <span class=\"nx\">minDate</span><span class=\"p\">,</span> <span class=\"nx\">maxDate</span><span class=\"p\">);</span>\n\n        <span class=\"cm\">/**\n         * Based on the number of points in the raw dataset after filtering,\n         * decide whether to keep raw or downsampled datasets.\n         *\n         * The criteria in this case is to verify the number of points, but it could\n         * also be the range of dates.\n         */</span>\n        <span class=\"k\">if </span><span class=\"p\">(</span><span class=\"nx\">dataRaw</span><span class=\"p\">.</span><span class=\"nx\">length</span> <span class=\"o\">&gt;</span> <span class=\"mi\">1000</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n            <span class=\"nx\">dataRaw</span> <span class=\"o\">=</span> <span class=\"nf\">pointFilter</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">key</span><span class=\"p\">].</span><span class=\"nx\">downsample</span><span class=\"p\">,</span> <span class=\"nx\">minDate</span><span class=\"p\">,</span> <span class=\"nx\">maxDate</span><span class=\"p\">);</span>\n            <span class=\"nx\">dataTypeName</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">downsampled</span><span class=\"dl\">'</span><span class=\"p\">;</span>\n        <span class=\"p\">}</span>\n\n        <span class=\"nx\">dataUpdate</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">({</span>\n            <span class=\"na\">name</span><span class=\"p\">:</span> <span class=\"s2\">`</span><span class=\"p\">${</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">[</span><span class=\"nx\">key</span><span class=\"p\">].</span><span class=\"nx\">label</span><span class=\"p\">}</span><span class=\"s2\"> (</span><span class=\"p\">${</span><span class=\"nx\">dataTypeName</span><span class=\"p\">}</span><span class=\"s2\">)`</span><span class=\"p\">,</span>\n            <span class=\"na\">data</span><span class=\"p\">:</span> <span class=\"nf\">toRaw</span><span class=\"p\">(</span><span class=\"nx\">dataRaw</span><span class=\"p\">),</span>\n        <span class=\"p\">});</span>\n    <span class=\"p\">});</span>\n\n    <span class=\"cm\">/**\n     * Add the minimum and maximum points available in the datasets\n     * to avoid zooming and scrolling to lose the minimum and maximum ranges.\n     */</span>\n    <span class=\"nx\">dataUpdate</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nf\">toRaw</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">plotMinMaxDataset</span><span class=\"p\">));</span>\n\n    <span class=\"nx\">chartContext</span><span class=\"p\">.</span><span class=\"nf\">updateOptions</span><span class=\"p\">({</span>\n        <span class=\"na\">series</span><span class=\"p\">:</span> <span class=\"nf\">toRaw</span><span class=\"p\">(</span><span class=\"nx\">dataUpdate</span><span class=\"p\">),</span>\n        <span class=\"na\">xaxis</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n            <span class=\"na\">min</span><span class=\"p\">:</span> <span class=\"nx\">minDate</span><span class=\"p\">,</span>\n            <span class=\"na\">max</span><span class=\"p\">:</span> <span class=\"nx\">maxDate</span><span class=\"p\">,</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">});</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h3>\n  \n  \n  Configuring the chart\n</h3>\n\n<p>We need to configure the <code>zoomed</code> and <code>scrolled</code> events to trigger the switch between raw and downsampled data using the method defined above. We also configure the <code>beforeResetZoom</code> event to restore the chart to the full available date range.</p>\n\n<p>These events provide:</p>\n\n<ol>\n<li>chartContext, which gives access to the chart instance.</li>\n<li>xaxis, which contains the currently visible minimum and maximum dates.</li>\n</ol>\n\n<p>At this stage, we also hide the auxiliary hidden dataset from the legend and remove its marker by filtering the series name in the <code>legend.formatter</code> of the plot options and setting its marker size to <code>0</code>.<br>\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">let</span> <span class=\"nx\">markersSize</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n<span class=\"nb\">Object</span><span class=\"p\">.</span><span class=\"nf\">keys</span><span class=\"p\">(</span><span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nx\">data</span><span class=\"p\">).</span><span class=\"nf\">forEach</span><span class=\"p\">(</span><span class=\"nx\">key</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n    <span class=\"nx\">markersSize</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"mi\">8</span><span class=\"p\">);</span>\n<span class=\"p\">});</span>\n\n<span class=\"nx\">markersSize</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n\n<span class=\"kd\">let</span> <span class=\"nx\">plotOptions</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n    <span class=\"na\">chart</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n        <span class=\"na\">id</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">vuechart-example</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n        <span class=\"na\">events</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n            <span class=\"cm\">/**\n             * Update data based on the selected range in the x-axis when events occur\n             */</span>\n            <span class=\"na\">zoomed</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"nx\">xaxis</span><span class=\"p\">})</span> <span class=\"o\">=&gt;</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">plotRangeOnChange</span><span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">,</span> <span class=\"nx\">xaxis</span><span class=\"p\">.</span><span class=\"nx\">min</span><span class=\"p\">,</span> <span class=\"nx\">xaxis</span><span class=\"p\">.</span><span class=\"nx\">max</span><span class=\"p\">),</span>\n            <span class=\"na\">scrolled</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"nx\">xaxis</span><span class=\"p\">})</span> <span class=\"o\">=&gt;</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">plotRangeOnChange</span><span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">,</span> <span class=\"nx\">xaxis</span><span class=\"p\">.</span><span class=\"nx\">min</span><span class=\"p\">,</span> <span class=\"nx\">xaxis</span><span class=\"p\">.</span><span class=\"nx\">max</span><span class=\"p\">),</span>\n            <span class=\"na\">beforeResetZoom</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"k\">this</span><span class=\"p\">.</span><span class=\"nf\">plotRangeOnChange</span><span class=\"p\">(</span><span class=\"nx\">chartContext</span><span class=\"p\">,</span> <span class=\"kc\">null</span><span class=\"p\">,</span> <span class=\"kc\">null</span><span class=\"p\">),</span>\n        <span class=\"p\">},</span>\n    <span class=\"p\">},</span>\n    <span class=\"na\">legend</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n        <span class=\"na\">formatter</span><span class=\"p\">:</span> <span class=\"nf\">function </span><span class=\"p\">(</span><span class=\"nx\">seriesName</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n            <span class=\"cm\">/**\n             * Hide the marker containing the minimum and maximum values in the x-axis range\n             */</span>\n            <span class=\"k\">return</span> <span class=\"nx\">seriesName</span> <span class=\"o\">===</span> <span class=\"dl\">'</span><span class=\"s1\">rangeMinMaxInvisible</span><span class=\"dl\">'</span> <span class=\"p\">?</span> <span class=\"kc\">null</span> <span class=\"p\">:</span> <span class=\"nx\">seriesName</span><span class=\"p\">;</span>\n        <span class=\"p\">},</span>\n        <span class=\"na\">markers</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n            <span class=\"na\">size</span><span class=\"p\">:</span> <span class=\"nx\">markersSize</span>\n        <span class=\"p\">},</span>\n    <span class=\"p\">},</span>\n    <span class=\"cm\">/* ... */</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<h2>\n  \n  \n  Wrapping up\n</h2>\n\n<p>In this article, I showed how to use downsampling data in a Vue.js + ApexCharts application to reduce the number of points to display and improve the user experience, while still allowing to zoom in, zoom out and scroll over the chart without encountering problems with the mouse wheel.</p>\n\n<p>The complete code of this example is available in the GitHub repository <a href=\"https://github.com/aaronestrada/vue-apexcharts-downsample-zoom\" rel=\"noopener noreferrer\">vue-apexcharts-downsample-zoom</a>.</p>\n\n<p>You can also try the live demo <a href=\"https://aaronestrada.github.io/vue-apexcharts-downsample-zoom/\" rel=\"noopener noreferrer\">here</a>.</p>\n\n<p>Leave a comment if you have any questions or suggestions to improve, I'll be happy to interact with you!</p>\n\n<p><strong>Let's make the world a better place together, one snippet at a time!</strong></p>","score":3},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Building GitHub&#8217;s next chapter in accessibility","link":"https://github.blog/open-source/building-githubs-next-chapter-in-accessibility/","pubDate":"Thu, 21 May 2026 16:00:00 +0000","description":"<p>Explore our update on GitHub’s accessibility strategy, and learn how you can join us in building a culture of accessibility.</p>\n<p>The post <a href=\"https://github.blog/open-source/building-githubs-next-chapter-in-accessibility/\">Building GitHub&#8217;s next chapter in accessibility</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://stackoverflow.com/feeds/tag/node.js","sourceHost":"stackoverflow.com","title":"Next.js development stage BFF pattern DX log optimization","link":"https://stackoverflow.com/questions/79944721/next-js-development-stage-bff-pattern-dx-log-optimization","pubDate":"2026-05-21T14:23:40Z","description":"<p>During development, if BFF is enabled and data returned by the backend gets truncated by BFF, are there any good approaches for DX optimization? (Exclude the following solution: directly returning logs from BFF in the test environment, as it still delivers poor readability and subpar effects.)</p>&#xA;<p><a href=\"https://i.sstatic.net/30yU4ElD.png\" rel=\"nofollow noreferrer\">enter image description here</a></p>&#xA;","score":2},{"source":"https://www.smashingmagazine.com/feed","sourceHost":"smashingmagazine.com","title":"Advanced Tree Counting: Mathematical Layouts With `sibling-index()` And `sibling-count()`","link":"https://smashingmagazine.com/2026/05/mathematical-layouts-sibling-index-sibling-count/","pubDate":"Thu, 21 May 2026 08:00:00 GMT","description":"Meet `sibling-index()` and `sibling-count()`. Staggered cascade effect in one line of CSS without `:nth-child()` rules or JS workarounds. Works for 5 items or 5,000.","score":2},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Investigating unauthorized access to GitHub-owned repositories","link":"https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/","pubDate":"Wed, 20 May 2026 21:07:38 +0000","description":"<p>If any impact is discovered, customers will be notified via established incident response and notification channels.</p>\n<p>The post <a href=\"https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/\">Investigating unauthorized access to GitHub-owned repositories</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://javascriptweekly.com/rss","sourceHost":"javascriptweekly.com","title":"Dr. Axel's blog is gone (for now)","link":"https://javascriptweekly.com/issues/786","pubDate":"Tue, 19 May 2026 00:00:00 +0000","description":"<table border=0 cellpadding=0 cellspacing=0 align=\"center\" border=\"0\">\n  <tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <div>    \n    <table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr>\n<td align=\"left\" style=\"padding-left: 4px; font-size: 15px; line-height: 1.48em;\"><p>#​786 — May 19, 2026</p></td>\n<td align=\"right\" style=\"padding-right: 4px; font-size: 15px; line-height: 1.48em;\"><p><a href=\"https://javascriptweekly.com/link/185305/rss\" style=\" color: #3366aa;\">Read on the Web</a></p></td>\n</tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n    \n    <table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0 12px;\"><p>JavaScript Weekly</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/185307/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/ql3ejie7zibdm4rd25zh.jpg\" width=\"640\" style=\"    line-height: 100%;    \"></a>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><span>RFC:</span> <a href=\"https://javascriptweekly.com/link/185307/rss\" title=\"github.com\" style=\" color: #3366aa;    font-size: 1.1em; line-height: 1.4em;\">It’s Time for <code>npm</code> to Make Install Scripts Opt-In</a></span> — npm is the only major package manager that runs dependency install scripts (e.g. <code>postinstall</code>) by default, and they’ve become too much of a security weakness, says Jamie, who works for GitHub (maintainers of npm). This RFC features further discussion of the idea and the tradeoffs involved.</p>\n  <p>Jamie Magee </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\"><p>💡 <a href=\"https://javascriptweekly.com/link/185308/rss\" style=\" color: #3366aa; font-weight: 500;\">npq</a> is a tool that makes <code>npm install</code>s safer. It stands in front of <code>npm</code> and audits packages before installing them, including the presence of pre/post install scripts.</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  <a href=\"https://javascriptweekly.com/link/185306/rss\" style=\" color: #3366aa;   \"><img src=\"https://res.cloudinary.com/cpress/image/upload/c_limit,w_480,h_480,q_auto/copm/f5067c40.png\" width=\"120\" height=\"120\" style=\"padding-top: 12px; padding-left: 12px;     line-height: 100%; \"></a>\n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185306/rss\" title=\"fandf.co\" style=\" color: #3366aa;    font-size: 1.05em;\">How Depot Built a CI Orchestrator on AWS Lambda</a></span> — Long-running CI orchestration without long-lived servers. Depot rebuilt their CI engine using AWS Lambda durable functions — stateful, callback-driven, and crash-recoverable. A deep dive into the run-workflow-job hierarchy powering Depot CI.</p>\n  <p>Depot <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185309/rss\" title=\"safedep.io\" style=\" color: #3366aa;    font-size: 1.05em;\"><em>Mini Shai-Hulud</em> Hits: 300+ Malicious npm Packages Published</a></span> — The <em>\"<a href=\"https://javascriptweekly.com/link/185310/rss\" style=\" color: #3366aa;   \">Shai-Hulud</a>\"</em> class of npm ecosystem attacks continues to rumble on. Today, <a href=\"https://javascriptweekly.com/link/185311/rss\" style=\" color: #3366aa;   \">hundreds more packages</a> – including popular ones from the <code>antv</code> family and <code>timeago.js</code> – were hit.</p>\n  <p>SafeDep Team </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><strong>IN BRIEF:</strong></p>\n<ul>\n<li>\n<p>😱 Dr. Axel Rauschmayer (JavaScript legend and former JS Weekly editor) has <a href=\"https://javascriptweekly.com/link/185312/rss\" style=\" color: #3366aa; font-weight: 500;   \">taken his blog and JavaScript books off the Web</a> due to being overwhelmed by AI crawlers. You can, however, still <a href=\"https://javascriptweekly.com/link/185313/rss\" style=\" color: #3366aa; font-weight: 500;   \">purchase his fantastic books here.</a></p>\n</li>\n<li>\n<p>The Bun saga continues. Despite once <a href=\"https://javascriptweekly.com/link/185314/rss\" style=\" color: #3366aa; font-weight: 500;   \">playing down its significance</a>, the <a href=\"https://javascriptweekly.com/link/185315/rss\" style=\" color: #3366aa; font-weight: 500;   \">Rust-based rewrite of Bun has been merged</a>, though there are <a href=\"https://javascriptweekly.com/link/185316/rss\" style=\" color: #3366aa; font-weight: 500;   \">questions over the quality</a> of the AI-ported code. Much <a href=\"https://javascriptweekly.com/link/185317/rss\" style=\" color: #3366aa; font-weight: 500;   \">discussion ensued on Hacker News.</a></p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185318/rss\" style=\" color: #3366aa; font-weight: 500;   \">The Deno team is teasing Deno 2.8</a>, due to be released this week. Significant Node.js compatibility improvements, <code>import defer</code>, and TypeScript 6.0.3 support await.</p>\n</li>\n<li>\n<p>The Chrome and Edge teams are working on <a href=\"https://javascriptweekly.com/link/185319/rss\" style=\" color: #3366aa; font-weight: 500;   \">a new <code>&lt;install&gt;</code> HTML element</a> for browsers to render a 'trusted install button' for PWAs.</p>\n</li>\n<li>\n<p>The <em>Express.js</em> project has <a href=\"https://javascriptweekly.com/link/185320/rss\" style=\" color: #3366aa; font-weight: 500;   \">an all new look</a>, including a new site, logo, and <a href=\"https://javascriptweekly.com/link/185321/rss\" style=\" color: #3366aa; font-weight: 500;   \">improved docs.</a></p>\n</li>\n</ul>\n\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><strong>RELEASES:</strong></p>\n<ul>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185322/rss\" style=\" color: #3366aa; font-weight: 500;   \">Angular 22 Release Candidate</a> – Final is due in early June. Expect <a href=\"https://javascriptweekly.com/link/185323/rss\" style=\" color: #3366aa; font-weight: 500;   \">signal-based forms</a> and the <a href=\"https://javascriptweekly.com/link/185324/rss\" style=\" color: #3366aa; font-weight: 500;   \">OnPush</a> change detection strategy becoming default.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185325/rss\" style=\" color: #3366aa; font-weight: 500;   \">Bun 1.3.14</a> – The alternative JS runtime gets <code>Bun.Image</code>, more HTTP/2 and HTTP/3 support, and more Node compatibility improvements.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185326/rss\" style=\" color: #3366aa; font-weight: 500;   \">ESLint Config Inspector 3.0</a> – A visual tool for inspecting and understanding your ESLint flat configs.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185327/rss\" style=\" color: #3366aa; font-weight: 500;   \">TypeORM 1.0</a>, <a href=\"https://javascriptweekly.com/link/185328/rss\" style=\" color: #3366aa; font-weight: 500;   \">ESLint 10.4.0</a>, <a href=\"https://javascriptweekly.com/link/185329/rss\" style=\" color: #3366aa; font-weight: 500;   \">Relay 21.0</a>, <a href=\"https://javascriptweekly.com/link/185330/rss\" style=\" color: #3366aa; font-weight: 500;   \">Rolldown 1.0.1</a></p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>📖  Articles and Videos</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/185331/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/echd5tqlridpzz6jd2bx.jpg\" width=\"640\" style=\"    line-height: 100%;      \"></a>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><span>🤖</span> <a href=\"https://javascriptweekly.com/link/185331/rss\" title=\"blog.isquaredsoftware.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Mark Erikson's Agent Setup, Workflow, and Tools</a></span> — Mark, well known for maintaining Redux and creating Redux Toolkit, goes deep into his daily development workflow, including his use of <a href=\"https://javascriptweekly.com/link/185332/rss\" style=\" color: #3366aa;   \">OpenCode</a> (an open source JavaScript-powered coding agent), how he manages his knowledge base, tasks, and more.</p>\n  <p>Mark Erikson </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185333/rss\" title=\"go.clerk.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Clerk API Keys Are Now Generally Available</a></span> — Let your users create credentials that delegate access to your API. Verify server-side, revoke instantly — all via the Backend SDK.</p>\n  <p>Clerk <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><span>📗</span> <a href=\"https://javascriptweekly.com/link/185334/rss\" title=\"www.thenodebook.com\" style=\" color: #3366aa;    font-size: 1.05em;\">NodeBook: An Advanced Guide to Node.js Internals</a></span> — Eight in-depth chapters for understanding Node.js internals, covering topics like <a href=\"https://javascriptweekly.com/link/185335/rss\" style=\" color: #3366aa;   \">event loop internals</a>, <a href=\"https://javascriptweekly.com/link/185336/rss\" style=\" color: #3366aa;   \">what V8 does</a>, <a href=\"https://javascriptweekly.com/link/185337/rss\" style=\" color: #3366aa;   \">streams</a>, <a href=\"https://javascriptweekly.com/link/185338/rss\" style=\" color: #3366aa;   \">module resolution</a>, and <a href=\"https://javascriptweekly.com/link/185339/rss\" style=\" color: #3366aa;   \">async/await</a>.</p>\n  <p>Ishtmeet Singh </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185340/rss\" title=\"css-tricks.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Soon We Can Finally Banish JavaScript to the <em>ShadowRealm</em></a></span> — A tour of the <em>in-progress</em> <a href=\"https://javascriptweekly.com/link/185341/rss\" style=\" color: #3366aa;   \">TC39 proposal</a> for running JavaScript in an isolated ‘pseudo-realm’ with its own globals and intrinsics. Handy for third-party code or anything you want to keep away from global scope.</p>\n  <p>Mat Marquis </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p>📄 <a href=\"https://javascriptweekly.com/link/185342/rss\" style=\" color: #3366aa; font-weight: 500;   \">Hardening TanStack After the npm Compromise</a> – What TanStack is doing to improve supply chain security after <a href=\"https://javascriptweekly.com/link/185343/rss\" style=\" color: #3366aa; font-weight: 500;   \">an attacker published</a> malicious versions of TanStack packages last week. <cite>The TanStack Team</cite></p>\n<p>📺 <a href=\"https://javascriptweekly.com/link/185344/rss\" style=\" color: #3366aa; font-weight: 500;   \">The <em>TanStack Start</em> Story: Tanner Linsley on Competing with Next.js</a> – A candid 40-minute interview with TanStack’s founder. <cite>Nuno Maduro</cite></p>\n<p>📄 <a href=\"https://javascriptweekly.com/link/185345/rss\" style=\" color: #3366aa; font-weight: 500;   \">Cross-Document View Transitions: The Gotchas Nobody Mentions</a> <cite>Durgesh Rajubhai Pawar (CSS Tricks)</cite></p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>🛠 Code &amp; Tools</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/185346/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/wbo5vpin96clokiyeke5.jpg\" width=\"640\" style=\"    line-height: 100%;      \"></a>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185346/rss\" title=\"orval.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">Orval: Generate Type-Safe Clients from OpenAPI/Swagger Specs</a></span> — Given a valid OpenAPI v3 or Swagger v2 spec, generate models, requests, hooks, and mocks for React, Vue, Svelte, Solid, and Hono apps, or even plain <code>fetch</code>.</p>\n  <p>Victor Bury </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185347/rss\" title=\"github.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Brownies: Browser Storage as a Plain Object, With Change Events</a></span> — One tiny API over cookies, localStorage, sessionStorage and IndexedDB. Typed values survive automatically, and you get <code>subscribe()</code> for change events.</p>\n  <p>Francisco Presencia </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185348/rss\" title=\"www.tigerdata.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Querying a Billion Rows Shouldn't Freeze Your API</a></span> — TimescaleDB extends Postgres so analytics queries stay fast at scale. No pipeline, no drift. <a href=\"https://javascriptweekly.com/link/185348/rss\" style=\" color: #3366aa;   \">$1000 credit to start</a>.</p>\n  <p>Tiger Data (creators of TimescaleDB) <span style=\"text-transform: uppercase; margin-left: 4px; font-size: 0.9em;   color: #993 !important; padding: 1px 4px; \">sponsor</span></p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><span>🖼️</span> <a href=\"https://javascriptweekly.com/link/185349/rss\" title=\"nodeca.github.io\" style=\" color: #3366aa;    font-size: 1.05em;\">Pica 10.0: High Quality Image Resizing in the Browser</a></span> — High quality in-browser image resizing that leans on WASM and Web Workers or falls back to pure JS as necessary. <a href=\"https://javascriptweekly.com/link/185350/rss\" style=\" color: #3366aa;   \">v10</a> is a modernization build (the first since 2021) that adds ESM and split builds and migrates to TypeScript. <a href=\"https://javascriptweekly.com/link/185351/rss\" style=\" color: #3366aa;   \">GitHub repo.</a></p>\n  <p>Vitaly Puzrin </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><span>🗓️</span> <a href=\"https://javascriptweekly.com/link/185352/rss\" title=\"svar.dev\" style=\" color: #3366aa;    font-size: 1.05em;\">SVAR Calendar: A Calendar Component for React, Svelte and Vue</a></span> — A flexible calendar component with a MIT-licensed core and extended commercial version. <a href=\"https://javascriptweekly.com/link/185353/rss\" style=\" color: #3366aa;   \">Here’s a live demo</a> of the open source version.</p>\n  <p>XB Software Sp. </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\"><p>💡 <a href=\"https://javascriptweekly.com/link/185354/rss\" style=\" color: #3366aa; font-weight: 500;\">Schedule-X</a> is another great option in this space and <a href=\"https://javascriptweekly.com/link/185355/rss\" style=\" color: #3366aa; font-weight: 500;\">v4.6</a> just landed.</p></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185356/rss\" title=\"fate.technology\" style=\" color: #3366aa;    font-size: 1.05em;\">Fate 1.0: A Modern Data Framework for React</a></span> — A new data framework from former Jest lead and ex-Meta engineer Christoph Nakazawa.</p>\n  <p>Christoph Nakazawa </p>\n</td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n  \n  <p><span style=\"font-weight: 600; font-size: 1.1em; color: #000;\"><a href=\"https://javascriptweekly.com/link/185357/rss\" title=\"github.com\" style=\" color: #3366aa;    font-size: 1.05em;\">Alien Signals: 'The Lightest Signal Library'</a></span> — Boils the best of Vue, Preact and Svelte’s approaches down into the lightest signal library going. A push-pull reactivity core so well-tuned it got merged back into Vue.</p>\n  <p>Johnson Chu </p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<ul>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185358/rss\" style=\" color: #3366aa; font-weight: 500;   \">Critical 8.0</a> – Addy Osmani's library for extracting and inlining above-the-fold CSS into HTML.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185359/rss\" style=\" color: #3366aa; font-weight: 500;   \">SQL Formatter 15.8</a> – Pretty-printer for SQL queries. (<a href=\"https://javascriptweekly.com/link/185360/rss\" style=\" color: #3366aa; font-weight: 500;   \">Demo</a>)</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185361/rss\" style=\" color: #3366aa; font-weight: 500;   \">Shiki 4.1</a> – Popular, powerful syntax highlighter.</p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185362/rss\" style=\" color: #3366aa; font-weight: 500;   \">Redux Toolkit 2.12.0</a> and <a href=\"https://javascriptweekly.com/link/185363/rss\" style=\" color: #3366aa; font-weight: 500;   \">React Redux 9.3</a></p>\n</li>\n<li>\n<p><a href=\"https://javascriptweekly.com/link/185364/rss\" style=\" color: #3366aa; font-weight: 500;   \">Vue.js Language Tools 3.3</a></p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px;\">\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n\t<p>📰 Classifieds</p>\n  </td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<p><a href=\"https://javascriptweekly.com/link/185365/rss\" style=\" color: #3366aa; font-weight: 500;   \">HyperFormula</a>: The headless spreadsheet engine with 400+ Excel-compatible formulas. Run complex calculations at high speed.</p>\n \n<p>Flaky tests slowing down dev? <a href=\"https://javascriptweekly.com/link/185366/rss\" style=\" color: #3366aa; font-weight: 500;   \">Meticulous</a> gives engineers confidence to ship faster by autonomously testing every edge case of your web app.</p>\n \n<p>⚙️ <a href=\"https://javascriptweekly.com/link/185367/rss\" style=\" color: #3366aa; font-weight: 500;   \">Middleware, but for AI agents</a>. Compose Claude Code, Codex &amp; Gemini as one TypeScript harness — 100+ agent recipes. <a href=\"https://javascriptweekly.com/link/185367/rss\" style=\" color: #3366aa; font-weight: 500;   \">agentfield.ai/github</a>.</p>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0;\"><p>📢  Elsewhere in the ecosystem</p></td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em;\">\n  <a href=\"https://javascriptweekly.com/link/185368/rss\" style=\" color: #3366aa;\"><img src=\"https://res.cloudinary.com/cpress/image/upload/w_1280,e_sharpen:60,q_auto/azx9afpomjipp3vdrpxi.jpg\" width=\"640\" style=\"    line-height: 100%;      \"></a>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\"font-size: 15px; line-height: 1.48em; padding: 0px 15px;\">\n<ul>\n<li>\n<p>Andrea Giammarchi <a href=\"https://javascriptweekly.com/link/185368/rss\" style=\" color: #3366aa; font-weight: 500;   \">proposes <code>JSONRegistry</code></a> <em>(above)</em>, an alternative to <code>JSON</code> that lets you define a registry for serializing and reviving custom/branded types.</p>\n</li>\n<li>\n<p>🐝 <a href=\"https://javascriptweekly.com/link/185369/rss\" style=\" color: #3366aa; font-weight: 500;   \">Wasp</a> is a Rails-like framework for Node, React and Prisma. It started life as a new programming language of its own before moving to JavaScript. Founder Matija Sosic tells the story in <a href=\"https://javascriptweekly.com/link/185370/rss\" style=\" color: #3366aa; font-weight: 500;   \">5 Years and $5M Later: Inventing a New Programming Language for Web Development Was a Mistake</a>.</p>\n</li>\n<li>\n<p>Did you know that <a href=\"https://javascriptweekly.com/link/185371/rss\" style=\" color: #3366aa; font-weight: 500;   \">some browsers render certain sites 'differently'</a> on an ad hoc basis? Firefox and Safari ship with <a href=\"https://javascriptweekly.com/link/185372/rss\" style=\" color: #3366aa; font-weight: 500;   \">built-in 'tweaks' for popular sites</a> to fix elements that don't render correctly by default.</p>\n</li>\n<li>\n<p>🤖 Simon Willison shares <a href=\"https://javascriptweekly.com/link/185373/rss\" style=\" color: #3366aa; font-weight: 500;   \">a quick look at the last six months in LLMs.</a></p>\n</li>\n</ul>\n</td></tr></table>\n<table border=0 cellpadding=0 cellspacing=0 border=0 cellpadding=0 cellspacing=0><tr><td style=\" font-size: 15px; line-height: 1.48em;\"></td></tr></table>\n</div>\n  </td></tr>\n</table>\n\n\n\n\n<img src=\"https://javascriptweekly.com/open/786/rss\" width=\"1\" height=\"1\" />","score":6},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Take your local GitHub sessions anywhere","link":"https://github.blog/news-insights/product-news/take-your-local-github-sessions-anywhere/","pubDate":"Mon, 18 May 2026 16:54:53 +0000","description":"<p>Kick off work in VS Code or the CLI, finish it from your phone. Remote control for GitHub Copilot sessions is now generally available on github.com and GitHub Mobile.  </p>\n<p>The post <a href=\"https://github.blog/news-insights/product-news/take-your-local-github-sessions-anywhere/\">Take your local GitHub sessions anywhere</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Building a general-purpose accessibility agent—and what we learned in the process","link":"https://github.blog/ai-and-ml/github-copilot/building-a-general-purpose-accessibility-agent-and-what-we-learned-in-the-process/","pubDate":"Fri, 15 May 2026 16:00:00 +0000","description":"<p>Learn about the experimental general-purpose accessibility agent that GitHub is piloting.</p>\n<p>The post <a href=\"https://github.blog/ai-and-ml/github-copilot/building-a-general-purpose-accessibility-agent-and-what-we-learned-in-the-process/\">Building a general-purpose accessibility agent—and what we learned in the process</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4},{"source":"https://github.blog/feed","sourceHost":"github.blog","title":"Raising the bar: Quality, shared responsibility, and the future of GitHub&#8217;s bug bounty program","link":"https://github.blog/security/raising-the-bar-quality-shared-responsibility-and-the-future-of-githubs-bug-bounty-program/","pubDate":"Fri, 15 May 2026 14:00:00 +0000","description":"<p>We're updating our bug bounty program standards to prioritize quality submissions, clarify shared responsibility boundaries, and evolve how we reward low-risk findings.</p>\n<p>The post <a href=\"https://github.blog/security/raising-the-bar-quality-shared-responsibility-and-the-future-of-githubs-bug-bounty-program/\">Raising the bar: Quality, shared responsibility, and the future of GitHub&#8217;s bug bounty program</a> appeared first on <a href=\"https://github.blog\">The GitHub Blog</a>.</p>","score":4}]}