<?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: Daniel Ferreira Monteiro Alves</title>
    <description>The latest articles on DEV Community by Daniel Ferreira Monteiro Alves (@danfma).</description>
    <link>https://dev.to/danfma</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%2F1250889%2F9e6ccc04-a88d-49c0-8fa1-39bfabd923b3.jpeg</url>
      <title>DEV Community: Daniel Ferreira Monteiro Alves</title>
      <link>https://dev.to/danfma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danfma"/>
    <language>en</language>
    <item>
      <title>Turfano: when the AI helps you admit the first version was wrong</title>
      <dc:creator>Daniel Ferreira Monteiro Alves</dc:creator>
      <pubDate>Wed, 22 Jul 2026 17:34:24 +0000</pubDate>
      <link>https://dev.to/danfma/turfano-when-the-ai-helps-you-admit-the-first-version-was-wrong-1k6a</link>
      <guid>https://dev.to/danfma/turfano-when-the-ai-helps-you-admit-the-first-version-was-wrong-1k6a</guid>
      <description>&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%2Fy8vj3ptdfef5rmxjqp15.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%2Fy8vj3ptdfef5rmxjqp15.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Turfano is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/danfma/Turfano" rel="noopener noreferrer"&gt;Turfano&lt;/a&gt; is a faithful .NET port of &lt;a href="https://turfjs.org" rel="noopener noreferrer"&gt;TurfJS&lt;/a&gt; — the most widely used geospatial analysis library in JavaScript — with its own GeoJSON types and &lt;strong&gt;zero external dependencies&lt;/strong&gt; in the core package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Turfano.GeoJson&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;122.4194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;37.7749&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;122.4094&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;37.7849&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Turfano.Units.Length&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kilometers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;F3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; km"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;union&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overlappingSquare&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Turfano.GeoJson.Geometry?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's today. Because last October's version was a completely different project — and the difference is the story worth telling.&lt;/p&gt;

&lt;h2&gt;
  
  
  October 2025: 63% parity, built on top of NetTopologySuite
&lt;/h2&gt;

&lt;p&gt;Before it was called Turfano, the project was called &lt;strong&gt;DotTerritory&lt;/strong&gt;: a wrapper that translated TurfJS's functions using &lt;a href="https://github.com/NetTopologySuite/NetTopologySuite" rel="noopener noreferrer"&gt;NetTopologySuite&lt;/a&gt; (NTS) for geometry types and &lt;a href="https://github.com/angularsen/UnitsNet" rel="noopener noreferrer"&gt;UnitsNet&lt;/a&gt; for quantities. An efficiency report generated by another AI (Devin, October 2025) documents the state of the art back then: 72 of the 114 functions in the &lt;code&gt;@turf&lt;/code&gt; index implemented — &lt;strong&gt;63% coverage&lt;/strong&gt;.&lt;/p&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%2Fwijiu12ymjet9lmlrgwh.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%2Fwijiu12ymjet9lmlrgwh.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That wasn't nothing. But it was a wrapper — and a wrapper means inheriting the semantics of whatever you're wrapping, not the semantics of whatever you're porting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem that only shows up when you compare against the real source
&lt;/h2&gt;

&lt;p&gt;NTS is an excellent topology engine. But it resolves the same geometric operations with subtly different rules than the original TurfJS: polygon boundary handling, whether the centroid includes the closing vertex, whether &lt;code&gt;nearestPointOnLine&lt;/code&gt; is planar or geodesic. Swapping the implementation underneath (re-typing logic that already existed on top of NTS) &lt;strong&gt;isn't safe&lt;/strong&gt; — that's documented, in plain terms, in the spec for the feature that pulls the NTS engine out of the overlay path (&lt;code&gt;specs/009-nts-engine-exit&lt;/code&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When porting functions (parity waves), re-typing existing logic is not safe: validating against the real &lt;code&gt;@turf&lt;/code&gt; caught bugs in the NTS-based code (&lt;code&gt;RhumbDestination&lt;/code&gt; with the wrong &lt;code&gt;q&lt;/code&gt;; &lt;code&gt;nearestPointOnLine&lt;/code&gt; planar vs. geodesic; &lt;code&gt;centroid&lt;/code&gt; including the closing vertex; &lt;code&gt;Boolean*&lt;/code&gt; predicates using NTS boundary semantics instead of Turf's). Always validate each function against the real &lt;code&gt;@turf&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the lesson logged — in the project's own lessons-learned file — right after a simpler, earlier one: &lt;strong&gt;avoid cryptic acronyms in names&lt;/strong&gt; (no &lt;code&gt;D2R&lt;/code&gt;, no &lt;code&gt;Rtan&lt;/code&gt;; prefer &lt;code&gt;RadiansPerDegree&lt;/code&gt;, &lt;code&gt;RightTangent&lt;/code&gt; — except well-known domain terms, like &lt;code&gt;BBox&lt;/code&gt;, &lt;code&gt;lon&lt;/code&gt;/&lt;code&gt;lat&lt;/code&gt;, and the geodesic notation &lt;code&gt;phi&lt;/code&gt;/&lt;code&gt;lambda&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The rewrite: port the real algorithm, not someone else's translation
&lt;/h2&gt;

&lt;p&gt;The decision was to throw away most of the existing surface and port the actual algorithms, validating every single function against the real TurfJS TypeScript source (vendored under &lt;code&gt;reference/&lt;/code&gt;, run with Bun):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overlay&lt;/strong&gt; (&lt;code&gt;Union&lt;/code&gt;/&lt;code&gt;Difference&lt;/code&gt;/&lt;code&gt;Intersect&lt;/code&gt;/&lt;code&gt;Dissolve&lt;/code&gt;): a port of the &lt;strong&gt;Martinez–Rueda&lt;/strong&gt; engine via &lt;code&gt;polyclip-ts&lt;/code&gt; — the very source &lt;code&gt;@turf&lt;/code&gt; itself runs — instead of delegating to NTS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolines/isobands&lt;/strong&gt;: marching squares faithful to TurfJS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clustering&lt;/strong&gt;: k-means with &lt;strong&gt;deterministic&lt;/strong&gt; centroids (the first k points), for reproducibility — no hidden randomness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffer&lt;/strong&gt;: the one operation that stays NTS-bound on purpose, isolated in an optional satellite package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result, across two identity rewrites: the package became &lt;strong&gt;Turfano&lt;/strong&gt; (it was &lt;code&gt;DotTerritory&lt;/code&gt;) and the main class became &lt;strong&gt;&lt;code&gt;Turf&lt;/code&gt;&lt;/strong&gt; (it was &lt;code&gt;Territory&lt;/code&gt;, to match TurfJS's own naming — &lt;code&gt;Turf.Area()&lt;/code&gt;, &lt;code&gt;Turf.Distance()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Today: &lt;strong&gt;100% of the &lt;code&gt;@turf&lt;/code&gt; index&lt;/strong&gt; covered by the &lt;code&gt;Geo&lt;/code&gt; facade, 295+ tests, &lt;code&gt;1.0.0-rc.1&lt;/code&gt;, zero external dependencies in the core. NTS didn't disappear — it became the &lt;code&gt;Turfano.NetTopologySuite&lt;/code&gt; satellite package, for anyone who needs &lt;code&gt;Buffer&lt;/code&gt; or already lives in that ecosystem (a public &lt;code&gt;ToNts&lt;/code&gt;/&lt;code&gt;FromNts&lt;/code&gt; bridge, with a packed-sequence boundary — zero materialized &lt;code&gt;Coordinate&lt;/code&gt; objects).&lt;/p&gt;

&lt;h2&gt;
  
  
  Two bonus finds while re-reading the repo to write this
&lt;/h2&gt;

&lt;p&gt;The lessons-learned file also records that running a subagent editing files in the same working tree while the orchestrator was doing &lt;code&gt;checkout&lt;/code&gt;/&lt;code&gt;commit&lt;/code&gt; in parallel caused a git race — the documented fix is to isolate that subagent in its own &lt;code&gt;git worktree&lt;/code&gt;. The AI documented its own operational mistake so it wouldn't repeat it.&lt;/p&gt;

&lt;p&gt;And the project's constitution file (the spec-kit governance "manifesto," &lt;code&gt;.specify/memory/constitution.md&lt;/code&gt;) still has the example placeholders — &lt;code&gt;[PRINCIPLE_1_NAME]&lt;/code&gt;, &lt;code&gt;[GOVERNANCE_RULES]&lt;/code&gt; — never filled in. The AI is rigorous enough to validate every ported function against the source; filling out the "why this project exists" template, not so much.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fast convergence — and what it doesn't replace
&lt;/h2&gt;

&lt;p&gt;The idea of "fast convergence between different systems" (getting a JS ecosystem and a .NET ecosystem to agree, function by function, on the same result) only worked here because every port was checked against the real source — never against the intuition of "it should be similar to what we already had." AI massively speeds up the work of porting the 42 functions that were missing. It doesn't speed up the part where you realize the 63%-covered foundation you already built had bugs too subtle to trust without re-checking — that still takes someone willing to throw away what "mostly worked."&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on how this was built
&lt;/h2&gt;

&lt;p&gt;Turfano was built with &lt;strong&gt;OpenCode running GLM-5.2&lt;/strong&gt; — a different tool-and-model pairing than the one used for MySheet (Claude Opus/Sonnet). Two different AIs, two different spec-kit workflows, and the same pattern underneath: a history only becomes a tellable story once someone — human or AI — bothers to write down the &lt;em&gt;why&lt;/em&gt;, not just the &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Turfano is open source. Full code, complete &lt;code&gt;@turf&lt;/code&gt; index coverage, and the specs for every parity wave: &lt;a href="https://github.com/danfma/Turfano" rel="noopener noreferrer"&gt;github.com/danfma/Turfano&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>MySheet: the Excel formula engine I learned to audit as fast as I wrote it</title>
      <dc:creator>Daniel Ferreira Monteiro Alves</dc:creator>
      <pubDate>Wed, 22 Jul 2026 17:32:22 +0000</pubDate>
      <link>https://dev.to/danfma/mysheet-the-excel-formula-engine-i-learned-to-audit-as-fast-as-i-wrote-it-1k6k</link>
      <guid>https://dev.to/danfma/mysheet-the-excel-formula-engine-i-learned-to-audit-as-fast-as-i-wrote-it-1k6k</guid>
      <description>&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%2Fps1q0r1jfo732i61kgpa.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%2Fps1q0r1jfo732i61kgpa.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What MySheet is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/danfma/my-sheet" rel="noopener noreferrer"&gt;MySheet&lt;/a&gt; is a spreadsheet formula engine for .NET. It loads an &lt;code&gt;.xlsx&lt;/code&gt; file, re-evaluates the formulas in memory, and hands back the results — no Excel installed, no COM automation, no shelling out.&lt;/p&gt;

&lt;p&gt;It doesn't compete with ClosedXML, EPPlus, or NPOI — those libraries remain the right tool for manipulating an Excel file with full fidelity (MySheet's own test suite uses ClosedXML as an independent oracle). MySheet solves a narrower problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You keep an Excel workbook on a server as the source of truth for a calculation. You need to load it, re-evaluate it with fresh inputs, and expose or write back the results — repeatedly, with low overhead, and without Excel installed anywhere.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sheet1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExpressionParser&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="s"&gt;"=SUM(A1:A2)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ComputedValue&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCellValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sheet1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"A3"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDouble&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 3.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;304 of Excel's ~520 catalogued functions are implemented. Not all 520 — and the README is upfront about it. Cell styling and number formatting are deliberately out of scope: that's not the problem this project set out to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first number that caught my eye: velocity
&lt;/h2&gt;

&lt;p&gt;I went back through the changelog to write this post and noticed something I hadn't consciously registered: between v0.1.0 and v3.15.0, &lt;strong&gt;19 straight days&lt;/strong&gt; passed and &lt;strong&gt;53 releases&lt;/strong&gt; shipped — each one with a changelog note generated from conventional commits.&lt;/p&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%2Fz7od1six6x1rnnxt7bwo.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%2Fz7od1six6x1rnnxt7bwo.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That number alone doesn't say much good — "fast" by itself isn't a virtue in engineering, it's just a cheaper way to accumulate technical debt if it isn't backed by some form of proof. What separates this from accelerated vibe-coding is what happened in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two reorganizations that would break any API — backed by proof, not "trust me"
&lt;/h2&gt;

&lt;p&gt;v2.0 reorganized ~190 public types into category namespaces (&lt;code&gt;Danfma.MySheet.Expressions.Logical&lt;/code&gt;, &lt;code&gt;.Mathematics&lt;/code&gt;, &lt;code&gt;.Text&lt;/code&gt;, and so on) — a 100% compile-time break, zero behavior change. v3.0 encapsulated the &lt;code&gt;Sheet&lt;/code&gt;'s cell storage behind a single write choke point, laying the groundwork for the reverse dependency graph that came later.&lt;/p&gt;

&lt;p&gt;Both shipped with a full migration guide (a type → namespace map, before/after &lt;code&gt;using&lt;/code&gt; examples) — and both are covered by the same regression test: a &lt;code&gt;.msgpack&lt;/code&gt; binary file saved by version 1.x is loaded and re-evaluated &lt;strong&gt;on every build&lt;/strong&gt;, proving the serialization format stays byte-identical three major versions later. That's not a claim in the README — it's a test that breaks CI if someone gets it wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2.x&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// direct mutation of the public dictionary&lt;/span&gt;

&lt;span class="c1"&gt;// 3.0 — Cells became an IReadOnlyDictionary; writes go through one path&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NumberValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// delegates to the SetCell choke point&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The July audit: four reports became a phased plan, with before/after numbers
&lt;/h2&gt;

&lt;p&gt;Instead of "we found and fixed some bugs," the repository documents a formal audit from 2026-07-10: four technical reports synthesized into a phased fix plan, each phase shipped as a partial release through a CI workflow, with an explicit author rule — &lt;strong&gt;no breaking changes&lt;/strong&gt;; any candidate for an API break gets pushed to the end and evaluated separately.&lt;/p&gt;

&lt;p&gt;Two performance findings, each with a counterfactual benchmark (run without the fix, run with the fix, compare):&lt;/p&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%2F1fe9edst0jx2skkupcju.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%2F1fe9edst0jx2skkupcju.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RANK.EQ&lt;/code&gt;/&lt;code&gt;RANK.AVG&lt;/code&gt; used to re-scan the entire range for every single cell — O(n²) on a 5,000-row column. They became two binary searches over the range snapshot's sorted view. The "20 to 40x faster" claim isn't left hanging: the BEFORE benchmark was reverted, run, measured (740ms), then compared against the fix (25ms) — same column, same process.&lt;/p&gt;

&lt;p&gt;Recompiling a &lt;code&gt;Regex&lt;/code&gt; on every evaluation (in &lt;code&gt;REGEXTEST&lt;/code&gt;, in &lt;code&gt;SEARCH&lt;/code&gt;'s wildcard matching, in the static &lt;code&gt;Criteria.WildcardMatch&lt;/code&gt;) became a bounded static cache with a 256-entry cap. A security bonus turned up along the way: &lt;code&gt;Criteria&lt;/code&gt;/&lt;code&gt;WildcardMatch&lt;/code&gt; had no timeout — a real ReDoS with chained wildcards — now it fails safe as "no match" instead of hanging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than the speed itself
&lt;/h2&gt;

&lt;p&gt;Nobody here wrote "trust me, it works." Every format change ships with a test that loads the old binary. Every optimization ships with a before/after benchmark right in the text of the fix. This isn't about AI writing code fast — it's about a process that accepts being audited at the same speed it ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on how this was built
&lt;/h2&gt;

&lt;p&gt;MySheet was built almost entirely with &lt;strong&gt;Claude — Opus and Sonnet&lt;/strong&gt; — including most of the July audit, delegated to subagents phase by phase, reviewed and committed by an "orchestrator" (which is just me, following the process I described above). The changelog, the plans, and the regression tests are what was left over afterward — and, without my planning it, they turned out to be the raw material for this post.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;MySheet is open source. Full code, complete changelog, and the list of all 304 functions: &lt;a href="https://github.com/danfma/my-sheet" rel="noopener noreferrer"&gt;github.com/danfma/my-sheet&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Write your domain once in C#, ship idiomatic TypeScript with Metano</title>
      <dc:creator>Daniel Ferreira Monteiro Alves</dc:creator>
      <pubDate>Mon, 01 Jun 2026 17:05:58 +0000</pubDate>
      <link>https://dev.to/danfma/write-your-domain-once-in-c-ship-idiomatic-typescript-with-metano-2m6e</link>
      <guid>https://dev.to/danfma/write-your-domain-once-in-c-ship-idiomatic-typescript-with-metano-2m6e</guid>
      <description>&lt;p&gt;If you build a full-stack product with a .NET backend and a TypeScript frontend, you have almost certainly described the same domain twice. The &lt;code&gt;User&lt;/code&gt; entity, the &lt;code&gt;Order&lt;/code&gt; status enum, the small validation rule, the JSON contract: written once in C#, then mirrored by hand in TypeScript. The mirror drifts. Someone adds a field on one side and forgets the other. A bug ships.&lt;/p&gt;

&lt;p&gt;I got tired of maintaining that second copy, so I built &lt;strong&gt;Metano&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Metano is a Roslyn-powered transpiler. You keep your domain in C#, as the single source of truth, and Metano generates real TypeScript from it. Not declaration stubs, not just type shapes: actual behavior. Records, methods, operators, guards, LINQ, async, exceptions, and JSON serializer contracts come across as idiomatic TypeScript that fits the tooling you already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use what already exists
&lt;/h2&gt;

&lt;p&gt;A few approaches get close, and each one stops short of what I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hand-written type mirrors.&lt;/strong&gt; The honest baseline, and the one that drifts. Every change is two changes, and the compiler on neither side knows about the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAPI / NSwag codegen.&lt;/strong&gt; Great for client SDKs and DTO shapes, but it generates data shapes, not behavior. Your &lt;code&gt;ToggleCompleted()&lt;/code&gt; method does not come along.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazor WebAssembly.&lt;/strong&gt; This ships the .NET runtime into the browser. It is .NET running on the client, not idiomatic TypeScript your frontend team reads and owns, and the runtime weight is real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fable.&lt;/strong&gt; Excellent project, but it is built around F#. If your backend and your team live in C#, that is a language switch, not a code-sharing strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Metano sits in the gap: C# in, idiomatic TypeScript out, with no .NET runtime in the browser and only small helper imports when the emitted code actually needs them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show, do not tell
&lt;/h2&gt;

&lt;p&gt;Here is a record written in C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Metano.Annotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TranspileAssembly&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;EmitPackage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sample-todo"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SampleTodo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StringEnum&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Medium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;High&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Completed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Medium&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TodoItem&lt;/span&gt; &lt;span class="nf"&gt;ToggleCompleted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Completed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;Completed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TodoItem&lt;/span&gt; &lt;span class="nf"&gt;SetPriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="n"&gt;Completed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"x"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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;And here is what Metano generates:&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;High&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&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;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;title&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="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&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="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;toggleCompleted&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setPriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&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="nx"&gt;priority&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;toString&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&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="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;other&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TodoItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The C# &lt;code&gt;record&lt;/code&gt; keeps its value semantics on the TypeScript side. You get structural &lt;code&gt;equals&lt;/code&gt;, a non-destructive &lt;code&gt;with&lt;/code&gt;, and the string enum rendered as a const object plus a union type, which is the idiom most TS codebases already reach for. No decorators to wire up, no runtime to load, just code your bundler treats like any other module.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Metano is built on Roslyn, so it understands your C# semantically rather than scraping syntax. The pipeline parses your project, lowers it into a shared intermediate representation, and then emits target code from that IR.&lt;/p&gt;

&lt;p&gt;TypeScript is the production target today. There is also an experimental Dart/Flutter backend riding the same IR, which exists mostly to validate that the architecture is genuinely multi-target rather than a TypeScript-shaped special case. Keeping the IR honest is what makes new backends tractable later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it handles today
&lt;/h2&gt;

&lt;p&gt;More than I expected to reach when I started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C# records, classes, structs, interfaces, delegates, inheritance, generics, overloads, extension methods, nullable types, async, exceptions, pattern matching, and nested types.&lt;/li&gt;
&lt;li&gt;BCL mappings for collections, LINQ, &lt;code&gt;decimal&lt;/code&gt;, &lt;code&gt;Guid&lt;/code&gt;, temporal types, &lt;code&gt;BigInteger&lt;/code&gt;, tasks, strings, and math.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;System.Text.Json&lt;/code&gt; source-generation metadata is emitted as a TypeScript &lt;code&gt;SerializerContext&lt;/code&gt;, so your serialization contracts stay in sync too.&lt;/li&gt;
&lt;li&gt;Output controls via attributes when you want to shape the emitted code: &lt;code&gt;[StringEnum]&lt;/code&gt;, &lt;code&gt;[PlainObject]&lt;/code&gt;, &lt;code&gt;[Branded]&lt;/code&gt;, &lt;code&gt;[GenerateGuard]&lt;/code&gt;, &lt;code&gt;[ObjectArgs]&lt;/code&gt;, &lt;code&gt;[Optional]&lt;/code&gt;, &lt;code&gt;[Import]&lt;/code&gt;, and more.&lt;/li&gt;
&lt;li&gt;Cross-project package generation with &lt;code&gt;[EmitPackage]&lt;/code&gt;, generated barrels, and package-aware imports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated TypeScript works with Bun, Vite, Vitest, Biome, ESLint, bundlers, and source maps. It behaves like code a person wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest scope
&lt;/h2&gt;

&lt;p&gt;Metano is young and moving quickly. TypeScript is the mature, production path; the Dart backend is intentionally documented as experimental while its coverage grows. It is built for sharing domain code and logic, not for porting an entire app or replacing your UI framework, although the repo does include a set of counter samples that explore component and MVU-style patterns, including SolidJS interop, if you want to see how far the idea stretches.&lt;/p&gt;

&lt;p&gt;If you are looking for "write C# and never touch the frontend again," that is not what this is. If you are looking for "stop maintaining a second copy of my domain," that is exactly this.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Metano
dotnet add package Metano.Build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your C# project at a generated TypeScript package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MetanoOutputDir&amp;gt;&lt;/span&gt;../my-domain-ts/src&lt;span class="nt"&gt;&amp;lt;/MetanoOutputDir&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MetanoClean&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/MetanoClean&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;dotnet build&lt;/code&gt;. There is also a global tool if you prefer manual runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; Metano.Compiler.TypeScript
dotnet metano-typescript &lt;span class="nt"&gt;-p&lt;/span&gt; path/to/YourProject.csproj &lt;span class="nt"&gt;-o&lt;/span&gt; path/to/output/src &lt;span class="nt"&gt;--clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/danfma/metano" rel="noopener noreferrer"&gt;https://github.com/danfma/metano&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/Metano" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/Metano&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm runtime: &lt;a href="https://www.npmjs.com/package/metano-runtime" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/metano-runtime&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;I will continue to evolve Metano by exploring more use cases and making it more complete, and along the way, I'm evaluating other approaches like JSX generation.&lt;/p&gt;

&lt;p&gt;The roadmap I am most curious about is a WebAssembly backend on the same IR, using the modern WasmGC proposal so the output stays lean instead of shipping a runtime. That raises a genuinely interesting compiler problem: how to lower C# value types onto WasmGC's reference-typed structs while preserving copy semantics. I will write that one up separately. Also, evaluate whether using WasmGC will allow me to transfer data between Wasm and JS with minimal friction, which opens up space for more interesting prototypes.&lt;/p&gt;

&lt;p&gt;If Metano is useful to you, or if it breaks in an interesting way, I would love your feedback. Issues and stars both help me prioritize.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
