<?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: desgh white</title>
    <description>The latest articles on DEV Community by desgh white (@desgh_white_7e4c654897816).</description>
    <link>https://dev.to/desgh_white_7e4c654897816</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%2F4020165%2F017e6d3a-173e-4404-87c6-42f2db79349b.png</url>
      <title>DEV Community: desgh white</title>
      <link>https://dev.to/desgh_white_7e4c654897816</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/desgh_white_7e4c654897816"/>
    <language>en</language>
    <item>
      <title>Modeling an Industrial Materials Catalog: Steel-Strip Specs as Structured Data</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:07:16 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/modeling-an-industrial-materials-catalog-steel-strip-specs-as-structured-data-2cjp</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/modeling-an-industrial-materials-catalog-steel-strip-specs-as-structured-data-2cjp</guid>
      <description>&lt;p&gt;B2B catalogs for industrial materials look boring until you try to make them queryable. A "steel strip" isn't one product — it's a point in a multi-dimensional space of grade, thickness, width, temper and finish. Here's how to model that without drowning in a combinatorial explosion of SKUs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't enumerate variants — describe axes
&lt;/h2&gt;

&lt;p&gt;The naive approach creates one row per physical variant, and a mid-size strip supplier ends up with tens of thousands of near-duplicate SKUs. Instead, separate the &lt;em&gt;product family&lt;/em&gt; from its &lt;em&gt;dimensional axes&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;          &lt;span class="c1"&gt;# e.g. "08X18H10", "12X18H10T", "cold-rolled-08kp"
&lt;/span&gt;    &lt;span class="n"&gt;thickness_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;width_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;temper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;         &lt;span class="c1"&gt;# annealed / hard / half-hard
&lt;/span&gt;    &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;         &lt;span class="c1"&gt;# 2B, BA, matte
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A concrete offering is a &lt;code&gt;StripSpec&lt;/code&gt; plus stock and price; the family ("cold-rolled strip", "stainless strip") groups them for browsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grades are an enum with metadata, not free text
&lt;/h2&gt;

&lt;p&gt;Stainless grades like 12X18H10T and 08X18H10 map to standardized compositions. Store them as a lookup keyed by canonical grade code, with cross-references (GOST / AISI equivalents) as data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;GRADES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12X18H10T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aisi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;321&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;austenitic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;08X18H10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aisi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;304&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;austenitic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08&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;Now "show me austenitic strips under 1mm" is a filter, not a full-text search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Range queries need half-open intervals
&lt;/h2&gt;

&lt;p&gt;Thickness and width are continuous, so users query ranges, not equality. Index them as numeric columns and always use half-open intervals &lt;code&gt;[lo, hi)&lt;/code&gt; to avoid the double-counting bug at boundaries when a strip sits exactly on a grid line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Looking at how an actual supplier organizes its range is a useful sanity check before you fix your schema. A catalog such as &lt;a href="https://lenta-stalnaja.ru/" rel="noopener noreferrer"&gt;лента 12х18н10т&lt;/a&gt; lays out cold-rolled and stainless strip by grade and dimension, which is a good reference for the axes worth exposing as filters versus the ones better left as free attributes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Model the axes, not the variants; make grades a metadata-carrying enum with standard cross-references; and treat dimensions as indexed numeric ranges. The catalog stays small, and every "do you have X in Y?" question becomes a query instead of a scan.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Modeling an Industrial Materials Catalog: Steel-Strip Specs as Structured Data</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:44:02 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/modeling-an-industrial-materials-catalog-steel-strip-specs-as-structured-data-1npj</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/modeling-an-industrial-materials-catalog-steel-strip-specs-as-structured-data-1npj</guid>
      <description>&lt;p&gt;B2B catalogs for industrial materials look boring until you try to make them queryable. A "steel strip" isn't one product — it's a point in a multi-dimensional space of grade, thickness, width, temper and finish. Here's how to model that without drowning in a combinatorial explosion of SKUs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't enumerate variants — describe axes
&lt;/h2&gt;

&lt;p&gt;The naive approach creates one row per physical variant, and a mid-size strip supplier ends up with tens of thousands of near-duplicate SKUs. Instead, separate the &lt;em&gt;product family&lt;/em&gt; from its &lt;em&gt;dimensional axes&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;          &lt;span class="c1"&gt;# e.g. "08X18H10", "12X18H10T", "cold-rolled-08kp"
&lt;/span&gt;    &lt;span class="n"&gt;thickness_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;width_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;temper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;         &lt;span class="c1"&gt;# annealed / hard / half-hard
&lt;/span&gt;    &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;         &lt;span class="c1"&gt;# 2B, BA, matte
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A concrete offering is a &lt;code&gt;StripSpec&lt;/code&gt; plus stock and price; the family ("cold-rolled strip", "stainless strip") groups them for browsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grades are an enum with metadata, not free text
&lt;/h2&gt;

&lt;p&gt;Stainless grades like 12X18H10T and 08X18H10 map to standardized compositions. Store them as a lookup keyed by canonical grade code, with cross-references (GOST / AISI equivalents) as data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;GRADES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12X18H10T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aisi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;321&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;austenitic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;08X18H10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aisi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;304&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;austenitic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08&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;Now "show me austenitic strips under 1mm" is a filter, not a full-text search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Range queries need half-open intervals
&lt;/h2&gt;

&lt;p&gt;Thickness and width are continuous, so users query ranges, not equality. Index them as numeric columns and always use half-open intervals &lt;code&gt;[lo, hi)&lt;/code&gt; to avoid the double-counting bug at boundaries when a strip sits exactly on a grid line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Looking at how an actual supplier organizes its range is a useful sanity check before you fix your schema. A catalog such as &lt;a href="https://lenta-stalnaja.ru/" rel="noopener noreferrer"&gt;лента стальная&lt;/a&gt; lays out cold-rolled and stainless strip by grade and dimension, which is a good reference for the axes worth exposing as filters versus the ones better left as free attributes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Model the axes, not the variants; make grades a metadata-carrying enum with standard cross-references; and treat dimensions as indexed numeric ranges. The catalog stays small, and every "do you have X in Y?" question becomes a query instead of a scan.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Modeling an Industrial Materials Catalog: Steel-Strip Specs as Structured Data</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Wed, 05 Aug 2026 11:47:33 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/modeling-an-industrial-materials-catalog-steel-strip-specs-as-structured-data-1d46</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/modeling-an-industrial-materials-catalog-steel-strip-specs-as-structured-data-1d46</guid>
      <description>&lt;p&gt;B2B catalogs for industrial materials look boring until you try to make them queryable. A "steel strip" isn't one product — it's a point in a multi-dimensional space of grade, thickness, width, temper and finish. Here's how to model that without drowning in a combinatorial explosion of SKUs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't enumerate variants — describe axes
&lt;/h2&gt;

&lt;p&gt;The naive approach creates one row per physical variant, and a mid-size strip supplier ends up with tens of thousands of near-duplicate SKUs. Instead, separate the &lt;em&gt;product family&lt;/em&gt; from its &lt;em&gt;dimensional axes&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;          &lt;span class="c1"&gt;# e.g. "08X18H10", "12X18H10T", "cold-rolled-08kp"
&lt;/span&gt;    &lt;span class="n"&gt;thickness_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;width_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;temper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;         &lt;span class="c1"&gt;# annealed / hard / half-hard
&lt;/span&gt;    &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;         &lt;span class="c1"&gt;# 2B, BA, matte
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A concrete offering is a &lt;code&gt;StripSpec&lt;/code&gt; plus stock and price; the family ("cold-rolled strip", "stainless strip") groups them for browsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grades are an enum with metadata, not free text
&lt;/h2&gt;

&lt;p&gt;Stainless grades like 12X18H10T and 08X18H10 map to standardized compositions. Store them as a lookup keyed by canonical grade code, with cross-references (GOST / AISI equivalents) as data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;GRADES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12X18H10T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aisi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;321&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;austenitic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;08X18H10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aisi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;304&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;austenitic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08&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;Now "show me austenitic strips under 1mm" is a filter, not a full-text search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Range queries need half-open intervals
&lt;/h2&gt;

&lt;p&gt;Thickness and width are continuous, so users query ranges, not equality. Index them as numeric columns and always use half-open intervals &lt;code&gt;[lo, hi)&lt;/code&gt; to avoid the double-counting bug at boundaries when a strip sits exactly on a grid line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Looking at how an actual supplier organizes its range is a useful sanity check before you fix your schema. A catalog such as &lt;a href="https://lenta-stalnaja.ru/" rel="noopener noreferrer"&gt;здесь&lt;/a&gt; lays out cold-rolled and stainless strip by grade and dimension, which is a good reference for the axes worth exposing as filters versus the ones better left as free attributes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Model the axes, not the variants; make grades a metadata-carrying enum with standard cross-references; and treat dimensions as indexed numeric ranges. The catalog stays small, and every "do you have X in Y?" question becomes a query instead of a scan.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Combinatorics in Production: Settling a 15-Line Full-Cover Bet Correctly</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Mon, 03 Aug 2026 07:06:45 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/combinatorics-in-production-settling-a-15-line-full-cover-bet-correctly-3kl8</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/combinatorics-in-production-settling-a-15-line-full-cover-bet-correctly-3kl8</guid>
      <description>&lt;p&gt;Every so often a domain hands you a problem that looks like a one-liner and turns out to have a dozen edge cases. Settling a "Lucky 15" — a betting slip covering four selections across fifteen combinations — is one of them. It is a nice study in why naive combinatorics plus real-world rules is where bugs live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the problem
&lt;/h2&gt;

&lt;p&gt;Four selections produce every non-empty combination of size 1..4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;combinations&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selections&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selections&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nf"&gt;combinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;   &lt;span class="c1"&gt;# 4 + 6 + 4 + 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four singles, six doubles, four trebles, one fourfold. The return for a line is the product of the decimal odds of its members, times the unit stake, and only if every member won:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;prod&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;line_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stake&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;won&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stake&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;odds&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total return is the sum over all fifteen lines. That is the entire happy path, and it is where most implementations stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually gets hard
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stake semantics.&lt;/strong&gt; The unit stake applies &lt;em&gt;per line&lt;/em&gt;, so a "£1 Lucky 15" costs £15. Getting this backwards is the single most common bug in hobby implementations, and it is off by a factor of fifteen — an error big enough that no one notices it is a rounding problem, because it isn't one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus rules are not arithmetic, they are policy.&lt;/strong&gt; Bookmakers attach concessions: a one-winner bonus paying a lone winner at double the odds, an all-winners bonus adding 10–20% to the return. They vary per operator in ways that resist a single formula — some apply the percentage to the whole return, others only to winning lines. Model them as a strategy object per operator, not as an &lt;code&gt;if&lt;/code&gt; in the settlement loop, or you will be editing the core function every time a rule changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-runners collapse the bet.&lt;/strong&gt; A withdrawn selection does not void the slip; it reduces it to the next size down — a Lucky 15 with one non-runner settles as a Lucky 11 over the remaining three. That means the line set is computed &lt;em&gt;after&lt;/em&gt; filtering, not before, and your "15" is a derived value rather than a constant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 4 deductions&lt;/strong&gt; apply a percentage reduction to winnings at prices taken before a withdrawal. It touches odds, not stake, and it must be applied before the bonus, not after. Order of operations is load-bearing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each-way doubles the line count&lt;/strong&gt; and settles the place portion at a fraction of the odds, fixed when the bet was struck rather than at settlement time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;This is a domain where property-based tests earn their keep. Useful invariants: the return is monotonic in each selection's odds; adding a losing selection never increases the return; a slip where every selection wins returns at least the sum of its parts; and settling with the unit stake scaled by &lt;em&gt;k&lt;/em&gt; scales the return by exactly &lt;em&gt;k&lt;/em&gt;. Then pin the awkward cases — one winner, all non-runners, a Rule 4 with a bonus — as explicit regression tests, because those are the ones that break when someone "simplifies" the bonus logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference implementation
&lt;/h2&gt;

&lt;p&gt;If you want to sanity-check your numbers against a working implementation before writing the tests, a &lt;a href="https://lucky-15-bet-calculator.uk/" rel="noopener noreferrer"&gt;visit the website&lt;/a&gt; settles the same fifteen lines with the operator concessions applied, which makes it a convenient oracle for the cases you have not thought of yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Generate the line set with &lt;code&gt;combinations&lt;/code&gt;, derive the count instead of hard-coding fifteen, keep operator rules in a strategy object, and get the order right: filter non-runners, apply Rule 4, sum the lines, then apply the bonus. The combinatorics are five lines of code; the domain rules are the actual program.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Onboarding Account Security: 2FA and Sane Defaults From Day One</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:13:25 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/onboarding-account-security-2fa-and-sane-defaults-from-day-one-455d</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/onboarding-account-security-2fa-and-sane-defaults-from-day-one-455d</guid>
      <description>&lt;p&gt;Most account takeovers exploit the gap between signup and the moment a user finally enables protection. Close that gap by making security the default path, not an optional settings page nobody visits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforce strong, unique secrets at creation
&lt;/h2&gt;

&lt;p&gt;Reject reused and breached passwords at the boundary with a k-anonymity check against a compromised-password API — you never send the full hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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;list&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.pwnedpasswords.com/range/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;breached&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;
  
  
  Make 2FA the onboarding step, not a chore
&lt;/h2&gt;

&lt;p&gt;Offer TOTP enrollment inside the first-run flow, before the first deposit or sensitive action. Show the QR, verify one code, and store recovery codes — completion rates collapse the moment you defer it to "later".&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify, then trust
&lt;/h2&gt;

&lt;p&gt;Confirm the email before granting privileged actions and walk the user through each step instead of failing silently. A guided setup that hides no terms converts far better than a wall of security jargon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Platforms built for first-time users are a good model for low-friction onboarding. A site like &lt;a href="https://fieryplaypoland.com/" rel="noopener noreferrer"&gt;FieryPlay&lt;/a&gt; shows a clear registration, sensible starting bonuses and transparent rules — the kind of frictionless first-run flow that keeps users from bouncing before they're protected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Breach-check at creation, enroll 2FA during onboarding, and gate privileged actions behind a verified email. Secure defaults beat a security page nobody opens.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Offline-First Mobile Payments: A PWA Pattern for Flaky Networks</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:12:42 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/offline-first-mobile-payments-a-pwa-pattern-for-flaky-networks-1m8p</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/offline-first-mobile-payments-a-pwa-pattern-for-flaky-networks-1m8p</guid>
      <description>&lt;p&gt;Mobile-first users expect an app that keeps working through a tunnel or a crowded stadium. For a payment or wallet flow, "offline-first" is not a nicety — it's the difference between a completed action and a lost user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Queue the intent, sync the result
&lt;/h2&gt;

&lt;p&gt;Never block the UI on the network. Capture the user's intent locally, acknowledge it, and reconcile when connectivity returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&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;idb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// durable, survives reload&lt;/span&gt;
  &lt;span class="nf"&gt;showOptimistic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;flushOutbox&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;flushOutbox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Background Sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Make replays safe
&lt;/h2&gt;

&lt;p&gt;An outbox means the same request can fire twice. Stamp every intent with a client-generated idempotency key and let the server dedupe — optimistic UI is only safe when the backend refuses to double-charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache the shell, revalidate the data
&lt;/h2&gt;

&lt;p&gt;Split caching by type: app shell cache-first, balance and history stale-while-revalidate. The user sees the last known state instantly, then a quiet refresh corrects it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Consumer entertainment apps are aggressive adopters of installable, mobile-first web experiences. An app like &lt;a href="https://lalabet-apps.nl/" rel="noopener noreferrer"&gt;Lalabet&lt;/a&gt; shows the fast, app-like flow — quick deposits, a clean interface, simple registration — that mobile users now treat as the baseline, a helpful benchmark when you tune your own install and offline states.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;A durable outbox, idempotency keys, and typed caching turn a fragile mobile form into something that survives the real world's terrible networks.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building an Each-Way Odds Calculator That Handles Mixed Formats</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:11:22 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/building-an-each-way-odds-calculator-that-handles-mixed-formats-eb1</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/building-an-each-way-odds-calculator-that-handles-mixed-formats-eb1</guid>
      <description>&lt;p&gt;A returns calculator looks trivial until fractional and decimal odds land on the same slip and an each-way term settles separately. Here's how to build one whose output you can actually trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalize odds first
&lt;/h2&gt;

&lt;p&gt;Never do math on the display format. Convert everything to a decimal multiplier at the boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toDecimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;
  &lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&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;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;d&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fractional &lt;code&gt;5/2&lt;/code&gt; becomes &lt;code&gt;3.5&lt;/code&gt;, decimal &lt;code&gt;3.5&lt;/code&gt; stays &lt;code&gt;3.5&lt;/code&gt;. Every downstream calculation now speaks one language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compound the legs explicitly
&lt;/h2&gt;

&lt;p&gt;For a double, the full return of the first leg becomes the stake on the second. Model it as a fold, not a special case, so trebles and accumulators fall out for free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;legs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;toDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leg&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="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Split the each-way portion
&lt;/h2&gt;

&lt;p&gt;The place part settles on its own fraction (often 1/4 or 1/5 of the odds) and only to the placed terms. Compute win-only, place-only and combined separately — surfacing all three prevents the classic "why is my return different" support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Dedicated tools handle these edge cases so users don't have to. A calculator like &lt;a href="https://double-bet-calculator.uk/" rel="noopener noreferrer"&gt;Double Bet Calculator review&lt;/a&gt; applies the correct each-way terms per leg and shows the exact payout instead of a rough guess — a solid reference for the output states your own UI has to cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Normalize at the boundary, compound with a fold, and split the each-way portion into three explicit results. That's a calculator whose numbers hold up to scrutiny.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Designing a Fair Payment-Method Comparison Table for Consumer Apps</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:10:38 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/designing-a-fair-payment-method-comparison-table-for-consumer-apps-2b01</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/designing-a-fair-payment-method-comparison-table-for-consumer-apps-2b01</guid>
      <description>&lt;p&gt;Comparison tables are where users make real decisions, yet most are built as static HTML that rots the moment fees or limits change. Here's a data-driven approach that stays honest and sortable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model each method as a record, not a row
&lt;/h2&gt;

&lt;p&gt;Don't hand-write &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; elements. Describe every payment method as data and render from it:&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"instant_transfer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deposit_fee_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"withdraw_hours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"min_deposit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"supports_mobile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;Now sorting by withdrawal time or filtering fee-free methods is a one-liner instead of a DOM rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the sort defensible
&lt;/h2&gt;

&lt;p&gt;Users distrust tables that look rigged. Expose the sort key, keep the comparator pure, and never hide a column that changes the ranking. A stable multi-key sort (primary fee, tiebreak payout time) reads as fair because it &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Consumer comparison portals are a good study in dense, decision-driving tables. A ranked listing such as &lt;a href="https://najlepsze-kasynaonline.com.pl/" rel="noopener noreferrer"&gt;Najlepsze Kasyna Online&lt;/a&gt; shows how licence, payout time and real bonus terms sit side by side so a non-technical reader can compare at a glance — a useful reference before you design your own columns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the data fresh
&lt;/h2&gt;

&lt;p&gt;Back the table with a small config service and a &lt;code&gt;last_updated&lt;/code&gt; timestamp per record. A stale comparison is worse than none: surface the date so users know the numbers are current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Records over rows, a pure sortable comparator, and a visible freshness stamp turn a comparison table from marketing decoration into a tool users actually trust.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Streaming Real-Time Game Stats to the Browser with WebSockets</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Mon, 20 Jul 2026 15:54:26 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/streaming-real-time-game-stats-to-the-browser-with-websockets-1nka</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/streaming-real-time-game-stats-to-the-browser-with-websockets-1nka</guid>
      <description>&lt;p&gt;Live dashboards that update the instant something happens feel magical to users and are surprisingly approachable to build. This is a compact pattern for pushing real-time event stats to the browser without hammering your API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WebSockets over polling
&lt;/h2&gt;

&lt;p&gt;Polling every second wastes requests and still lags. A single WebSocket keeps a persistent channel open and pushes only deltas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://api.example.com/stats&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;round&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;ts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyDelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;round&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;ts&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;h2&gt;
  
  
  Keep the client honest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequence numbers&lt;/strong&gt; on every message so the client can detect gaps and request a resync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heartbeat/ping&lt;/strong&gt; to distinguish a quiet feed from a dead socket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpressure&lt;/strong&gt;: batch high-frequency updates into animation-frame flushes so the UI never thrashes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Live game-show stat trackers are a good study in dense, constantly-updating UIs. A tracker like &lt;a href="https://crazytimes.it/" rel="noopener noreferrer"&gt;crazy time&lt;/a&gt; surfaces rolling histories and live outcomes — a useful reference for how to present a high-frequency event stream to non-technical users without overwhelming them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server side
&lt;/h2&gt;

&lt;p&gt;Fan-out is the hard part. Put a Redis pub/sub (or NATS) between your ingest and your socket layer so any number of socket servers can subscribe to the same event stream and scale horizontally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Persistent socket, sequence-numbered deltas, animation-frame flushing, and a pub/sub fan-out — that's the whole recipe for a real-time stats UI that stays smooth under load.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Designing a Promo-Code and Referral System That Actually Scales</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Sun, 19 Jul 2026 10:51:16 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/designing-a-promo-code-and-referral-system-that-actually-scales-29bd</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/designing-a-promo-code-and-referral-system-that-actually-scales-29bd</guid>
      <description>&lt;p&gt;Promo codes look trivial until you're refunding a bug that let one code stack a thousand times. A robust promo/referral system is mostly about idempotency, atomic limits, and clean auditability. Here's a design that holds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model codes as immutable rules
&lt;/h2&gt;

&lt;p&gt;A code isn't a string — it's a rule set: eligibility, reward, and constraints.&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WELCOME25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reward"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"percent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&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;"max_redemptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"per_user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"starts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-01"&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;h2&gt;
  
  
  Redeem atomically
&lt;/h2&gt;

&lt;p&gt;The whole value of a limit is that it can't be exceeded under concurrency. Enforce it in one atomic step:&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;UPDATE&lt;/span&gt; &lt;span class="n"&gt;promo&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No returned row means the code is exhausted — no race, no oversell. Pair it with a unique &lt;code&gt;(code, user_id)&lt;/code&gt; constraint to enforce &lt;code&gt;per_user&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Consumer sites that lean on promo-driven acquisition are a good model for the redemption UX. A promo page like &lt;a href="https://voxcasino-pol.com/promocode" rel="noopener noreferrer"&gt;vox casino&lt;/a&gt; shows how a code, its terms, and its call-to-action are presented so users understand exactly what they're claiming — worth studying before you design your own redemption screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit everything
&lt;/h2&gt;

&lt;p&gt;Every redemption writes an immutable ledger row (&lt;code&gt;code&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;ts&lt;/code&gt;, &lt;code&gt;reward_granted&lt;/code&gt;). When finance asks why a campaign cost what it did, the answer is one query — not a forensic reconstruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Codes as immutable rules, atomic decrement for limits, unique constraints for per-user caps, and an append-only ledger. That's a promo system you can run a real budget through.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>On-Chain Settlement Patterns for Turn-Based Multiplayer Games</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Sun, 19 Jul 2026 10:50:38 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/on-chain-settlement-patterns-for-turn-based-multiplayer-games-31d1</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/on-chain-settlement-patterns-for-turn-based-multiplayer-games-31d1</guid>
      <description>&lt;p&gt;Putting real value on the outcome of a multiplayer game raises a hard question: how do you settle stakes trustlessly without paying gas on every single move? The answer is to keep gameplay off-chain and settle on-chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  State channels in one paragraph
&lt;/h2&gt;

&lt;p&gt;Players lock a stake in a contract, then exchange &lt;strong&gt;signed state updates&lt;/strong&gt; peer-to-peer for the whole game. Only the final signed state (or a dispute) ever touches the chain. Gas is paid twice — open and close — not per turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function settle(bytes32 finalState, bytes[] calldata sigs) external {
    require(verifyAll(finalState, sigs), "bad sigs");
    _payout(decode(finalState));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The dispute window
&lt;/h2&gt;

&lt;p&gt;If a peer disappears mid-game, the honest player submits the latest state they hold. A timeout window lets the counterparty challenge with a newer signed state; whoever holds the freshest valid state wins the settlement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Crypto-native card platforms are where these settlement ideas get battle-tested at scale. A platform like &lt;a href="https://bitcoin-poker-de.com/" rel="noopener noreferrer"&gt;bitcoin poker&lt;/a&gt; illustrates the deposit, play, and payout loop players expect — a concrete reference point for the latency and finality guarantees your settlement layer has to meet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provable fairness
&lt;/h2&gt;

&lt;p&gt;Pair settlement with a commit-reveal RNG so shuffles are verifiable: commit to a hashed seed before the hand, reveal after, and let clients recompute the deal. Trustless money demands trustless randomness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;State channels for gas efficiency, a dispute window for liveness, and commit-reveal for fair shuffles — together they make on-chain stakes practical for real-time card games.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Offline-First Progressive Web Apps for the Nordic Market</title>
      <dc:creator>desgh white</dc:creator>
      <pubDate>Sun, 19 Jul 2026 10:50:00 +0000</pubDate>
      <link>https://dev.to/desgh_white_7e4c654897816/offline-first-progressive-web-apps-for-the-nordic-market-2jil</link>
      <guid>https://dev.to/desgh_white_7e4c654897816/offline-first-progressive-web-apps-for-the-nordic-market-2jil</guid>
      <description>&lt;p&gt;Nordic users are mobile-heavy and expect apps that keep working through spotty coverage on trains and in lifts. A Progressive Web App with an offline-first service worker delivers that without shipping to two native app stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache strategy that matters
&lt;/h2&gt;

&lt;p&gt;Split your caching by resource type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app shell -&amp;gt; cache-first&lt;/span&gt;
&lt;span class="c1"&gt;// API data  -&amp;gt; stale-while-revalidate&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetch&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;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;staleWhileRevalidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cacheFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&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;h2&gt;
  
  
  Localize more than strings
&lt;/h2&gt;

&lt;p&gt;For the Nordic market, currency (NOK), date formats, and consent flows differ per country. Drive them off the &lt;code&gt;Accept-Language&lt;/code&gt; header and a per-locale config bundle, lazy-loaded so a Norwegian user never downloads Swedish copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world reference
&lt;/h2&gt;

&lt;p&gt;Consumer entertainment sites are aggressive adopters of installable, mobile-first web apps. A site like &lt;a href="https://vulkan-vegas-norway.com/" rel="noopener noreferrer"&gt;vulkan vegas&lt;/a&gt; shows the kind of fast, app-like mobile experience Nordic users now expect by default — a helpful benchmark when you tune your own install and offline flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't forget the install prompt
&lt;/h2&gt;

&lt;p&gt;Capture &lt;code&gt;beforeinstallprompt&lt;/code&gt;, stash the event, and surface your own "Add to home screen" CTA at a natural moment rather than letting the browser's default prompt fire at random.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Typed cache strategies, locale-driven bundles, and a controlled install prompt turn a plain site into an app Nordic users will actually keep on their home screen.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
