<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: L Anil Kumar Singha</title>
    <description>The latest articles on DEV Community by L Anil Kumar Singha (@anilloutombam).</description>
    <link>https://dev.to/anilloutombam</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F381275%2F7c05e551-19bf-4978-b673-1fbb9db63c52.jpeg</url>
      <title>DEV Community: L Anil Kumar Singha</title>
      <link>https://dev.to/anilloutombam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anilloutombam"/>
    <language>en</language>
    <item>
      <title>What Happens When an MCP Tool Call Never Returns?</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Thu, 27 Aug 2026 04:57:27 +0000</pubDate>
      <link>https://dev.to/anilloutombam/what-happens-when-an-mcp-tool-call-never-returns-3gdd</link>
      <guid>https://dev.to/anilloutombam/what-happens-when-an-mcp-tool-call-never-returns-3gdd</guid>
      <description>&lt;p&gt;A normal MCP tool call is straightforward: the client sends a request, the server runs the tool, and a response comes back.&lt;/p&gt;

&lt;p&gt;A hang is different. The server accepts the call but never returns a result or an error.&lt;/p&gt;

&lt;p&gt;I wanted to test how an MCP client behaves in that case without relying on network tricks or manually killing a process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducing a hang
&lt;/h2&gt;

&lt;p&gt;MCP Failure Lab has a &lt;code&gt;hang&lt;/code&gt; tool specifically for this.&lt;/p&gt;

&lt;p&gt;The behavior is intentionally simple: once called, it doesn't resolve.&lt;/p&gt;

&lt;p&gt;That gives us a repeatable failure instead of trying to approximate one with a very long delay.&lt;/p&gt;

&lt;p&gt;A scenario for it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hung tool call times out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"call"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hang"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeoutMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"timeout"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server behavior and the expected client behavior are separate here.&lt;/p&gt;

&lt;p&gt;The server hangs.&lt;/p&gt;

&lt;p&gt;The client is expected to time out after one second.&lt;/p&gt;

&lt;p&gt;That distinction matters because a hung server doesn't produce a timeout by itself. The timeout has to be enforced by the client or by something around the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hang vs delay
&lt;/h2&gt;

&lt;p&gt;I originally treated a long delay as being close enough to a hang, but they're useful for testing different things.&lt;/p&gt;

&lt;p&gt;A delayed call still has a completion point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request ---- 5 seconds ----&amp;gt; response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hung call doesn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request --------------------&amp;gt;
        --------------------&amp;gt;
        --------------------&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a delay, a response can still arrive after the client has timed out.&lt;/p&gt;

&lt;p&gt;With a hang, there is no eventual response.&lt;/p&gt;

&lt;p&gt;That difference starts to matter when testing cancellation and cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  A timeout only tells you what the client saw
&lt;/h2&gt;

&lt;p&gt;Suppose a client calls a tool and gets a timeout.&lt;/p&gt;

&lt;p&gt;It's tempting to treat that as meaning the operation failed.&lt;/p&gt;

&lt;p&gt;That's not necessarily true.&lt;/p&gt;

&lt;p&gt;Consider a tool that changes some state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client                    server
   |                         |
   | ---- request ----------&amp;gt;|
   |                         | changes state
   |                         |
   |       response lost     X
   |
   | ---- timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client saw a timeout, but the server may have already completed the operation.&lt;/p&gt;

&lt;p&gt;If the client automatically retries, the operation might run twice.&lt;/p&gt;

&lt;p&gt;This is one reason I'm interested in keeping the observed outcome separate from server state in Failure Lab.&lt;/p&gt;

&lt;p&gt;It's also where simple timeout testing stops being enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should happen after the timeout?
&lt;/h2&gt;

&lt;p&gt;There are a few things worth checking beyond whether a timeout was thrown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the original operation cancelled?&lt;/li&gt;
&lt;li&gt;Is the MCP session still usable?&lt;/li&gt;
&lt;li&gt;Can another tool call succeed?&lt;/li&gt;
&lt;li&gt;Does the client retry?&lt;/li&gt;
&lt;li&gt;Can you determine whether the original operation changed state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one is particularly useful for tools with side effects.&lt;/p&gt;

&lt;p&gt;Failure Lab supports an independent &lt;code&gt;observe&lt;/code&gt; call for scenarios where state needs to be checked after the main call.&lt;/p&gt;

&lt;p&gt;That lets a test distinguish between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client observed: timeout
server state: unchanged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client observed: timeout
server state: changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are very different outcomes even though the client reported the same error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the failure deterministic
&lt;/h2&gt;

&lt;p&gt;The main reason I built the hang fault wasn't to simulate an unreliable network.&lt;/p&gt;

&lt;p&gt;It was to remove the unreliable part from the test.&lt;/p&gt;

&lt;p&gt;If the server hangs deterministically, I can run different clients against the same behavior and compare what they do.&lt;/p&gt;

&lt;p&gt;The path still goes through MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scenario
   ↓
MCP client
   ↓
transport
   ↓
MCP server
   ↓
hang
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the failure is controlled.&lt;/p&gt;

&lt;p&gt;That makes bugs around timeout handling much easier to reproduce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The project is open source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mcp-failure-lab demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/anilloutombam/mcp-failure-lab" rel="noopener noreferrer"&gt;https://github.com/anilloutombam/mcp-failure-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm working through other failure cases as well, particularly cancellation, session loss, malformed responses, and cases where the client reports failure even though the server changed state.&lt;/p&gt;

&lt;p&gt;If you've hit an MCP failure that was difficult to reproduce, open an issue. I'd rather turn real failure cases into deterministic scenarios than invent them.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>typescript</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing MCP Failure Lab: Testing MCP Beyond the Happy Path</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Sat, 15 Aug 2026 19:21:09 +0000</pubDate>
      <link>https://dev.to/anilloutombam/introducing-mcp-failure-lab-testing-mcp-beyond-the-happy-path-2m22</link>
      <guid>https://dev.to/anilloutombam/introducing-mcp-failure-lab-testing-mcp-beyond-the-happy-path-2m22</guid>
      <description>&lt;p&gt;Most MCP examples focus on successful tool calls. That is useful for getting started, but production systems also need to handle calls that stall, exceed their timeout, get cancelled, or lose their connection.&lt;/p&gt;

&lt;p&gt;I started building &lt;strong&gt;MCP Failure Lab&lt;/strong&gt; to make those failure paths controlled, repeatable, and easier to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MCP Failure Lab?
&lt;/h2&gt;

&lt;p&gt;MCP Failure Lab is an open-source TypeScript toolkit for testing how MCP clients and related systems respond to failure conditions.&lt;/p&gt;

&lt;p&gt;The project currently includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic &lt;code&gt;delay&lt;/code&gt;, &lt;code&gt;hang&lt;/code&gt;, and &lt;code&gt;disconnect&lt;/code&gt; fault tools&lt;/li&gt;
&lt;li&gt;A TypeScript CLI and stdio MCP server&lt;/li&gt;
&lt;li&gt;A code-first scenario runner&lt;/li&gt;
&lt;li&gt;Success, error, timeout, and duration assertions&lt;/li&gt;
&lt;li&gt;Unit and integration tests&lt;/li&gt;
&lt;li&gt;CI, coverage, dependency, and security checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of relying on unreliable network conditions or arbitrary sleeps, the goal is to provide explicit failure behavior that can be reproduced in a test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build this?
&lt;/h2&gt;

&lt;p&gt;A successful tool call only proves the happy path.&lt;/p&gt;

&lt;p&gt;It does not tell us whether a client:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enforces request timeouts&lt;/li&gt;
&lt;li&gt;Cancels work correctly&lt;/li&gt;
&lt;li&gt;Cleans up resources after failure&lt;/li&gt;
&lt;li&gt;Distinguishes tool errors from transport failures&lt;/li&gt;
&lt;li&gt;Recovers after a connection is interrupted&lt;/li&gt;
&lt;li&gt;Produces useful diagnostic information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These behaviors become increasingly important as MCP integrations move beyond local experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current architecture
&lt;/h2&gt;

&lt;p&gt;Fault tools are registered directly on an MCP server. Tests connect through MCP transports and exercise the same protocol path used by real tool calls.&lt;/p&gt;

&lt;p&gt;The scenario runner adds a small testing layer around those calls. A scenario describes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which tool to call&lt;/li&gt;
&lt;li&gt;Which arguments to provide&lt;/li&gt;
&lt;li&gt;The request timeout&lt;/li&gt;
&lt;li&gt;The expected outcome&lt;/li&gt;
&lt;li&gt;Optional duration limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The runner records the observed result and evaluates the configured assertions.&lt;/p&gt;

&lt;p&gt;This keeps the project code-first, type-safe, and suitable for existing TypeScript test suites.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next?
&lt;/h2&gt;

&lt;p&gt;The project is still early, and there are several areas where contributions would be valuable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/anilloutombam/mcp-failure-lab/issues/9" rel="noopener noreferrer"&gt;Add end-to-end CLI coverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anilloutombam/mcp-failure-lab/issues/15" rel="noopener noreferrer"&gt;Add structured scenario reports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anilloutombam/mcp-failure-lab/issues/16" rel="noopener noreferrer"&gt;Define a target-client adapter contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anilloutombam/mcp-failure-lab/issues/17" rel="noopener noreferrer"&gt;Add Streamable HTTP support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each issue includes its expected behavior, acceptance criteria, limitations, and relevant files.&lt;/p&gt;

&lt;p&gt;Before starting substantial work, please comment on the issue with your proposed approach and ask to be assigned. This helps avoid duplicated work and gives us a place to discuss the design first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributions are welcome
&lt;/h2&gt;

&lt;p&gt;If you work with MCP, TypeScript, resilience testing, or developer tooling, I would value your feedback.&lt;/p&gt;

&lt;p&gt;You can contribute code, review the design, improve documentation, test the project, or suggest failure modes that are worth supporting.&lt;/p&gt;

&lt;p&gt;MCP Failure Lab is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/anilloutombam/mcp-failure-lab" rel="noopener noreferrer"&gt;https://github.com/anilloutombam/mcp-failure-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the project is useful to you, consider starring it, opening a discussion, or picking up one of the contributor-ready issues. I would be glad to build it with others.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>mcp</category>
      <category>testing</category>
    </item>
    <item>
      <title>How I Designed a Production Global Search System on PostgreSQL</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Fri, 14 Aug 2026 16:41:28 +0000</pubDate>
      <link>https://dev.to/anilloutombam/how-i-designed-a-production-global-search-system-on-postgresql-113</link>
      <guid>https://dev.to/anilloutombam/how-i-designed-a-production-global-search-system-on-postgresql-113</guid>
      <description>&lt;p&gt;Search often starts as a text box.&lt;/p&gt;

&lt;p&gt;Then the requirements arrive.&lt;/p&gt;

&lt;p&gt;Users want to search by a specific field. Then multiple fields. Then exclusions. Date ranges. Multi-select filters. &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt;. Parentheses. Nested conditions.&lt;/p&gt;

&lt;p&gt;Eventually, this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;turns into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(
  "payment approval"
  OR (
    department:finance
    AND (status:open OR status:pending)
  )
)
AND NOT (
  owner:system
  OR category:archived
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point, you're no longer building a text box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're designing a query language.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran into exactly this while designing a global search system for a production application.&lt;/p&gt;

&lt;p&gt;From the user's perspective, I wanted the experience to remain simple: one global search surface, similar to the issue-search experience developers are familiar with in tools like GitHub or GitLab.&lt;/p&gt;

&lt;p&gt;Underneath, the requirements evolved into a Lucene-style query model supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;free-text search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;key:value&lt;/code&gt; filters&lt;/li&gt;
&lt;li&gt;quoted phrases&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, and &lt;code&gt;NOT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;parentheses and grouping&lt;/li&gt;
&lt;li&gt;recursively nested expressions&lt;/li&gt;
&lt;li&gt;text filters&lt;/li&gt;
&lt;li&gt;date ranges&lt;/li&gt;
&lt;li&gt;multi-select fields&lt;/li&gt;
&lt;li&gt;column-level filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there was one requirement I considered just as important as the query language itself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Users shouldn't need to understand the query language to use it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the system supported two ways of expressing the same search intent.&lt;/p&gt;

&lt;p&gt;Power users could write the query directly.&lt;/p&gt;

&lt;p&gt;Everyone else could build it through column-level filters. Depending on the field, the UI provided a text input, date-range picker, multi-select dropdown, or Boolean condition and constructed the expression as the user filtered.&lt;/p&gt;

&lt;p&gt;Then the production dataset grew, and a second problem emerged:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;free-text search performance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That eventually led to another architectural decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I introduce a dedicated search engine, or can PostgreSQL continue owning search?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I kept PostgreSQL.&lt;/p&gt;

&lt;p&gt;After profiling the expensive path and moving the free-text workload to a GIN-backed indexing strategy, measured search latency dropped from roughly &lt;strong&gt;40ms to 12ms&lt;/strong&gt; on a production dataset containing more than &lt;strong&gt;20,000 records&lt;/strong&gt; at the time.&lt;/p&gt;

&lt;p&gt;The performance number is useful, but it isn't the most interesting part of the story.&lt;/p&gt;

&lt;p&gt;What interested me more was how one search box ended up touching UX design, language parsing, recursive data structures, database performance, and infrastructure trade-offs.&lt;/p&gt;




&lt;h2&gt;
  
  
  One search box, different levels of precision
&lt;/h2&gt;

&lt;p&gt;I didn't want advanced search to make basic search harder.&lt;/p&gt;

&lt;p&gt;Someone should always be able to type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and search normally.&lt;/p&gt;

&lt;p&gt;Nothing else should be required.&lt;/p&gt;

&lt;p&gt;A user who knows exactly what they're looking for can be more specific:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status:open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;owner:"John Doe"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free text and structured conditions can be combined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"payment approval" AND status:open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And power users can go much further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;("payment approval" OR refund)
AND (status:open OR status:pending)
AND NOT owner:system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gave the search experience &lt;strong&gt;progressive complexity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You don't need to understand the language to start searching.&lt;/p&gt;

&lt;p&gt;But when you need more precision, the language is there.&lt;/p&gt;

&lt;p&gt;The next problem was usability.&lt;/p&gt;

&lt;p&gt;Most users shouldn't have to learn that syntax.&lt;/p&gt;

&lt;p&gt;So I didn't make them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The UI could write the query for you
&lt;/h2&gt;

&lt;p&gt;Alongside the global search input, I designed column-level filtering.&lt;/p&gt;

&lt;p&gt;The control shown to the user depended on the type of data being filtered.&lt;/p&gt;

&lt;p&gt;For text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Owner
┌────────────────────────┐
│ John                   │
└────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For dates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created Date

From: 01 Aug 2026
To:   14 Aug 2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For predefined values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status

✓ Open
✓ Pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conditions could also include or exclude values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status
AND
Open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status
NOT
Archived
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As users interacted with those controls, the application constructed the corresponding search expression.&lt;/p&gt;

&lt;p&gt;A user might interact with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search: payment approval

Status:
  ✓ Open
  ✓ Pending

Owner:
  NOT System

Created:
  Aug 1 → Aug 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the system represented the same intent as something equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"payment approval"
AND (status:open OR status:pending)
AND NOT owner:system
AND createdDate:[2026-08-01 TO 2026-08-14]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user didn't need to write that.&lt;/p&gt;

&lt;p&gt;The UI did it for them.&lt;/p&gt;

&lt;p&gt;That became an important principle in the design:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A system can support a sophisticated query language without requiring users to speak that language.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Two interfaces, one search model
&lt;/h2&gt;

&lt;p&gt;I didn't want the visual filter builder and the advanced query input to become two independent search implementations.&lt;/p&gt;

&lt;p&gt;That would eventually create two sets of semantics, two places for bugs, and two implementations that could disagree about what the same filter means.&lt;/p&gt;

&lt;p&gt;Instead, both interfaces converged on the same search model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        ┌─────────────────────────┐
        │    Global Search Box    │
        │                         │
        │ Lucene-style syntax     │
        └────────────┬────────────┘
                     │
                     ▼
              Search Expression
                     ▲
                     │
        ┌────────────┴────────────┐
        │ Column Filter Builder   │
        │                         │
        │ • Text input            │
        │ • Date range            │
        │ • Multi-select          │
        │ • AND / NOT             │
        └─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the expression entered the same processing pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search Expression
       │
       ▼
   Tokenizer
       │
       ▼
     Parser
       │
       ▼
      AST
       │
       ▼
   Validation
       │
       ▼
 Query Compiler
       │
       ▼
 Prisma / SQL
       │
       ▼
  PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There weren't two search engines.&lt;/p&gt;

&lt;p&gt;There were &lt;strong&gt;two ways of expressing the same search intent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction kept the architecture much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  When search became a language
&lt;/h2&gt;

&lt;p&gt;Boolean operators changed the nature of the problem.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status:open OR status:pending AND priority:high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With normal Boolean precedence, that means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status:open
OR
(status:pending AND priority:high)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is different from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(status:open OR status:pending)
AND priority:high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add &lt;code&gt;NOT&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(status:open OR status:pending)
AND NOT owner:system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then nested grouping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(
  status:open
  OR (
    status:pending
    AND (
      priority:high
      OR priority:critical
    )
  )
)
AND NOT owner:system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, &lt;code&gt;.split("AND")&lt;/code&gt; isn't an architecture.&lt;/p&gt;

&lt;p&gt;Neither is continuing to grow a regular expression until nobody wants to touch it.&lt;/p&gt;

&lt;p&gt;The requirement had crossed an architectural boundary.&lt;/p&gt;

&lt;p&gt;I needed a parser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Treating search like a small compiler
&lt;/h2&gt;

&lt;p&gt;I ended up treating the search input similarly to a small language-processing pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw Query
    │
    ▼
Tokenizer
    │
    ▼
 Parser
    │
    ▼
   AST
    │
    ▼
Validator
    │
    ▼
Compiler
    │
    ▼
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage had a narrow responsibility.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;tokenizer&lt;/strong&gt; identified meaningful pieces of the language.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;parser&lt;/strong&gt; determined how those pieces related to each other.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;AST&lt;/strong&gt; represented the user's intent independently of the original string.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;validator&lt;/strong&gt; ensured that only supported fields and operations could be used.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;compiler&lt;/strong&gt; translated that structured representation into database conditions.&lt;/p&gt;

&lt;p&gt;That separation mattered.&lt;/p&gt;

&lt;p&gt;The parser didn't need to understand how PostgreSQL executed search.&lt;/p&gt;

&lt;p&gt;PostgreSQL didn't need to understand our user-facing syntax.&lt;/p&gt;

&lt;p&gt;And the database layer didn't need to repeatedly reinterpret an arbitrary search string.&lt;/p&gt;




&lt;h2&gt;
  
  
  From a query string to structured data
&lt;/h2&gt;

&lt;p&gt;Take:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(payment OR refund) AND status:open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tokenizer can identify units such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LPAREN
TEXT(payment)
OR
TEXT(refund)
RPAREN
AND
FIELD(status, open)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quoted values remain intact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;owner:"John Doe"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as do quoted free-text phrases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"payment approval"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parentheses and Boolean operators remain explicit because they determine the structure of the expression.&lt;/p&gt;

&lt;p&gt;Now consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(payment OR refund) AND NOT status:archived
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of carrying that string through the application, the parser can represent its meaning as a tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 AND
                /   \
              OR     NOT
             /  \      \
      payment  refund   FIELD
                         │
                   status:archived
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified TypeScript representation could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SearchNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;field&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;and&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;or&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;operand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact TypeScript isn't the interesting part.&lt;/p&gt;

&lt;p&gt;The boundary is.&lt;/p&gt;

&lt;p&gt;The user's arbitrary string has become &lt;strong&gt;structured data&lt;/strong&gt; that can be validated, transformed, tested, and compiled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Supporting N-level nested search
&lt;/h2&gt;

&lt;p&gt;I didn't want the grammar to contain a hard-coded concept of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;level 1
level 2
level 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expressions are recursive.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expression :=
    FreeText
  | FieldCondition
  | NOT Expression
  | (Expression)
  | Expression AND Expression
  | Expression OR Expression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An expression can contain another expression, which can contain another expression.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A AND (B OR (C AND (D OR (E AND NOT F))))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't require special handling.&lt;/p&gt;

&lt;p&gt;It's simply a deeper tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             AND
            /   \
           A     OR
                /  \
               B    AND
                   /   \
                  C     OR
                       /  \
                      D    AND
                          /   \
                         E    NOT
                               │
                               F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler recursively walks the tree.&lt;/p&gt;

&lt;p&gt;There is &lt;strong&gt;no fixed application-level nesting depth in the search grammar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That lets users continue composing conditions when they need more precision instead of running into an arbitrary maximum number of filter groups.&lt;/p&gt;

&lt;p&gt;Of course, that doesn't mean execution has infinite resources.&lt;/p&gt;

&lt;p&gt;Request size, runtime behavior, query complexity, timeouts, and infrastructure still create practical boundaries.&lt;/p&gt;

&lt;p&gt;Those are operational constraints rather than artificial limits in the grammar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Search fields are an API, not database columns
&lt;/h2&gt;

&lt;p&gt;Supporting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status:open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;raises another question.&lt;/p&gt;

&lt;p&gt;What happens if someone tries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;someInternalDatabaseField:value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A search language shouldn't automatically become an interface to the underlying database schema.&lt;/p&gt;

&lt;p&gt;Instead, searchable fields should be explicitly supported.&lt;/p&gt;

&lt;p&gt;A simplified mapping might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchableFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* internal mapping */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* internal mapping */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* internal mapping */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* internal mapping */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual implementation can contain considerably more metadata because different fields require different controls, validation rules, and database behavior.&lt;/p&gt;

&lt;p&gt;But the principle is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The search language has its own contract.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A field becomes searchable because the application deliberately exposes it, not because a similarly named PostgreSQL column happens to exist.&lt;/p&gt;

&lt;p&gt;That also gives the database schema room to evolve without necessarily breaking the query syntax users already know.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then the dataset grew
&lt;/h2&gt;

&lt;p&gt;The most complicated-looking part of this architecture wasn't where I encountered the main performance problem.&lt;/p&gt;

&lt;p&gt;The parser worked.&lt;/p&gt;

&lt;p&gt;Recursive expressions worked.&lt;/p&gt;

&lt;p&gt;Structured filters worked.&lt;/p&gt;

&lt;p&gt;The problem appeared somewhere much less exotic:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;free-text search.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the dataset was smaller, the existing implementation performed well enough.&lt;/p&gt;

&lt;p&gt;Then production data accumulated.&lt;/p&gt;

&lt;p&gt;The same kind of free-text search now had increasingly more data to work through.&lt;/p&gt;

&lt;p&gt;The code hadn't necessarily regressed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workload had changed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's an important distinction.&lt;/p&gt;

&lt;p&gt;A query that behaves perfectly well at one data volume can have very different characteristics as that volume grows.&lt;/p&gt;

&lt;p&gt;So I didn't start by optimizing the parser.&lt;/p&gt;

&lt;p&gt;I started by measuring the database path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measure before changing the architecture
&lt;/h2&gt;

&lt;p&gt;I wanted to understand what PostgreSQL was actually doing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; was part of that investigation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was looking for answers to concrete questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many rows are being examined?&lt;/li&gt;
&lt;li&gt;Are we scanning more data than necessary?&lt;/li&gt;
&lt;li&gt;Are the indexes I expect actually being used?&lt;/li&gt;
&lt;li&gt;Which part of the generated condition is expensive?&lt;/li&gt;
&lt;li&gt;Where is execution time being spent?&lt;/li&gt;
&lt;li&gt;How does the execution plan behave as the dataset grows?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction mattered.&lt;/p&gt;

&lt;p&gt;The system contained sophisticated application code, but optimizing the most complicated-looking code wouldn't help if PostgreSQL was doing the expensive work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Optimize what the measurements tell you is expensive, not what looks complicated.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why GIN fit the workload
&lt;/h2&gt;

&lt;p&gt;Structured filters and free text have different access patterns.&lt;/p&gt;

&lt;p&gt;A B-tree index is a natural fit for many structured conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'OPEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free-text search asks a different kind of question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which records contain these searchable terms?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where an inverted index becomes useful.&lt;/p&gt;

&lt;p&gt;PostgreSQL already provides this capability through &lt;strong&gt;GIN — Generalized Inverted Index&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At a high level, an inverted index maintains relationships between searchable terms and the records containing them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment ─────► Record 12
        ├────► Record 48
        └────► Record 91

approval ────► Record 12
         └───► Record 103

invoice ─────► Record 27
        └────► Record 91
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified PostgreSQL example might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_records_search&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;searchable_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a corresponding full-text condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;searchable_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'payment approval'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production implementation was more involved because free text could appear alongside structured conditions and recursively nested Boolean expressions.&lt;/p&gt;

&lt;p&gt;Architecturally, though, the responsibilities remained separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Search AST
                         │
               ┌─────────┴─────────┐
               ▼                   ▼
         Structured            Free Text
          Conditions              Search
               │                   │
               ▼                   ▼
          Appropriate             GIN
           indexes               index
               │                   │
               └─────────┬─────────┘
                         ▼
                     PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query language remained expressive while the expensive free-text path could be optimized independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  From roughly 40ms to 12ms
&lt;/h2&gt;

&lt;p&gt;After optimizing the free-text path around the appropriate PostgreSQL indexing strategy, measured search latency moved approximately from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before       After
 ~40ms   →    ~12ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's roughly a &lt;strong&gt;70% reduction&lt;/strong&gt; for the workload we measured.&lt;/p&gt;

&lt;p&gt;The production dataset contained more than &lt;strong&gt;20,000 records&lt;/strong&gt; at the time.&lt;/p&gt;

&lt;p&gt;That result needs context.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add GIN and PostgreSQL becomes 70% faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Database performance depends on schema, data distribution, query shape, hardware, cache state, and workload.&lt;/p&gt;

&lt;p&gt;The useful part is the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Observe
   ↓
Measure
   ↓
Inspect the execution plan
   ↓
Understand the access pattern
   ↓
Choose the appropriate index
   ↓
Measure again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GIN fit our access pattern.&lt;/p&gt;

&lt;p&gt;The decision came from the workload, not the other way around.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I didn't introduce Elasticsearch
&lt;/h2&gt;

&lt;p&gt;Once free-text search became a performance discussion, a dedicated search engine was an obvious option to consider.&lt;/p&gt;

&lt;p&gt;Elasticsearch could handle this kind of search.&lt;/p&gt;

&lt;p&gt;But capability alone wasn't enough reason for me to introduce another system.&lt;/p&gt;

&lt;p&gt;PostgreSQL was already part of the production architecture and remained the source of truth.&lt;/p&gt;

&lt;p&gt;Keeping search there meant the architecture remained roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Application
                   │
                   ▼
              PostgreSQL
             ┌─────┴─────┐
             │           │
       Structured     Free Text
         Search         Search
             │           │
        Appropriate     GIN
         indexes        index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Introducing another search datastore changes that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   Application
                        │
                ┌───────┴───────┐
                ▼               ▼
           PostgreSQL      Search Engine
                │               ▲
                └───── Sync ────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That synchronization arrow looks small on an architecture diagram.&lt;/p&gt;

&lt;p&gt;Operationally, it isn't.&lt;/p&gt;

&lt;p&gt;It introduces concerns around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data synchronization&lt;/li&gt;
&lt;li&gt;eventual consistency&lt;/li&gt;
&lt;li&gt;failed indexing operations&lt;/li&gt;
&lt;li&gt;retry and replay&lt;/li&gt;
&lt;li&gt;index rebuilding&lt;/li&gt;
&lt;li&gt;mappings&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;deployments and upgrades&lt;/li&gt;
&lt;li&gt;infrastructure cost&lt;/li&gt;
&lt;li&gt;another failure domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those make Elasticsearch a bad choice.&lt;/p&gt;

&lt;p&gt;They're simply part of the cost of owning another system.&lt;/p&gt;

&lt;p&gt;For our workload, PostgreSQL was already there, GIN matched the free-text access pattern, and the resulting performance satisfied the requirements.&lt;/p&gt;

&lt;p&gt;Adding another datastore would have increased the operational surface without solving a problem we still had.&lt;/p&gt;

&lt;p&gt;So I didn't add one.&lt;/p&gt;




&lt;h2&gt;
  
  
  GIN was an architectural decision, not just an index
&lt;/h2&gt;

&lt;p&gt;It's easy to summarize the change as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I added a GIN index and made search faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's technically part of what happened.&lt;/p&gt;

&lt;p&gt;But the architectural decision was larger.&lt;/p&gt;

&lt;p&gt;I effectively had three directions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Growing Search Cost
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
         Leave it     Optimize     Introduce
          as-is       PostgreSQL   Search Engine
                          │
                          ▼
                         GIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing nothing wasn't going to age well.&lt;/p&gt;

&lt;p&gt;A dedicated search engine could solve the problem, but it would introduce another operational component.&lt;/p&gt;

&lt;p&gt;Optimizing PostgreSQL gave me a middle path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use more of the capabilities of the system we already operate before introducing another one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That kept PostgreSQL as the source of truth.&lt;/p&gt;

&lt;p&gt;No search synchronization pipeline.&lt;/p&gt;

&lt;p&gt;No additional datastore.&lt;/p&gt;

&lt;p&gt;No additional operational dependency.&lt;/p&gt;

&lt;p&gt;And it met the performance requirements.&lt;/p&gt;

&lt;p&gt;The question wasn't which technology was more powerful.&lt;/p&gt;

&lt;p&gt;The question was which architecture introduced the right amount of complexity for the problem we actually had.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I would introduce a dedicated search engine
&lt;/h2&gt;

&lt;p&gt;This isn't an argument that PostgreSQL should replace Elasticsearch.&lt;/p&gt;

&lt;p&gt;I'd revisit the architecture if the requirements started demanding things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sophisticated relevance ranking&lt;/li&gt;
&lt;li&gt;advanced fuzzy matching and typo tolerance&lt;/li&gt;
&lt;li&gt;complex language-specific analysis&lt;/li&gt;
&lt;li&gt;significantly larger search workloads&lt;/li&gt;
&lt;li&gt;independent scaling of search&lt;/li&gt;
&lt;li&gt;complex search aggregations&lt;/li&gt;
&lt;li&gt;search-specific availability requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, specialized search infrastructure may provide enough value to justify its operational cost.&lt;/p&gt;

&lt;p&gt;The useful question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PostgreSQL or Elasticsearch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Have the requirements crossed the point where owning another system is justified?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For us, they hadn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Leaving room to evolve without building the future
&lt;/h2&gt;

&lt;p&gt;There was another reason I was comfortable keeping PostgreSQL.&lt;/p&gt;

&lt;p&gt;The query language wasn't directly coupled to it.&lt;/p&gt;

&lt;p&gt;The boundary looked roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Intent
     │
     ▼
Search Expression
     │
     ▼
    AST
     │
     ▼
Validation
     │
     ▼
PostgreSQL Compiler
     │
     ▼
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL was the current execution strategy.&lt;/p&gt;

&lt;p&gt;It wasn't the definition of the search language.&lt;/p&gt;

&lt;p&gt;If the requirements eventually justify another backend, there's a natural architectural seam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       AST
                        │
                ┌───────┴───────┐
                ▼               ▼
          PostgreSQL        Dedicated
           Compiler       Search Compiler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wouldn't implement that second compiler today.&lt;/p&gt;

&lt;p&gt;That's an important distinction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designing a boundary is useful. Building hypothetical infrastructure isn't.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently today
&lt;/h2&gt;

&lt;p&gt;If I were starting this feature again, I'd formalize the query language earlier.&lt;/p&gt;

&lt;p&gt;Search requirements tend to grow incrementally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status:open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment AND status:open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment AND (status:open OR status:pending)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and eventually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(
  payment
  OR (
    refund
    AND (status:open OR status:pending)
  )
)
AND NOT owner:system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At some point, you've created a language without explicitly deciding to create one.&lt;/p&gt;

&lt;p&gt;Once structured fields, Boolean operators, and grouping start appearing in the requirements, I'd define the boundaries early:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Grammar
   ↓
Tokenizer
   ↓
Parser
   ↓
AST
   ↓
Validation
   ↓
Compilation
   ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes everything that follows easier to reason about and test.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I took away from building it
&lt;/h2&gt;

&lt;p&gt;Looking back, what appeared to be one feature was really three different engineering problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expressiveness
&lt;/h3&gt;

&lt;p&gt;Free text gradually became a query language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;free text
key:value
AND / OR / NOT
parentheses
recursive expressions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That required treating search as structured input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenize → parse → AST → validate → compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usability
&lt;/h3&gt;

&lt;p&gt;The query language could be powerful without becoming a prerequisite for using search.&lt;/p&gt;

&lt;p&gt;The column-level filter builder exposed controls appropriate to the underlying data—text inputs, date ranges, multi-selects, and Boolean conditions—and generated the same search representation underneath.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two interfaces. One search model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The complexity stayed in the system instead of being pushed onto every user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale
&lt;/h3&gt;

&lt;p&gt;The part that looked complicated wasn't the part that eventually became expensive.&lt;/p&gt;

&lt;p&gt;As the dataset grew, free-text search became the bottleneck.&lt;/p&gt;

&lt;p&gt;The response wasn't to rewrite the parser or immediately introduce another datastore.&lt;/p&gt;

&lt;p&gt;It was to measure the query path, inspect what PostgreSQL was doing, and optimize the access pattern that was actually expensive.&lt;/p&gt;

&lt;p&gt;For our workload, a GIN-backed strategy brought measured search latency from roughly &lt;strong&gt;40ms to 12ms&lt;/strong&gt; while allowing PostgreSQL to remain the source of truth.&lt;/p&gt;

&lt;p&gt;Could a dedicated search engine become the right architecture later?&lt;/p&gt;

&lt;p&gt;Absolutely.&lt;/p&gt;

&lt;p&gt;But I didn't want to pay the synchronization, consistency, infrastructure, and operational costs of another system before the requirements justified them.&lt;/p&gt;

&lt;p&gt;That's probably the main thing I took away from building this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good architecture isn't about eliminating complexity. It's about putting complexity in the right place—and only introducing it when the problem actually requires it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>postgressql</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Your Dog Has Thoughts. I Built an AI to Translate Them 🐶</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Fri, 14 Aug 2026 12:03:00 +0000</pubDate>
      <link>https://dev.to/anilloutombam/your-dog-has-thoughts-i-built-an-ai-to-translate-them-3fk9</link>
      <guid>https://dev.to/anilloutombam/your-dog-has-thoughts-i-built-an-ai-to-translate-them-3fk9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;Dog Mind&lt;/strong&gt;, a playful AI-powered web app that turns a dog photo into an entertaining interpretation of the dog’s mood, visible body language, personality signals, and imaginary inner monologue.&lt;/p&gt;

&lt;p&gt;Upload a photo and Dog Mind generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cautious breed or dog-type guess&lt;/li&gt;
&lt;li&gt;A mood and confidence score&lt;/li&gt;
&lt;li&gt;Happiness, energy, and mischief ratings&lt;/li&gt;
&lt;li&gt;Observations based on visible body-language signals&lt;/li&gt;
&lt;li&gt;A short personality summary&lt;/li&gt;
&lt;li&gt;A funny, family-friendly imaginary thought&lt;/li&gt;
&lt;li&gt;A matching voice personality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the analysis, users can hear the dog’s imagined thought spoken aloud, ask up to three follow-up questions, and download or share the final result card.&lt;/p&gt;

&lt;p&gt;No dog photo available? The &lt;strong&gt;Try a sample dog&lt;/strong&gt; button opens a pre-generated experience without consuming an AI request.&lt;/p&gt;

&lt;p&gt;Dog Mind is made for entertainment. It does not claim to read a dog’s mind or provide veterinary or behavioral advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🐶 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://dog-mind-eight.vercel.app" rel="noopener noreferrer"&gt;dog-mind-eight.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To try it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload a JPG, PNG, or WebP photo of a dog.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Analyze my dog&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Review the dog’s mood, signals, and imaginary inner monologue.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Hear this dog’s voice&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Ask the dog up to three playful questions.&lt;/li&gt;
&lt;li&gt;Share or download the result card.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also select &lt;strong&gt;Try a sample dog&lt;/strong&gt; for an instant preview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The complete source code is available on GitHub:&lt;/p&gt;

&lt;p&gt;🐙 &lt;a href="https://github.com/anilloutombam/dog-mind" rel="noopener noreferrer"&gt;github.com/anilloutombam/dog-mind&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Dog Mind uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;li&gt;Google Gemini&lt;/li&gt;
&lt;li&gt;ElevenLabs&lt;/li&gt;
&lt;li&gt;Zod&lt;/li&gt;
&lt;li&gt;pnpm&lt;/li&gt;
&lt;li&gt;Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Multimodal dog analysis with Google Gemini
&lt;/h3&gt;

&lt;p&gt;When a user uploads a photo, the browser sends it to a server-side Next.js route as multipart form data.&lt;/p&gt;

&lt;p&gt;Before contacting Gemini, the server checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The declared MIME type&lt;/li&gt;
&lt;li&gt;The file size&lt;/li&gt;
&lt;li&gt;The actual binary image signature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents renamed or unsupported files from being accepted based only on their extensions.&lt;/p&gt;

&lt;p&gt;Gemini first determines whether the primary subject is a dog. For a valid dog photo, it returns structured JSON containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;breedGuess&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;breedConfidence&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dogSize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;voiceStyle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mood&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;confidence&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signals&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;observations&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thought&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shared Zod schema validates the complete response before it reaches the interface. It also enforces the same character and number limits expected by downstream features.&lt;/p&gt;

&lt;p&gt;The prompt asks Gemini to interpret only clearly visible signals, avoid medical claims, make cautious breed guesses, and keep the imaginary thought short and family-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Giving the dog a voice with ElevenLabs
&lt;/h3&gt;

&lt;p&gt;Dog Mind sends the generated inner monologue to ElevenLabs only after the user selects &lt;strong&gt;Hear this dog’s voice&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Gemini recommends one of six controlled voice personalities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bright&lt;/li&gt;
&lt;li&gt;Warm&lt;/li&gt;
&lt;li&gt;Bold&lt;/li&gt;
&lt;li&gt;Dramatic&lt;/li&gt;
&lt;li&gt;Gentle&lt;/li&gt;
&lt;li&gt;Gruff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each personality maps to approved ElevenLabs stability, style, and speed settings.&lt;/p&gt;

&lt;p&gt;The voice choice is inspired by the dog’s visible size, expression, and energy. It is a playful creative decision, not a scientific claim about breed behavior.&lt;/p&gt;

&lt;p&gt;Generated audio is cached by text and voice personality. Replaying the same voice can reuse the existing audio, while &lt;strong&gt;New take&lt;/strong&gt; deliberately requests a fresh performance.&lt;/p&gt;

&lt;p&gt;The audio cache uses a fixed size and removes older entries so it cannot grow indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask Your Dog
&lt;/h3&gt;

&lt;p&gt;After the initial analysis, users can continue the imaginary conversation by asking the dog up to three questions.&lt;/p&gt;

&lt;p&gt;The follow-up request sends only the established dog persona and the new question. It does not upload the image again.&lt;/p&gt;

&lt;p&gt;Gemini also acts as a topic guard. It rejects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unrelated questions&lt;/li&gt;
&lt;li&gt;Role-changing instructions&lt;/li&gt;
&lt;li&gt;Prompt-injection attempts&lt;/li&gt;
&lt;li&gt;Requests to reveal internal prompts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dog health questions receive no diagnosis or treatment advice.&lt;/p&gt;

&lt;p&gt;The three-question limit is enforced on the server instead of relying only on a disabled client button. Conversation records expire after 24 hours, expired entries are pruned, and the process-local store has a maximum size.&lt;/p&gt;

&lt;p&gt;The client persists the successful question count and synchronizes it between browser tabs. When the limit is reached, the API returns a machine-readable terminal state so the interface hides the form instead of displaying a misleading retry button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching repeated image analysis
&lt;/h3&gt;

&lt;p&gt;Gemini API requests are valuable, especially while working within free-tier limits.&lt;/p&gt;

&lt;p&gt;Dog Mind calculates a SHA-256 digest of each uploaded image and uses it as a cache key. If the same image is analyzed again during the browser session, the validated result can be reused without another Gemini request.&lt;/p&gt;

&lt;p&gt;Cached values are validated when they are read and written. Invalid stored values are removed instead of being trusted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling stale requests
&lt;/h3&gt;

&lt;p&gt;A user can select another image, reset the experience, or open the sample result while an earlier request is still running.&lt;/p&gt;

&lt;p&gt;To prevent an old response from replacing a newer state, Dog Mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assigns a generation number to each analysis&lt;/li&gt;
&lt;li&gt;Cancels superseded requests with &lt;code&gt;AbortController&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Checks the generation after asynchronous operations&lt;/li&gt;
&lt;li&gt;Discards responses that no longer belong to the active image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the UI consistent even when users move quickly between states.&lt;/p&gt;

&lt;h3&gt;
  
  
  Friendly failure and quota handling
&lt;/h3&gt;

&lt;p&gt;AI APIs can fail because of temporary capacity, rate limits, exhausted quotas, network problems, or invalid responses.&lt;/p&gt;

&lt;p&gt;Dog Mind provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Friendly Gemini 429 messages&lt;/li&gt;
&lt;li&gt;Retry timing from response metadata&lt;/li&gt;
&lt;li&gt;Live retry countdowns&lt;/li&gt;
&lt;li&gt;Disabled retry controls during cooldowns&lt;/li&gt;
&lt;li&gt;Retry actions for analysis, voice generation, and dog chat&lt;/li&gt;
&lt;li&gt;Provider timeouts&lt;/li&gt;
&lt;li&gt;Safe request IDs for diagnostics&lt;/li&gt;
&lt;li&gt;Graceful handling for non-dog images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app also includes a pre-generated sample so visitors can still explore the main interface when the AI provider is temporarily unavailable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and privacy
&lt;/h3&gt;

&lt;p&gt;Security was an important part of the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemini and ElevenLabs credentials remain server-side&lt;/li&gt;
&lt;li&gt;Images are checked in both the browser and server&lt;/li&gt;
&lt;li&gt;Binary signatures are verified&lt;/li&gt;
&lt;li&gt;Uploads are limited to 5 MB&lt;/li&gt;
&lt;li&gt;AI responses are schema-validated&lt;/li&gt;
&lt;li&gt;Client errors do not reveal credentials or stack traces&lt;/li&gt;
&lt;li&gt;Follow-up prompts receive minimal dog context&lt;/li&gt;
&lt;li&gt;API routes use request throttling and provider timeouts&lt;/li&gt;
&lt;li&gt;Security headers prevent framing and MIME sniffing&lt;/li&gt;
&lt;li&gt;Camera, microphone, and location access are disabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building the experience
&lt;/h3&gt;

&lt;p&gt;I wanted Dog Mind to feel playful without becoming confusing.&lt;/p&gt;

&lt;p&gt;The interface includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag-and-drop uploads&lt;/li&gt;
&lt;li&gt;Full-image previews without cropping&lt;/li&gt;
&lt;li&gt;Animated analysis progress&lt;/li&gt;
&lt;li&gt;A running dog progress indicator&lt;/li&gt;
&lt;li&gt;Rotating dog facts&lt;/li&gt;
&lt;li&gt;Screen-reader announcements&lt;/li&gt;
&lt;li&gt;Reduced-motion support&lt;/li&gt;
&lt;li&gt;Responsive layouts&lt;/li&gt;
&lt;li&gt;Native sharing where supported&lt;/li&gt;
&lt;li&gt;Downloadable square result cards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The codebase uses a feature-first structure. Route files compose the page, feature modules own the workflow, and server utilities centralize validation, throttling, and safe error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keeping playful AI output honest
&lt;/h3&gt;

&lt;p&gt;Dog Mind creates a humorous interpretation of a dog’s expression, but the result should never be confused with a diagnosis or a factual reading of the animal’s thoughts.&lt;/p&gt;

&lt;p&gt;The prompts, interface copy, and error messages consistently describe the output as an imaginary interpretation based on visible clues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coordinating multiple AI services
&lt;/h3&gt;

&lt;p&gt;Gemini and ElevenLabs have different request formats, limits, errors, and response types.&lt;/p&gt;

&lt;p&gt;Keeping both integrations behind server routes gave the browser one consistent application API while protecting provider credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Designing useful recovery states
&lt;/h3&gt;

&lt;p&gt;A failed request should not leave the user stuck.&lt;/p&gt;

&lt;p&gt;Retry buttons, cooldown countdowns, cached results, audio reuse, stale-request cancellation, and the sample experience all help users continue without restarting the entire flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Best Use of Google AI
&lt;/h3&gt;

&lt;p&gt;Google Gemini powers the core multimodal experience. It examines dog photos, identifies visible body-language signals, produces structured mood data, creates the imaginary inner monologue, recommends a controlled voice personality, and handles guarded follow-up conversations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of ElevenLabs
&lt;/h3&gt;

&lt;p&gt;ElevenLabs transforms the dog’s imaginary thought and optional follow-up replies into expressive voice performances. Controlled presets adapt stability, speed, and expression to complement the visual analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Dog Mind began with a funny question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if AI could translate the look your dog gives you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building it showed me that playful AI products still need careful engineering.&lt;/p&gt;

&lt;p&gt;Schema validation, file-signature checks, caching, rate-limit handling, stale-request protection, prompt-injection resistance, accessibility, and honest disclaimers made the experience more reliable and enjoyable.&lt;/p&gt;

&lt;p&gt;The best AI experience is not only about generating an answer. It is also about presenting that answer with the right personality, boundaries, and recovery path when something goes wrong.&lt;/p&gt;

&lt;p&gt;Thanks for checking out Dog Mind! 🐾&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>googleai</category>
      <category>elevenlabs</category>
    </item>
    <item>
      <title>Fixing an Infinite Loading State in the npmx Code Browser</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:23:15 +0000</pubDate>
      <link>https://dev.to/anilloutombam/fixing-an-infinite-loading-state-in-the-npmx-code-browser-n25</link>
      <guid>https://dev.to/anilloutombam/fixing-an-infinite-loading-state-in-the-npmx-code-browser-n25</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx&lt;/a&gt; is an open-source package explorer for the npm ecosystem. It helps developers inspect package metadata, releases, dependencies, documentation, comparisons, and published source files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;The npmx code browser has a fallback for files that exceed its supported display size. It should show a “File too large” warning and provide an option to open the raw file.&lt;/p&gt;

&lt;p&gt;Instead, selecting an oversized file left the page displaying its loading state indefinitely.&lt;/p&gt;

&lt;p&gt;The issue can be reproduced here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://npmx.dev/package-code/@types/vscode/v/1.118.0/index.d.ts" rel="noopener noreferrer"&gt;https://npmx.dev/package-code/@types/vscode/v/1.118.0/index.d.ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The corrected behaviour can be verified on the PR preview:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://npmx-h3s403v0g-npmx.vercel.app/package-code/@types/vscode/v/1.118.0/index.d.ts" rel="noopener noreferrer"&gt;https://npmx-h3s403v0g-npmx.vercel.app/package-code/@types/vscode/v/1.118.0/index.d.ts&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub issue: &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2732" rel="noopener noreferrer"&gt;https://github.com/npmx-dev/npmx.dev/issues/2732&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pull request: &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3120" rel="noopener noreferrer"&gt;https://github.com/npmx-dev/npmx.dev/pull/3120&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The production fix is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isFileTooLarge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was added to the page’s loading-state calculation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isViewingFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;treeStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;treeStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isFileTooLarge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fileStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fileStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fileStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;The page already knew when a selected file exceeded the size limit. In that situation, it intentionally skipped the file-content request.&lt;/p&gt;

&lt;p&gt;Because the request never started, its status remained &lt;code&gt;idle&lt;/code&gt;. The loading logic treated that status as if the file were still waiting to load, so the loading skeleton prevented the existing fallback from appearing.&lt;/p&gt;

&lt;p&gt;The new condition makes the page stop reporting a loading state when the file is already known to be too large. This allows the existing warning and raw-file action to render without changing the server limit or downloading unnecessary content.&lt;/p&gt;

&lt;p&gt;I also added a regression test that verifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The large-file warning is displayed.&lt;/li&gt;
&lt;li&gt;The page does not remain in its loading state.&lt;/li&gt;
&lt;li&gt;The file-content endpoint is not called.&lt;/li&gt;
&lt;li&gt;The raw-file action points to the correct jsDelivr URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code change is small, but it fixes an important state-handling edge case: an &lt;code&gt;idle&lt;/code&gt; request is not always waiting to begin. Sometimes the application has intentionally decided that it should not run.&lt;/p&gt;




&lt;p&gt;If you're interested in more of my open-source work and engineering projects, you can explore my &lt;a href="https://anilsingha.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=npmx-infinite-loading" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; or follow my work on &lt;a href="https://github.com/anilloutombam" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Fixing a Search Race Condition in npmx</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:55:07 +0000</pubDate>
      <link>https://dev.to/anilloutombam/fixing-a-search-race-condition-in-npmx-4la3</link>
      <guid>https://dev.to/anilloutombam/fixing-a-search-race-condition-in-npmx-4la3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx&lt;/a&gt; is a modern browser for the npm registry. It provides package search, version timelines, dependency information, comparisons, and other tools for exploring npm packages.&lt;/p&gt;

&lt;p&gt;I've been contributing to npmx recently, mostly around search behavior and edge cases. One of those contributions started with a particularly interesting symptom:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The search failed the first time, but worked after a refresh.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;The bug was reported in &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2617" rel="noopener noreferrer"&gt;issue #2617&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reproduction was quite specific.&lt;/p&gt;

&lt;p&gt;With instant search disabled and a saved page size of 50 or higher:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the search page.&lt;/li&gt;
&lt;li&gt;Search for a package.&lt;/li&gt;
&lt;li&gt;No package results appear.&lt;/li&gt;
&lt;li&gt;The organization suggestion can still appear.&lt;/li&gt;
&lt;li&gt;Refresh the exact same page.&lt;/li&gt;
&lt;li&gt;The package results suddenly show up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same query. Same page size. Same application.&lt;/p&gt;

&lt;p&gt;The refresh behavior was the interesting part.&lt;/p&gt;

&lt;p&gt;If the packages didn't exist, refreshing shouldn't make them appear. And if the search request itself was fundamentally broken, I'd expect the second attempt to fail too.&lt;/p&gt;

&lt;p&gt;That pointed away from the search data itself and toward what was happening during the &lt;strong&gt;initialization of the page&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Following the timing
&lt;/h3&gt;

&lt;p&gt;npmx stores the user's preferred search page size locally.&lt;/p&gt;

&lt;p&gt;On the first navigation to the search page, however, the initial search could begin before that saved preference had finished loading.&lt;/p&gt;

&lt;p&gt;The sequence looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search page opens
        ↓
Initial search starts
(page size = 25)
        ↓
Saved preference loads
        ↓
Page size changes to 50
        ↓
fetchMore() runs
        ↓
Initial search is still pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Individually, none of these operations were wrong.&lt;/p&gt;

&lt;p&gt;The problem was &lt;strong&gt;when they happened relative to each other&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetchMore()&lt;/code&gt; could run while the initial Algolia search was still pending.&lt;/p&gt;

&lt;p&gt;At that moment, the search result hadn't arrived yet. &lt;code&gt;fetchMore()&lt;/code&gt; could therefore read the temporary empty response and store that value in the search cache.&lt;/p&gt;

&lt;p&gt;Then the original search completed with the real package results.&lt;/p&gt;

&lt;p&gt;But it was too late.&lt;/p&gt;

&lt;p&gt;The empty cached value could take priority, leaving the UI showing no packages even though the search had successfully returned them.&lt;/p&gt;

&lt;p&gt;That also explained why refreshing appeared to magically fix the problem.&lt;/p&gt;

&lt;p&gt;It wasn't magic.&lt;/p&gt;

&lt;p&gt;The initialization timing had changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The fix is in &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3109" rel="noopener noreferrer"&gt;npmx-dev/npmx.dev#3109&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead of allowing &lt;code&gt;fetchMore()&lt;/code&gt; to continue while the initial search is unresolved, it now checks whether that search is still pending and waits for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;asyncData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dedupe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;defer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail here is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;dedupe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;defer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't want &lt;code&gt;fetchMore()&lt;/code&gt; to cancel the search that was already running and replace it with another request.&lt;/p&gt;

&lt;p&gt;It needed to &lt;strong&gt;join the existing in-flight request&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the flow changed from something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial search ────────────────► package results
        │
        └── fetchMore()
                ↓
          reads empty state
                ↓
          empty value cached
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial search ────────────────► package results
                                      ↓
                               fetchMore() continues
                                      ↓
                               results preserved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a small change in code, but it changes the ordering guarantee between the two asynchronous operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;The main fix was making &lt;code&gt;fetchMore()&lt;/code&gt; wait for the pending initial search before consuming its result.&lt;/p&gt;

&lt;p&gt;But a race-condition fix also needs a test that can reliably reproduce the timing that caused it.&lt;/p&gt;

&lt;p&gt;Otherwise, the test might pass simply because the operations happened in a convenient order that particular time.&lt;/p&gt;

&lt;p&gt;So I added a regression test that deliberately recreates the problematic sequence.&lt;/p&gt;

&lt;p&gt;The test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Starts the initial Algolia search.&lt;/li&gt;
&lt;li&gt;Keeps that request pending.&lt;/li&gt;
&lt;li&gt;Changes the requested page size.&lt;/li&gt;
&lt;li&gt;Triggers the additional result-loading path.&lt;/li&gt;
&lt;li&gt;Resolves the original search.&lt;/li&gt;
&lt;li&gt;Verifies that the completed package results are preserved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That was important because the test isn't relying on the race condition happening naturally.&lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;controls the timing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After the fix, users with a saved page size of 50 or higher get their package results on the first search without needing to refresh the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I took away from this bug
&lt;/h3&gt;

&lt;p&gt;What made this issue interesting wasn't the amount of code required to fix it.&lt;/p&gt;

&lt;p&gt;It was the symptom:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It doesn't work the first time, but refreshing fixes it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That can be a useful debugging clue.&lt;/p&gt;

&lt;p&gt;When persisted client state, initialization, caching, and asynchronous requests interact, a refresh can change the order in which those operations happen.&lt;/p&gt;

&lt;p&gt;In this case, the individual pieces were working.&lt;/p&gt;

&lt;p&gt;The initial search worked.&lt;/p&gt;

&lt;p&gt;The saved preference worked.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetchMore()&lt;/code&gt; worked.&lt;/p&gt;

&lt;p&gt;The problem only appeared when they ran in a particular order.&lt;/p&gt;

&lt;p&gt;The final fix wasn't to make the search faster or add another request.&lt;/p&gt;

&lt;p&gt;It was simply to make one asynchronous operation respect another operation that was &lt;strong&gt;already in progress&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes fixing a race condition is less about doing more work and more about making sure the work you already have happens in the right order.&lt;/p&gt;




&lt;p&gt;If you're interested in more of my open-source work and engineering projects, you can explore my &lt;a href="https://anilsingha.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=npmx-search-race-condition" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; or follow my work on &lt;a href="https://github.com/anilloutombam" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Midnight Ramen: A Rainy CSS Art Love Letter to Comfort Food</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Fri, 31 Jul 2026 15:29:29 +0000</pubDate>
      <link>https://dev.to/anilloutombam/midnight-ramen-a-rainy-css-art-love-letter-to-comfort-food-hed</link>
      <guid>https://dev.to/anilloutombam/midnight-ramen-a-rainy-css-art-love-letter-to-comfort-food-hed</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge - Comfort Food Edition, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;My inspiration was a warm bowl of ramen on a quiet, rainy night—the kind of meal that feels comforting before you even take the first bite.&lt;/p&gt;

&lt;p&gt;I wanted to capture more than the food itself, so I created a complete midnight ramen-shop scene. The steaming bowl is surrounded by a glowing lantern, falling rain, a handwritten order slip, shelves of ingredients, and a tiny shop cat peeking over the counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/anilloutombam/embed/019fb8b9-6447-7117-9662-e41035bfc2c4?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can also explore the source code on &lt;a href="https://github.com/anilloutombam/midnight-ramen-css-art" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;I created the illustration using HTML and CSS, without image assets or JavaScript. The bowl, broth, noodles, egg, chashu pork, nori, narutomaki, mushrooms, scallions, lantern, window, furniture, and cat are all composed from regular HTML elements.&lt;/p&gt;

&lt;p&gt;The visual details rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layered CSS gradients&lt;/li&gt;
&lt;li&gt;Borders and border radii&lt;/li&gt;
&lt;li&gt;Pseudo-elements&lt;/li&gt;
&lt;li&gt;Transforms and clipping&lt;/li&gt;
&lt;li&gt;CSS custom properties&lt;/li&gt;
&lt;li&gt;Responsive media queries&lt;/li&gt;
&lt;li&gt;Subtle keyframe animations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scene also includes CSS-only interactions. The chili-oil control changes the appearance of the broth and introduces small animated spice sparks. A compact day/night control changes the window, lantern, steam, and surrounding color palette without JavaScript.&lt;/p&gt;

&lt;p&gt;The ingredient labels can be revealed with a mouse or keyboard. I also included &lt;code&gt;prefers-reduced-motion&lt;/code&gt; support so visitors who disable animation receive a calmer version of the scene.&lt;/p&gt;

&lt;p&gt;The most challenging part was creating depth while keeping the markup understandable. The ramen bowl contains several overlapping layers, and each topping needs to remain recognizable without using an image. Careful positioning, contrast, shadows, and restrained animation helped the individual pieces feel like one cohesive illustration.&lt;/p&gt;

&lt;p&gt;I am particularly proud that the finished project requires only two files for the artwork: &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;style.css&lt;/code&gt;. It can run directly in a browser without a framework, build process, or external JavaScript.&lt;/p&gt;

&lt;p&gt;This project reminded me that CSS is not only a styling language—it can also be an expressive illustration medium.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source
&lt;/h2&gt;

&lt;p&gt;The complete source is available on &lt;a href="https://github.com/anilloutombam/midnight-ramen-css-art" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The project is shared under the MIT License.&lt;/p&gt;

&lt;p&gt;If you'd like to see more of what I'm building, you can explore my &lt;a href="https://dev.toPASTE_THE_ARTICLE_UTM_URL_HERE"&gt;portfolio&lt;/a&gt; or find me on &lt;a href="https://github.com/anilloutombam" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Fixing Exact Package Search Relevance in npmx</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:27:09 +0000</pubDate>
      <link>https://dev.to/anilloutombam/fixing-exact-package-search-relevance-in-npmx-34c7</link>
      <guid>https://dev.to/anilloutombam/fixing-exact-package-search-relevance-in-npmx-34c7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx&lt;/a&gt; is an open-source alternative interface for exploring packages from the npm registry. It provides package details, documentation, version history, dependencies, search tools, and other information for JavaScript developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;I worked on &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2978" rel="noopener noreferrer"&gt;issue #2978&lt;/a&gt;, which was reported by another contributor.&lt;/p&gt;

&lt;p&gt;When Algolia was selected as the search provider, the page displayed “Relevance” as the active sorting option. However, an exact package-name match did not always appear first.&lt;/p&gt;

&lt;p&gt;For example, searching for &lt;code&gt;napkin&lt;/code&gt; could show other packages above the exact match. Switching to “Downloads/wk” and then back to “Relevance” changed the results and moved &lt;code&gt;napkin&lt;/code&gt; to the top.&lt;/p&gt;

&lt;p&gt;I reproduced this behavior locally before investigating the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2978" rel="noopener noreferrer"&gt;Issue #2978&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pull request: &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3103" rel="noopener noreferrer"&gt;https://github.com/npmx-dev/npmx.dev/pull/3103&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://private-user-images.githubusercontent.com/23106365/629641010-7b90dc30-62b3-419a-9442-36a198edd8e3.mov?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODU0OTM3MzYsIm5iZiI6MTc4NTQ5MzQzNiwicGF0aCI6Ii8yMzEwNjM2NS82Mjk2NDEwMTAtN2I5MGRjMzAtNjJiMy00MTlhLTk0NDItMzZhMTk4ZWRkOGUzLm1vdj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MzElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzMxVDEwMjM1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWUxMzEwYjI3OGUxN2I0M2ExZGI1ZjM5MTAzMTk0OWRhYmI0NjIzMzc2M2Q4YjBkZmNhNGNkNTE1MWNjY2RmZGYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT12aWRlbyUyRnF1aWNrdGltZSJ9.jYPPoG_cEHKfZk_oXZ1Qy1_0D2WPHbcGASouXU0PEpE" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;private-user-images.githubusercontent.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  After
&lt;/h3&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://private-user-images.githubusercontent.com/23106365/629641622-e73ea88e-e74c-40c6-a1a6-462760619c4b.mov?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODU0OTM3NTksIm5iZiI6MTc4NTQ5MzQ1OSwicGF0aCI6Ii8yMzEwNjM2NS82Mjk2NDE2MjItZTczZWE4OGUtZTc0Yy00MGM2LWExYTYtNDYyNzYwNjE5YzRiLm1vdj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MzElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzMxVDEwMjQxOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTMzNzkxNmE1MTMyODRiNWIyNTg3MWIyN2MwNGI5MThlM2RlNmYzNWIzNjZhOGVhNTViZGE5OGM5NmQ0NGZlNGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT12aWRlbyUyRnF1aWNrdGltZSJ9.DDshiEDIyCohCupPhqxI7XlriGJAovK3XOnrHSflnRc" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;private-user-images.githubusercontent.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;npmx already made an additional Algolia request to check whether a package with the exact searched name existed.&lt;/p&gt;

&lt;p&gt;However, this request retrieved only the package name and used the result only as an existence check. If the exact package was missing from the initial batch of results, the application knew it existed but did not have enough information to display it.&lt;/p&gt;

&lt;p&gt;I updated the exact-package request to retrieve the complete package information. When the returned package name matches the searched name, it is placed at the beginning of the results.&lt;/p&gt;

&lt;p&gt;Before adding it, the code removes any existing occurrence of the same package. This prevents duplicate results.&lt;/p&gt;

&lt;p&gt;I also added two safety checks during review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A missing Algolia &lt;code&gt;hits&lt;/code&gt; array falls back to an empty array.&lt;/li&gt;
&lt;li&gt;A returned hit is promoted only when its name matches the requested package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I added a regression test that supplies a regular search result and a separate exact-package result. The test verifies that the exact package is detected and placed first.&lt;/p&gt;

&lt;p&gt;I validated the change by running the focused Nuxt tests, TypeScript checks, lint and formatting checks, and by testing the behavior manually in the local UI.&lt;/p&gt;

&lt;p&gt;After the fix, an exact package-name match appears at the top immediately when “Relevance” is selected.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Fixing a False “TypeScript Types Removed” Event in npmx</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Fri, 31 Jul 2026 08:43:14 +0000</pubDate>
      <link>https://dev.to/anilloutombam/fixing-a-false-typescript-types-removed-event-in-npmx-5bj6</link>
      <guid>https://dev.to/anilloutombam/fixing-a-false-typescript-types-removed-event-in-npmx-5bj6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev/" rel="noopener noreferrer"&gt;npmx&lt;/a&gt; is an open-source browser for the npm registry. It helps developers explore package metadata, versions, dependencies, documentation, and other information.&lt;/p&gt;

&lt;p&gt;I worked on &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2791" rel="noopener noreferrer"&gt;npmx issue #2791&lt;/a&gt;, which reported an incorrect event in the package-version timeline. npmx could say that a package had removed its TypeScript types even though valid declaration files were still included in the published package.&lt;/p&gt;

&lt;p&gt;My fix is available in &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3102" rel="noopener noreferrer"&gt;npmx PR #3102&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;A package can expose TypeScript declarations explicitly through fields such as &lt;code&gt;types&lt;/code&gt; or &lt;code&gt;typings&lt;/code&gt;. It can also provide declarations implicitly by placing them beside its JavaScript entry points.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/index.mjs
dist/index.d.mts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript can use &lt;code&gt;dist/index.d.mts&lt;/code&gt; even when &lt;code&gt;package.json&lt;/code&gt; does not contain a top-level &lt;code&gt;types&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;npmx already understood this pattern on its package-analysis page. However, the timeline endpoint used &lt;code&gt;hasBuiltInTypes()&lt;/code&gt;, which only checked package metadata.&lt;/p&gt;

&lt;p&gt;This caused the two parts of npmx to disagree:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The package page correctly showed “Types included.”&lt;/li&gt;
&lt;li&gt;The timeline incorrectly showed “TypeScript types removed.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The fix keeps the fast metadata check as the first step. It performs the more expensive published-file analysis only for visible versions that could be part of a type-removal sequence.&lt;/p&gt;

&lt;p&gt;The latest implementation selects every untyped version that has an older typed version somewhere earlier in the package’s history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;possibleTypeRemovals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;visibleVersions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;versionIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allVersions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allVersions&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;versionIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousVersion&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;previousVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking the full sequence is important. Consider these versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.0.0 — explicit types field
2.0.0 — colocated .d.mts declaration
3.0.0 — colocated .d.mts declaration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An earlier version of my fix analyzed only &lt;code&gt;2.0.0&lt;/code&gt;, because it was directly beside the metadata transition. The latest version also analyzes &lt;code&gt;3.0.0&lt;/code&gt; and any other affected version in the sequence.&lt;/p&gt;

&lt;p&gt;For each candidate, the timeline fetches the package metadata and published file tree. It then reuses the project’s existing &lt;code&gt;analyzePackage()&lt;/code&gt; utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;typesPackage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchPackageWithTypesAndFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;analyzePackage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;typesPackage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;included&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the additional lookup fails, the endpoint preserves the original metadata-only result instead of failing the complete timeline request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;I started by running the existing test suite and confirming that all 1,716 tests passed before making changes.&lt;/p&gt;

&lt;p&gt;I then added a regression test with three package versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1.0.0&lt;/code&gt; declares types explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2.0.0&lt;/code&gt; publishes &lt;code&gt;index.mjs&lt;/code&gt; and &lt;code&gt;index.d.mts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3.0.0&lt;/code&gt; also publishes &lt;code&gt;index.mjs&lt;/code&gt; and &lt;code&gt;index.d.mts&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test verifies that both implicit versions are analyzed and that all three versions report TypeScript types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchPackageWithTypesAndFilesMock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-pkg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchPackageWithTypesAndFilesMock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-pkg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;hasTypes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;hasTypes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;hasTypes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added a genuine-removal test. In that case, the newer version publishes &lt;code&gt;index.mjs&lt;/code&gt; without a matching declaration file, so &lt;code&gt;hasTypes&lt;/code&gt; correctly remains unset.&lt;/p&gt;

&lt;p&gt;The final validation included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript type checking&lt;/li&gt;
&lt;li&gt;Repository lint checks&lt;/li&gt;
&lt;li&gt;The focused timeline test suite&lt;/li&gt;
&lt;li&gt;All 82 unit-test files&lt;/li&gt;
&lt;li&gt;The complete unit-test suite with the new regression coverage&lt;/li&gt;
&lt;li&gt;GitHub continuous-integration checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;The final change improves the timeline in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It recognizes colocated &lt;code&gt;.d.mts&lt;/code&gt; declarations.&lt;/li&gt;
&lt;li&gt;It reuses the existing package-analysis implementation.&lt;/li&gt;
&lt;li&gt;It handles consecutive implicit-type versions after a metadata transition.&lt;/li&gt;
&lt;li&gt;It preserves genuine “types removed” events.&lt;/li&gt;
&lt;li&gt;It avoids failing the entire timeline when an additional file lookup is unavailable.&lt;/li&gt;
&lt;li&gt;Its tests verify array length before using optional indexed access.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The main lesson was that package metadata does not always describe everything inside a published npm package. Removing a &lt;code&gt;types&lt;/code&gt; field does not necessarily mean that TypeScript support was removed.&lt;/p&gt;

&lt;p&gt;I also learned why regression tests should cover a sequence rather than only one transition. Testing just &lt;code&gt;1.0.0 → 2.0.0&lt;/code&gt; missed the behavior of &lt;code&gt;3.0.0&lt;/code&gt;. Extending the test revealed that every affected version after the transition needed file-aware analysis.&lt;/p&gt;

&lt;p&gt;Review feedback also improved the test quality. I replaced non-null assertions on indexed versions with an explicit length assertion and optional access.&lt;/p&gt;

&lt;p&gt;Most importantly, I reused npmx’s existing analysis logic instead of implementing another declaration-detection algorithm. This keeps the package page and timeline consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and After
&lt;/h2&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.0.0 — TypeScript types included
2.0.0 — TypeScript types removed
3.0.0 — TypeScript types not detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could appear even when &lt;code&gt;2.0.0&lt;/code&gt; and &lt;code&gt;3.0.0&lt;/code&gt; contained valid &lt;code&gt;.d.mts&lt;/code&gt; declarations.&lt;/p&gt;

&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.0.0 — TypeScript types included
2.0.0 — TypeScript types included
3.0.0 — TypeScript types included
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A release that genuinely removes both the metadata and the declaration files is still treated as a type removal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Request
&lt;/h2&gt;

&lt;p&gt;The implementation, tests, and review history are available in &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3102" rel="noopener noreferrer"&gt;npmx PR #3102&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Miso &amp; Memory: A CSS-Illustrated Ramen Landing Page for Rainy Nights</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Fri, 31 Jul 2026 05:08:24 +0000</pubDate>
      <link>https://dev.to/anilloutombam/miso-memory-a-css-illustrated-ramen-landing-page-for-rainy-nights-4i1c</link>
      <guid>https://dev.to/anilloutombam/miso-memory-a-css-illustrated-ramen-landing-page-for-rainy-nights-4i1c</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge — Comfort Food Edition, Perfect Landing&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;Miso &amp;amp; Memory&lt;/strong&gt;, a landing page for an imaginary late-night ramen counter.&lt;/p&gt;

&lt;p&gt;The concept is inspired by the feeling of coming home to a hot bowl after a long, rainy day: slow broth, springy noodles, warm light, and the good kind of quiet.&lt;/p&gt;

&lt;p&gt;The visual direction combines editorial typography, an earthy restaurant-inspired palette, Japanese print-inspired details, and a steaming ramen bowl illustrated with custom CSS.&lt;/p&gt;

&lt;p&gt;The landing page includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A responsive CSS-art ramen hero&lt;/li&gt;
&lt;li&gt;Animated steam and a scrolling kitchen-principles banner&lt;/li&gt;
&lt;li&gt;A curated menu of comfort bowls&lt;/li&gt;
&lt;li&gt;The story behind the imaginary restaurant&lt;/li&gt;
&lt;li&gt;An interactive “build your comfort bowl” experience&lt;/li&gt;
&lt;li&gt;Dynamic topping selection and price calculation&lt;/li&gt;
&lt;li&gt;Keyboard-friendly controls and accessible labels&lt;/li&gt;
&lt;li&gt;Visible selected and confirmation states&lt;/li&gt;
&lt;li&gt;Reduced-motion support&lt;/li&gt;
&lt;li&gt;Responsive mobile, tablet, and desktop layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://miso-and-memory.nlkr50.workers.dev" rel="noopener noreferrer"&gt;Open Miso &amp;amp; Memory&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/anilloutombam/miso-and-memory" rel="noopener noreferrer"&gt;View the source code on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;I wanted this landing page to feel handcrafted rather than relying on conventional restaurant photography.&lt;/p&gt;

&lt;p&gt;The hero illustration is constructed from CSS shapes, borders, clipping paths, shadows, and animation. The bowl contains noodles, a jammy egg, mushrooms, greens, narutomaki, chopsticks, and rising steam.&lt;/p&gt;

&lt;p&gt;One of my favorite parts is how the illustration and interface share the same visual language. The rust-red sun becomes the primary action color, broth-inspired gold highlights selected toppings, and forest green carries through the typography and bowl outlines.&lt;/p&gt;

&lt;p&gt;I also wanted the page to be functional rather than purely decorative. Visitors can choose from three bowls, add or remove toppings, see the total update immediately, and save their selection.&lt;/p&gt;

&lt;p&gt;The interactive controls use semantic buttons, &lt;code&gt;aria-pressed&lt;/code&gt; states, and a status message when the bowl is saved. The page also respects &lt;code&gt;prefers-reduced-motion&lt;/code&gt; for visitors who prefer a calmer experience.&lt;/p&gt;

&lt;p&gt;The project is built with React, TypeScript, Vinext, Vite, and custom CSS. It is deployed publicly on Cloudflare Workers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Learned
&lt;/h3&gt;

&lt;p&gt;This project reminded me how much atmosphere can be created with a restrained color palette, expressive typography, and carefully composed CSS.&lt;/p&gt;

&lt;p&gt;I also learned more about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constructing detailed illustrations from CSS primitives&lt;/li&gt;
&lt;li&gt;Balancing animation with accessibility&lt;/li&gt;
&lt;li&gt;Designing responsive editorial layouts&lt;/li&gt;
&lt;li&gt;Building keyboard-friendly selection controls&lt;/li&gt;
&lt;li&gt;Deploying a Vinext application and its static assets to Cloudflare Workers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI Assistance
&lt;/h3&gt;

&lt;p&gt;I used Codex as a collaborative development tool for design exploration, implementation, accessibility improvements, troubleshooting, and deployment guidance.&lt;/p&gt;

&lt;p&gt;I reviewed and refined the project’s creative direction, interface, content, and interactions before submitting it. The social-preview image was created using an AI image-generation tool. The ramen illustration visible in the website’s hero section is implemented with CSS.&lt;/p&gt;

&lt;h3&gt;
  
  
  License
&lt;/h3&gt;

&lt;p&gt;The source code is available under the MIT License.&lt;/p&gt;

&lt;p&gt;Thanks for stopping by. Your seat at the counter is waiting. 🍜&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Beyond Test Automation</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Mon, 27 Jul 2026 15:32:54 +0000</pubDate>
      <link>https://dev.to/anilloutombam/beyond-test-automation-1k6l</link>
      <guid>https://dev.to/anilloutombam/beyond-test-automation-1k6l</guid>
      <description>&lt;h2&gt;
  
  
  How I Built a Scalable Release Confidence Platform with Playwright, CI/CD, and AI
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;From a manual regression process to an engineering platform that improved release confidence through deterministic automation, reusable architecture, and AI-assisted coverage analysis.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Software engineering becomes harder as software becomes more successful.&lt;/p&gt;

&lt;p&gt;Early on, shipping a release is straightforward—few features, a small user base, and limited integration points. Manual verification is practical.&lt;/p&gt;

&lt;p&gt;That doesn't last.&lt;/p&gt;

&lt;p&gt;New modules ship, workflows interconnect, and changes that appear isolated begin breaking completely unrelated parts of the application.&lt;/p&gt;

&lt;p&gt;The problem stops being:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Write the feature."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can we deploy today without breaking an existing workflow?&lt;/li&gt;
&lt;li&gt;Can we merge changes knowing critical user journeys still work?&lt;/li&gt;
&lt;li&gt;Can we tell what isn't being tested before it reaches production?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our case, the product had grown into a large enterprise application with many interconnected workflows, shipping once or twice every month. Every release depended on completing a full manual regression cycle before deployment.&lt;/p&gt;

&lt;p&gt;Regression scenarios lived in spreadsheets and were executed manually across business-critical workflows. Depending on the scope of the release, this consumed nearly an entire working day, followed by additional verification before anyone felt comfortable approving production deployment.&lt;/p&gt;

&lt;p&gt;Some releases were delayed for one reason:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Regression wasn't finished yet.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The software was ready.&lt;/p&gt;

&lt;p&gt;The pipeline was ready.&lt;/p&gt;

&lt;p&gt;Confidence wasn't.&lt;/p&gt;

&lt;p&gt;The original objective sounded simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Automate manual regression using Playwright.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It didn't stay simple.&lt;/p&gt;

&lt;p&gt;Parallel execution exposed shared-state bugs.&lt;/p&gt;

&lt;p&gt;Authentication became significantly more complex.&lt;/p&gt;

&lt;p&gt;Flaky tests quickly eroded trust.&lt;/p&gt;

&lt;p&gt;Maintaining hundreds of scenarios required the same engineering discipline as maintaining any production software system.&lt;/p&gt;

&lt;p&gt;Eventually one realization changed the direction of the project.&lt;/p&gt;

&lt;p&gt;Passing tests only answers one question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Did the scenarios we already know about still work?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; answer another equally important question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What important user journeys are we not testing at all?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That realization transformed the effort.&lt;/p&gt;

&lt;p&gt;Instead of building a collection of Playwright scripts, we built an internal engineering platform that combined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic end-to-end automation&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;li&gt;Reusable automation architecture&lt;/li&gt;
&lt;li&gt;AI-assisted coverage analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI-assisted coverage analysis agent examines existing Playwright automation, identifies missing business workflows, highlights coverage gaps, and recommends the next scenarios worth automating before those blind spots become production incidents.&lt;/p&gt;

&lt;p&gt;This isn't a Playwright tutorial.&lt;/p&gt;

&lt;p&gt;It's the engineering decisions, failures, trade-offs, and architectural lessons behind transforming manual regression from a release bottleneck into a system engineers genuinely trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Cost of Manual Regression
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F94a58kowf07r0dphjy93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F94a58kowf07r0dphjy93.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We shipped one to two releases every month.&lt;/p&gt;

&lt;p&gt;Development generally finished on time.&lt;/p&gt;

&lt;p&gt;Release preparation became the bottleneck.&lt;/p&gt;

&lt;p&gt;What initially looked like a manageable checklist gradually evolved into an entire day of validation covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workflow management&lt;/li&gt;
&lt;li&gt;Document processing&lt;/li&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Approval flows&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Administrative tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...all executed manually before every release, even when only a small feature had changed.&lt;/p&gt;

&lt;p&gt;This wasn't a discipline problem.&lt;/p&gt;

&lt;p&gt;Manual verification simply doesn't scale.&lt;/p&gt;

&lt;p&gt;Every release raised the same questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have we tested every critical workflow?&lt;/li&gt;
&lt;li&gt;Did this change silently break another module?&lt;/li&gt;
&lt;li&gt;Are we repeatedly testing familiar paths while missing new functionality?&lt;/li&gt;
&lt;li&gt;Can we complete regression quickly enough without cutting corners?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Too often, the answer to the last question was &lt;strong&gt;no&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Releases slipped.&lt;/p&gt;

&lt;p&gt;That experience fundamentally changed how I viewed automation.&lt;/p&gt;

&lt;p&gt;Automation isn't about reducing manual effort.&lt;/p&gt;

&lt;p&gt;It's about removing release uncertainty.&lt;/p&gt;

&lt;p&gt;The real question an engineering platform should answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can we confidently release this change today?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That became the design principle for everything that followed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Owning the Platform
&lt;/h2&gt;

&lt;p&gt;There was no dedicated QA or test automation engineer on the team.&lt;/p&gt;

&lt;p&gt;Alongside designing and delivering business-critical features, I took ownership of improving release quality by building the automation platform that supported them.&lt;/p&gt;

&lt;p&gt;That distinction mattered.&lt;/p&gt;

&lt;p&gt;The framework wasn't developed from the perspective of someone testing the application from the outside.&lt;/p&gt;

&lt;p&gt;It was designed with an understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business workflows&lt;/li&gt;
&lt;li&gt;System architecture&lt;/li&gt;
&lt;li&gt;Operational constraints&lt;/li&gt;
&lt;li&gt;The kinds of failures that mattered most to end users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building the framework, integrating it into the CI/CD pipeline, improving execution reliability, and evolving it alongside the application happened while continuing normal feature development.&lt;/p&gt;

&lt;p&gt;That experience completely changed how I viewed test automation.&lt;/p&gt;

&lt;p&gt;It wasn't a collection of Playwright scripts anymore.&lt;/p&gt;

&lt;p&gt;It became an engineering platform whose purpose was to improve release confidence through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliable execution&lt;/li&gt;
&lt;li&gt;Reusable architecture&lt;/li&gt;
&lt;li&gt;Deterministic behavior&lt;/li&gt;
&lt;li&gt;Fast developer feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every architectural decision that followed was driven by that objective.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building the First Automation Framework
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0foo48naju5q2xwn3qkv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0foo48naju5q2xwn3qkv.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why &lt;strong&gt;Playwright&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Its native TypeScript support, automatic waiting, built-in assertions, reliable parallel execution, and excellent debugging capabilities made it a strong foundation for building a maintainable end-to-end testing platform.&lt;/p&gt;

&lt;p&gt;One architectural constraint influenced almost every design decision.&lt;/p&gt;

&lt;p&gt;The application exposed &lt;strong&gt;no dedicated testing APIs&lt;/strong&gt; for preparing test data or bootstrapping scenarios.&lt;/p&gt;

&lt;p&gt;Automation therefore had to interact with the system exactly like a real user.&lt;/p&gt;

&lt;p&gt;That meant authentication, fixtures, state isolation, and test data management weren't conveniences.&lt;/p&gt;

&lt;p&gt;They became core responsibilities of the framework itself.&lt;/p&gt;

&lt;p&gt;Designing around those constraints ensured that automated tests validated real user journeys instead of privileged internal APIs, making the resulting feedback significantly more trustworthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designing for Scale
&lt;/h2&gt;

&lt;p&gt;Writing Playwright tests is easy.&lt;/p&gt;

&lt;p&gt;Maintaining hundreds isn't.&lt;/p&gt;

&lt;p&gt;From the beginning, the framework separated responsibilities into clear layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Test suites&lt;/li&gt;
&lt;li&gt;Page Objects&lt;/li&gt;
&lt;li&gt;Utilities&lt;/li&gt;
&lt;li&gt;Fixtures&lt;/li&gt;
&lt;li&gt;Test data&lt;/li&gt;
&lt;li&gt;Reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation meant a UI change usually affected one file instead of dozens.&lt;/p&gt;

&lt;p&gt;As the suite expanded, maintenance remained predictable instead of becoming exponentially harder.&lt;/p&gt;




&lt;h2&gt;
  
  
  Business Workflows Over Pages
&lt;/h2&gt;

&lt;p&gt;Rather than organizing tests around screens or UI components, suites were organized around complete business capabilities.&lt;/p&gt;

&lt;p&gt;That approach provided several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better ownership&lt;/li&gt;
&lt;li&gt;Targeted regression execution&lt;/li&gt;
&lt;li&gt;Easier failure isolation&lt;/li&gt;
&lt;li&gt;Simpler onboarding for new engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each suite validated an end-to-end business workflow rather than isolated button clicks or page interactions.&lt;/p&gt;

&lt;p&gt;The framework reflected how users actually interacted with the product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reusable Page Objects
&lt;/h2&gt;

&lt;p&gt;Tests interacted with business-level abstractions instead of raw selectors.&lt;/p&gt;

&lt;p&gt;Instead of repeatedly locating buttons and fields throughout the suite, implementation details remained encapsulated inside reusable Page Objects.&lt;/p&gt;

&lt;p&gt;That separation kept test cases focused on business intent.&lt;/p&gt;

&lt;p&gt;When the UI changed, updates were usually confined to a single location instead of requiring widespread modifications across hundreds of tests.&lt;/p&gt;

&lt;p&gt;The result was cleaner code, improved readability, and dramatically lower maintenance costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shared Fixtures
&lt;/h2&gt;

&lt;p&gt;Common setup was centralized across the framework, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticated browser contexts&lt;/li&gt;
&lt;li&gt;Test data preparation&lt;/li&gt;
&lt;li&gt;Browser configuration&lt;/li&gt;
&lt;li&gt;Shared fixtures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond reducing boilerplate, this guaranteed that every test started from a predictable environment.&lt;/p&gt;

&lt;p&gt;As the automation suite grew, that consistency became one of the biggest contributors to execution reliability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Authentication as Framework Architecture
&lt;/h2&gt;

&lt;p&gt;Authentication wasn't treated as something every individual test should perform.&lt;/p&gt;

&lt;p&gt;Instead, it became part of the framework itself.&lt;/p&gt;

&lt;p&gt;Authenticated contexts were reused where appropriate, reducing repetitive setup while improving execution speed.&lt;/p&gt;

&lt;p&gt;More importantly, separating authentication from business validation reduced instability once tests began executing concurrently.&lt;/p&gt;

&lt;p&gt;Authentication became infrastructure rather than test logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reporting Built for Investigation
&lt;/h2&gt;

&lt;p&gt;Knowing that a test failed isn't enough.&lt;/p&gt;

&lt;p&gt;Engineers need to know &lt;strong&gt;why&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The framework generated rich debugging artifacts for every execution, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML reports&lt;/li&gt;
&lt;li&gt;Playwright traces&lt;/li&gt;
&lt;li&gt;Execution logs&lt;/li&gt;
&lt;li&gt;Failure screenshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of reproducing failures locally, engineers could often identify root causes directly from the CI pipeline.&lt;/p&gt;

&lt;p&gt;Investigation became significantly faster, allowing teams to spend more time fixing problems than reproducing them.&lt;/p&gt;




&lt;p&gt;The initial results were encouraging.&lt;/p&gt;

&lt;p&gt;Manual regression effort dropped significantly.&lt;/p&gt;

&lt;p&gt;Test execution became repeatable.&lt;/p&gt;

&lt;p&gt;Releases gained a much more reliable validation process.&lt;/p&gt;

&lt;p&gt;But as the automation suite continued growing—and parallel execution increased—a completely different class of problems appeared.&lt;/p&gt;

&lt;p&gt;Interestingly, those problems weren't in the application.&lt;/p&gt;

&lt;p&gt;They were inside the automation framework itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Scaling Broke Everything
&lt;/h2&gt;

&lt;p&gt;Parallel execution changed everything.&lt;/p&gt;

&lt;p&gt;Running tests concurrently reduced execution time dramatically—but immediately exposed assumptions nobody had questioned.&lt;/p&gt;

&lt;p&gt;Tests that had always passed in sequential execution suddenly began failing intermittently.&lt;/p&gt;

&lt;p&gt;Same suite.&lt;/p&gt;

&lt;p&gt;Same code.&lt;/p&gt;

&lt;p&gt;Different outcome.&lt;/p&gt;

&lt;p&gt;That inconsistency was the first indication that the automation framework—not the application—needed architectural improvements.&lt;/p&gt;




&lt;h3&gt;
  
  
  Flaky Tests Kill Trust Fast
&lt;/h3&gt;

&lt;p&gt;Once a test can pass, fail, and then pass again without any application changes, every failure creates the same question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Is the application broken, or is the test broken?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If engineers can't answer that confidently, automation quickly loses credibility.&lt;/p&gt;

&lt;p&gt;Reliability isn't simply a nice-to-have.&lt;/p&gt;

&lt;p&gt;It's the foundation that determines whether developers trust automated regression enough to make release decisions based on it.&lt;/p&gt;




&lt;h3&gt;
  
  
  Root Cause: Shared State
&lt;/h3&gt;

&lt;p&gt;The biggest issue turned out to be shared state.&lt;/p&gt;

&lt;p&gt;Parallel workers were unintentionally interacting with common resources.&lt;/p&gt;

&lt;p&gt;Tests assumed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data created by previous executions still existed.&lt;/li&gt;
&lt;li&gt;Application state would remain unchanged throughout the suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both assumptions broke under concurrent execution.&lt;/p&gt;

&lt;p&gt;Instead of adding retries, the framework introduced stronger isolation between test runs so each scenario could execute independently.&lt;/p&gt;

&lt;p&gt;Isolation—not retries—became the long-term solution.&lt;/p&gt;




&lt;h3&gt;
  
  
  Authentication Under Load
&lt;/h3&gt;

&lt;p&gt;Authentication became another scaling challenge.&lt;/p&gt;

&lt;p&gt;Initially, authenticated sessions were reused across multiple workers.&lt;/p&gt;

&lt;p&gt;That worked until concurrency increased.&lt;/p&gt;

&lt;p&gt;Multiple workers sharing authentication contexts introduced unpredictable session behavior that appeared completely random during execution.&lt;/p&gt;

&lt;p&gt;The solution wasn't adding more login steps.&lt;/p&gt;

&lt;p&gt;It was treating authentication as framework architecture rather than business logic.&lt;/p&gt;

&lt;p&gt;Authentication became infrastructure.&lt;/p&gt;

&lt;p&gt;Business validation remained inside the tests.&lt;/p&gt;

&lt;p&gt;That separation improved both execution speed and reliability.&lt;/p&gt;




&lt;h3&gt;
  
  
  Timeouts Were Symptoms, Not Problems
&lt;/h3&gt;

&lt;p&gt;Increasing timeout values is one of the easiest ways to make unstable tests appear reliable.&lt;/p&gt;

&lt;p&gt;It's also one of the easiest ways to hide real problems.&lt;/p&gt;

&lt;p&gt;Whenever a timeout occurred, the first question was never:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Should we increase the timeout?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, the investigation focused on the underlying cause.&lt;/p&gt;

&lt;p&gt;Was the application genuinely slow?&lt;/p&gt;

&lt;p&gt;Was a selector unstable?&lt;/p&gt;

&lt;p&gt;Was another worker interfering?&lt;/p&gt;

&lt;p&gt;Was the test depending on stale data?&lt;/p&gt;

&lt;p&gt;Fixing those issues produced a far more reliable framework than simply increasing timeout values.&lt;/p&gt;




&lt;h3&gt;
  
  
  Scaling Without Guesswork
&lt;/h3&gt;

&lt;p&gt;As the suite continued growing, another pattern emerged.&lt;/p&gt;

&lt;p&gt;Increasing the number of parallel workers increased contention on shared backend resources—particularly database connection pools.&lt;/p&gt;

&lt;p&gt;Interestingly, latency didn't grow linearly.&lt;/p&gt;

&lt;p&gt;Doubling worker count didn't double execution time.&lt;/p&gt;

&lt;p&gt;Instead of relying on arbitrary timeout values, timeout budgets were tuned using observed execution characteristics.&lt;/p&gt;

&lt;p&gt;The objective wasn't making tests pass.&lt;/p&gt;

&lt;p&gt;It was ensuring failures remained meaningful.&lt;/p&gt;

&lt;p&gt;That distinction changed how the framework evolved.&lt;/p&gt;

&lt;p&gt;Engineering effort shifted from masking failures to eliminating the reasons they occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Retries Weren't the Answer
&lt;/h2&gt;

&lt;p&gt;Many automation frameworks rely on retries.&lt;/p&gt;

&lt;p&gt;If a test fails, run it again.&lt;/p&gt;

&lt;p&gt;That approach can improve pass rates, but it often hides the real problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Race conditions&lt;/li&gt;
&lt;li&gt;Shared state&lt;/li&gt;
&lt;li&gt;Timing assumptions&lt;/li&gt;
&lt;li&gt;Unstable selectors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A green pipeline doesn't necessarily mean a reliable test suite.&lt;/p&gt;

&lt;p&gt;It may simply mean the framework retried enough times to hide the instability.&lt;/p&gt;

&lt;p&gt;Instead of increasing retries, the objective was to eliminate the reasons retries were needed in the first place.&lt;/p&gt;

&lt;p&gt;That required architectural changes—not additional assertions.&lt;/p&gt;

&lt;p&gt;The framework was redesigned for deterministic parallel execution, introducing stronger isolation between concurrent test runs and a clearer separation between shared setup and business validation.&lt;/p&gt;

&lt;p&gt;Authentication, fixtures, and test state were managed so that one test could no longer influence another, even under heavy concurrency.&lt;/p&gt;

&lt;p&gt;As execution became deterministic, retries gradually stopped providing meaningful value.&lt;/p&gt;

&lt;p&gt;Instead of masking intermittent failures, the framework consistently surfaced genuine issues, allowing engineers to fix root causes rather than chase flaky tests.&lt;/p&gt;

&lt;p&gt;The broader lesson was clear.&lt;/p&gt;

&lt;p&gt;Large-scale automation problems are usually software engineering problems.&lt;/p&gt;

&lt;p&gt;Solving them required architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear isolation boundaries&lt;/li&gt;
&lt;li&gt;Reusable abstractions&lt;/li&gt;
&lt;li&gt;Reliable fixture management&lt;/li&gt;
&lt;li&gt;Predictable execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;—not simply writing more assertions.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Stability to Coverage: AI-Assisted Coverage Analysis
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5oceo3qwg1u8ekvreig2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5oceo3qwg1u8ekvreig2.png" alt=" " width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even after building a fast and reliable automation platform, one question remained unanswered.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do we know which important workflows aren't being tested at all?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Execution quality had improved.&lt;/p&gt;

&lt;p&gt;Coverage quality was still largely a guess.&lt;/p&gt;

&lt;p&gt;That realization led to building an AI-assisted coverage analysis agent.&lt;/p&gt;

&lt;p&gt;Instead of simply answering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Did the tests pass?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the platform could now answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What are we still not testing?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The objective wasn't generating tests automatically.&lt;/p&gt;

&lt;p&gt;The objective was helping engineers make better decisions about where automation effort should go next.&lt;/p&gt;




&lt;h3&gt;
  
  
  Design Principles
&lt;/h3&gt;

&lt;h4&gt;
  
  
  AI Assists. Engineers Decide.
&lt;/h4&gt;

&lt;p&gt;The agent doesn't generate automation autonomously.&lt;/p&gt;

&lt;p&gt;Instead, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identifies blind spots&lt;/li&gt;
&lt;li&gt;Detects missing business workflows&lt;/li&gt;
&lt;li&gt;Suggests high-value scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Engineers remain responsible for reviewing and implementing those recommendations.&lt;/p&gt;

&lt;p&gt;Decision-making stays transparent and accountable.&lt;/p&gt;




&lt;h4&gt;
  
  
  Business Workflows Over UI Elements
&lt;/h4&gt;

&lt;p&gt;Counting pages, buttons, or components says very little about release confidence.&lt;/p&gt;

&lt;p&gt;Instead, the analysis focuses on complete business journeys.&lt;/p&gt;

&lt;p&gt;The objective is measuring workflow coverage—not interface coverage.&lt;/p&gt;

&lt;p&gt;That ensures the scenarios users actually depend on are represented inside the automated suite.&lt;/p&gt;




&lt;h4&gt;
  
  
  Explainable, Not Opaque
&lt;/h4&gt;

&lt;p&gt;Every coverage classification—&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Covered&lt;/li&gt;
&lt;li&gt;Partially Covered&lt;/li&gt;
&lt;li&gt;Not Covered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can be traced back to observable application behavior and existing Playwright automation.&lt;/p&gt;

&lt;p&gt;Nothing is hidden behind a black-box score.&lt;/p&gt;

&lt;p&gt;Engineers can validate every recommendation before acting on it.&lt;/p&gt;




&lt;h4&gt;
  
  
  Actionable Over Informational
&lt;/h4&gt;

&lt;p&gt;Reports don't simply describe the current state.&lt;/p&gt;

&lt;p&gt;They prioritize uncovered workflows by estimated business impact and recommend the next scenarios worth automating.&lt;/p&gt;

&lt;p&gt;The goal is reducing engineering decision fatigue—not generating another dashboard.&lt;/p&gt;




&lt;h4&gt;
  
  
  Deterministic Output
&lt;/h4&gt;

&lt;p&gt;Running the analysis twice against the same application and the same automation suite should produce identical conclusions.&lt;/p&gt;

&lt;p&gt;Recommendations should change only because the application changes—not because the analysis is inconsistent.&lt;/p&gt;




&lt;h4&gt;
  
  
  Continuous, Not One-Time
&lt;/h4&gt;

&lt;p&gt;Coverage isn't something you "finish."&lt;/p&gt;

&lt;p&gt;Every feature changes the application's risk profile.&lt;/p&gt;

&lt;p&gt;The question evolves from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Have we completed automation?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What should we automate next to reduce release risk?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Current Limitations
&lt;/h3&gt;

&lt;p&gt;The coverage analysis agent deliberately has a narrow responsibility.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute Playwright tests&lt;/li&gt;
&lt;li&gt;Determine whether application behavior is correct&lt;/li&gt;
&lt;li&gt;Replace engineering judgment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, it analyzes the relationship between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expected business workflows&lt;/li&gt;
&lt;li&gt;Existing Playwright automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its effectiveness depends on well-structured Playwright projects, meaningful test names, reusable page objects, and clearly defined business workflows.&lt;/p&gt;

&lt;p&gt;It's a decision-support system—not a quality certification.&lt;/p&gt;

&lt;p&gt;For business-critical functionality, human review remains essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Quality in CI/CD
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flrxkmttdi878gvqdfh2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flrxkmttdi878gvqdfh2t.png" alt=" " width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The framework delivered its greatest value once it stopped depending on someone remembering to run it manually.&lt;/p&gt;

&lt;p&gt;Instead, it became part of the delivery pipeline itself.&lt;/p&gt;

&lt;p&gt;Integrated into GitLab CI/CD, every meaningful change executed the same deterministic regression suite before moving forward.&lt;/p&gt;

&lt;p&gt;The biggest mindset shift wasn't technical.&lt;/p&gt;

&lt;p&gt;It was moving from asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Has regression been run?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Did regression pass?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That seemingly small change fundamentally altered how release decisions were made.&lt;/p&gt;

&lt;p&gt;The pipeline provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardized execution&lt;/strong&gt; — Every run used the same browser, configuration, assertions, and reporting, eliminating "works on my machine" inconsistencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel execution&lt;/strong&gt; — Runtime remained practical even as the suite grew, without sacrificing isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail-fast behavior&lt;/strong&gt; — Critical failures stopped the pipeline early instead of wasting time executing an already-failed build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich debugging artifacts&lt;/strong&gt; — HTML reports, Playwright traces, logs, and screenshots made most failures understandable directly from CI without reproducing them locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regression stopped being a checklist.&lt;/p&gt;

&lt;p&gt;It became a deployment gate alongside build validation and code quality.&lt;/p&gt;

&lt;p&gt;Passing the pipeline no longer meant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"The project compiles."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It meant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Critical business workflows continue to work."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Counterintuitively, adding more automation actually accelerated delivery.&lt;/p&gt;

&lt;p&gt;Less manual verification.&lt;/p&gt;

&lt;p&gt;More predictable releases.&lt;/p&gt;

&lt;p&gt;Release decisions backed by evidence instead of intuition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measuring the Impact
&lt;/h2&gt;

&lt;p&gt;Several hundred end-to-end scenarios that previously required nearly an entire working day of manual validation now execute automatically within minutes as part of every meaningful CI run.&lt;/p&gt;

&lt;p&gt;The impact wasn't simply faster execution.&lt;/p&gt;

&lt;p&gt;It fundamentally changed how the team approached release quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repeatable Outcomes
&lt;/h3&gt;

&lt;p&gt;Regression results no longer depended on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who executed the tests&lt;/li&gt;
&lt;li&gt;How much time remained before release&lt;/li&gt;
&lt;li&gt;Individual interpretation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every execution followed the same deterministic process.&lt;/p&gt;




&lt;h3&gt;
  
  
  Faster Investigation
&lt;/h3&gt;

&lt;p&gt;Rich debugging artifacts—including Playwright traces, execution reports, logs, and screenshots—made it possible to understand most failures directly from the CI pipeline.&lt;/p&gt;

&lt;p&gt;Instead of spending time reproducing issues locally, engineers could begin fixing problems immediately.&lt;/p&gt;




&lt;h3&gt;
  
  
  Measurable Confidence
&lt;/h3&gt;

&lt;p&gt;Release readiness was supported by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validated business workflows&lt;/li&gt;
&lt;li&gt;Reproducible failures&lt;/li&gt;
&lt;li&gt;Reviewable reports&lt;/li&gt;
&lt;li&gt;Coverage analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confidence became measurable instead of subjective.&lt;/p&gt;




&lt;h3&gt;
  
  
  From Reactive to Proactive Quality
&lt;/h3&gt;

&lt;p&gt;Instead of repeatedly asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Why did this break?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the conversation increasingly became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What important workflows are still missing from automation?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift transformed automation from a validation activity into a continuous engineering practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building this platform changed how I think about software engineering—not just test automation.&lt;/p&gt;

&lt;p&gt;Several lessons continue to influence how I design systems today.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Automation Is Software
&lt;/h3&gt;

&lt;p&gt;Automation deserves the same engineering discipline as production code.&lt;/p&gt;

&lt;p&gt;It needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modular architecture&lt;/li&gt;
&lt;li&gt;Code reviews&lt;/li&gt;
&lt;li&gt;Reusable abstractions&lt;/li&gt;
&lt;li&gt;Continuous refactoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;—not simply more scripts.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Reliability Beats Quantity
&lt;/h3&gt;

&lt;p&gt;A small automation suite that engineers trust creates more value than hundreds of unreliable tests.&lt;/p&gt;

&lt;p&gt;Determinism matters more than raw test count.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Fast Feedback Changes Engineering Behavior
&lt;/h3&gt;

&lt;p&gt;Slow regression gradually gets ignored.&lt;/p&gt;

&lt;p&gt;Fast, deterministic regression integrated into CI becomes part of every engineering decision.&lt;/p&gt;

&lt;p&gt;The faster feedback arrives, the earlier problems are discovered.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Coverage Matters More Than Test Count
&lt;/h3&gt;

&lt;p&gt;Hundreds of green tests don't necessarily mean important workflows are protected.&lt;/p&gt;

&lt;p&gt;Understanding what &lt;strong&gt;isn't&lt;/strong&gt; covered is often more valuable than knowing what already is.&lt;/p&gt;

&lt;p&gt;That's exactly the problem the AI-assisted coverage analysis agent was designed to solve.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. AI Should Augment Engineering Judgment
&lt;/h3&gt;

&lt;p&gt;The goal wasn't replacing engineers.&lt;/p&gt;

&lt;p&gt;The goal was accelerating analysis, surfacing blind spots, and helping teams prioritize automation work more effectively.&lt;/p&gt;

&lt;p&gt;Engineering decisions remained human.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Quality Is Continuous
&lt;/h3&gt;

&lt;p&gt;Quality isn't something added before a release.&lt;/p&gt;

&lt;p&gt;Every feature changes the application's risk surface.&lt;/p&gt;

&lt;p&gt;Automation must evolve continuously alongside the software itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Confidence Is the Real Deliverable
&lt;/h3&gt;

&lt;p&gt;The most valuable outcome wasn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Playwright&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;li&gt;Parallel execution&lt;/li&gt;
&lt;li&gt;Reporting&lt;/li&gt;
&lt;li&gt;AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are implementation details.&lt;/p&gt;

&lt;p&gt;The real deliverable was enabling engineers to make release decisions based on evidence instead of intuition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The original goal was straightforward.&lt;/p&gt;

&lt;p&gt;Automate manual regression.&lt;/p&gt;

&lt;p&gt;What ultimately emerged was something much broader.&lt;/p&gt;

&lt;p&gt;It demonstrated that quality engineering is fundamentally a software engineering discipline—not a separate track.&lt;/p&gt;

&lt;p&gt;This wasn't work performed instead of building the product.&lt;/p&gt;

&lt;p&gt;It came directly from building it.&lt;/p&gt;

&lt;p&gt;Understanding the application's architecture, business workflows, and operational constraints made it possible to design an automation platform that remained reliable, maintainable, and scalable as the product evolved.&lt;/p&gt;

&lt;p&gt;The platform combined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic automation&lt;/li&gt;
&lt;li&gt;Parallel execution&lt;/li&gt;
&lt;li&gt;Reusable architecture&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;li&gt;Rich debugging artifacts&lt;/li&gt;
&lt;li&gt;AI-assisted coverage analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the most important outcome wasn't the technology.&lt;/p&gt;

&lt;p&gt;It was changing how release decisions were made.&lt;/p&gt;

&lt;p&gt;Replacing intuition with evidence.&lt;/p&gt;

&lt;p&gt;Automation tells you what you already know still works.&lt;/p&gt;

&lt;p&gt;Coverage analysis tells you what you've overlooked.&lt;/p&gt;

&lt;p&gt;Both are necessary.&lt;/p&gt;

&lt;p&gt;Together, they provide a stronger foundation for release confidence than either could alone.&lt;/p&gt;




&lt;p&gt;Looking ahead, there are still opportunities to push quality engineering further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Risk-based regression selection&lt;/li&gt;
&lt;li&gt;Historical coverage trend analysis&lt;/li&gt;
&lt;li&gt;Pull request–level coverage impact reports&lt;/li&gt;
&lt;li&gt;AI-assisted test maintenance&lt;/li&gt;
&lt;li&gt;Flaky-test detection&lt;/li&gt;
&lt;li&gt;Visual regression&lt;/li&gt;
&lt;li&gt;Requirement-to-test traceability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are areas I'd like to continue exploring as software systems—and the expectations around software quality—continue to evolve.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you've built something similar, or approached release confidence differently, I'd genuinely love to hear about it. Let's connect and discuss.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>playwright</category>
      <category>softwareengineering</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to add watermark on an image in Laravel 5.8 Step by Step</title>
      <dc:creator>L Anil Kumar Singha</dc:creator>
      <pubDate>Tue, 04 Aug 2020 06:46:41 +0000</pubDate>
      <link>https://dev.to/anilloutombam/how-to-add-watermark-on-an-image-in-laravel-5-8-step-by-step-5acd</link>
      <guid>https://dev.to/anilloutombam/how-to-add-watermark-on-an-image-in-laravel-5-8-step-by-step-5acd</guid>
      <description>&lt;p&gt;In this blog post, we will learn, how to add watermark to an image in Laravel 5.8 application, to add watermark to images we will use the intervention image composer package in Laravel 5.8. we can add images or text as a watermark on the image in Laravel. For more docs, you can follow them.&lt;br&gt;
&lt;a href="http://image.intervention.io/" rel="noopener noreferrer"&gt;Intervention Image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To make a watermark on an image. we will install intervention/image package and then we will create one simple route to adding image watermark in the Laravel app. so let’s follow the below step to add image watermark in Laravel 5.8.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step:1 Install intervention/image Package.
&lt;/h2&gt;

&lt;p&gt;In the first step, I install an intervention/image composer package for adding watermark to the image in Laravel 5.8. to install a package using the following command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;composer require intervention/image&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
After installation, we need to set providers and alias, to do that we need to follow some steps… go to your project and search.&lt;/p&gt;
&lt;h3&gt;
  
  
  config/app.php
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="nc"&gt;Intervention\Image\ImageServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="n"&gt;aliases&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Intervention\Image\Facades\Image&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Step:2 Add Watermark to image
&lt;/h3&gt;

&lt;p&gt;Here, I created a controller(you can use a simple route) to add a watermark to the image. so you need to add two images on your public “images” folder for testing purposes.&lt;/p&gt;

&lt;p&gt;Here I use two images one for the main image and a second image for watermark so I have a &lt;strong&gt;main.png&lt;/strong&gt; and &lt;strong&gt;watermark.png&lt;/strong&gt; image on your images folder for demo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;addWatermark&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
   &lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;public_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/main.png'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;    
   &lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;public_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'watermark.png'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="s1"&gt;'bottom-right'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
   &lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here we completed tutorials on How to Add Watermark on an Images in Laravel 5.8 step by step.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
