<?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: Tosiiko</title>
    <description>The latest articles on DEV Community by Tosiiko (@tosiiko).</description>
    <link>https://dev.to/tosiiko</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%2F3959814%2Fa5789435-8504-4051-b206-0620d67a1c58.png</url>
      <title>DEV Community: Tosiiko</title>
      <link>https://dev.to/tosiiko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tosiiko"/>
    <language>en</language>
    <item>
      <title>Wetask Live HTTP Benchmark</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Mon, 03 Aug 2026 01:08:45 +0000</pubDate>
      <link>https://dev.to/tosiiko/wetask-live-http-benchmark-2nl0</link>
      <guid>https://dev.to/tosiiko/wetask-live-http-benchmark-2nl0</guid>
      <description>&lt;h1&gt;
  
  
  Phase 9 Live HTTP Benchmark
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Started: 2026-08-03T01:01:34.819Z&lt;/li&gt;
&lt;li&gt;Finished: 2026-08-03T01:01:46.304Z&lt;/li&gt;
&lt;li&gt;Base URL: &lt;code&gt;http://127.0.0.1:18081&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run ID: &lt;code&gt;phase9-live-2026-08-03T01-01-34-817Z-c5cc02&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Output directory: &lt;code&gt;/Users/ocenwalteronen/Documents/APPS/wetask/benchmarks/phase9-live/dashboard-complete-20260803&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Host: darwin 24.6.0 arm64, 8 CPUs&lt;/li&gt;
&lt;li&gt;Workloads: &lt;code&gt;health&lt;/code&gt;, &lt;code&gt;task-burst&lt;/code&gt;, &lt;code&gt;task-batch&lt;/code&gt;, &lt;code&gt;task-closed-loop&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;, &lt;code&gt;scheduler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Snapshot scope: &lt;code&gt;full&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workloads
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Ops&lt;/th&gt;
&lt;th&gt;OK&lt;/th&gt;
&lt;th&gt;Errors&lt;/th&gt;
&lt;th&gt;Throughput/s&lt;/th&gt;
&lt;th&gt;p50 ms&lt;/th&gt;
&lt;th&gt;p95 ms&lt;/th&gt;
&lt;th&gt;p99 ms&lt;/th&gt;
&lt;th&gt;max ms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;health_get&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;891.743&lt;/td&gt;
&lt;td&gt;26.123&lt;/td&gt;
&lt;td&gt;61.586&lt;/td&gt;
&lt;td&gt;63.670&lt;/td&gt;
&lt;td&gt;64.805&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task_submit_burst&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;324.013&lt;/td&gt;
&lt;td&gt;87.172&lt;/td&gt;
&lt;td&gt;99.532&lt;/td&gt;
&lt;td&gt;100.574&lt;/td&gt;
&lt;td&gt;100.574&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task_submit_burst_completion&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;397.503&lt;/td&gt;
&lt;td&gt;60.379&lt;/td&gt;
&lt;td&gt;100.605&lt;/td&gt;
&lt;td&gt;102.872&lt;/td&gt;
&lt;td&gt;102.872&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task_submit_batch&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5.294&lt;/td&gt;
&lt;td&gt;752.396&lt;/td&gt;
&lt;td&gt;754.885&lt;/td&gt;
&lt;td&gt;754.885&lt;/td&gt;
&lt;td&gt;754.885&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task_submit_batch_completion&lt;/td&gt;
&lt;td&gt;320&lt;/td&gt;
&lt;td&gt;320&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2783.316&lt;/td&gt;
&lt;td&gt;3.077&lt;/td&gt;
&lt;td&gt;69.023&lt;/td&gt;
&lt;td&gt;86.042&lt;/td&gt;
&lt;td&gt;86.988&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task_completion_closed_loop&lt;/td&gt;
&lt;td&gt;320&lt;/td&gt;
&lt;td&gt;320&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;201.390&lt;/td&gt;
&lt;td&gt;142.041&lt;/td&gt;
&lt;td&gt;210.894&lt;/td&gt;
&lt;td&gt;212.689&lt;/td&gt;
&lt;td&gt;214.631&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache_set&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5388.441&lt;/td&gt;
&lt;td&gt;10.009&lt;/td&gt;
&lt;td&gt;20.021&lt;/td&gt;
&lt;td&gt;26.018&lt;/td&gt;
&lt;td&gt;30.497&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache_get_hit&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7997.203&lt;/td&gt;
&lt;td&gt;7.090&lt;/td&gt;
&lt;td&gt;15.211&lt;/td&gt;
&lt;td&gt;17.100&lt;/td&gt;
&lt;td&gt;19.612&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache_delete&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7244.545&lt;/td&gt;
&lt;td&gt;7.982&lt;/td&gt;
&lt;td&gt;17.162&lt;/td&gt;
&lt;td&gt;19.522&lt;/td&gt;
&lt;td&gt;22.204&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler_interval_probe&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.611&lt;/td&gt;
&lt;td&gt;9.186&lt;/td&gt;
&lt;td&gt;13.252&lt;/td&gt;
&lt;td&gt;13.252&lt;/td&gt;
&lt;td&gt;13.252&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fylz9ve2sxtrnve7lgcwi.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%2Fylz9ve2sxtrnve7lgcwi.png" alt="Throughput by workload" width="800" height="295"&gt;&lt;/a&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%2Ffqxgmq121ple8qcxac4w.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%2Ffqxgmq121ple8qcxac4w.png" alt="p95 latency by workload" width="799" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No workload-level errors were observed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scheduler Probe
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Expression: &lt;code&gt;1s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Wait: 6500 ms&lt;/li&gt;
&lt;li&gt;Created: true&lt;/li&gt;
&lt;li&gt;Deleted: true&lt;/li&gt;
&lt;li&gt;History rows observed: 6&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Isolation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Isolation: cache scope samples only &lt;code&gt;/health&lt;/code&gt;, &lt;code&gt;/metrics&lt;/code&gt;, &lt;code&gt;/api/v1/cache/stats&lt;/code&gt;, and cache operation endpoints.&lt;/li&gt;
&lt;li&gt;Lifecycle/admin endpoints such as &lt;code&gt;/api/v1/node/drain&lt;/code&gt; and &lt;code&gt;/api/v1/cluster/bootstrap&lt;/code&gt; are not benchmark workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Snapshots
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Health before: healthy&lt;/li&gt;
&lt;li&gt;Health after: healthy&lt;/li&gt;
&lt;li&gt;Cache entries after: 0&lt;/li&gt;
&lt;li&gt;Metrics files: &lt;code&gt;metrics-before.txt&lt;/code&gt;, &lt;code&gt;metrics-after.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Summary charts: &lt;code&gt;throughput.svg&lt;/code&gt;, &lt;code&gt;latency-p95.svg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Raw per-operation records are stored as JSONL files next to this summary.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Wetask Baseline Benchmarks</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Mon, 03 Aug 2026 01:08:06 +0000</pubDate>
      <link>https://dev.to/tosiiko/wetask-baseline-benchmarks-f8</link>
      <guid>https://dev.to/tosiiko/wetask-baseline-benchmarks-f8</guid>
      <description>&lt;h1&gt;
  
  
  Phase 9 Baseline Benchmarks
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Generated: 2026-08-03T01:01:18.903Z&lt;/li&gt;
&lt;li&gt;Artifact directory: &lt;code&gt;/Users/ocenwalteronen/Documents/APPS/wetask/benchmarks/phase9/dashboard-complete-20260803&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Raw files: &lt;code&gt;60&lt;/code&gt; Go benchmark rows, &lt;code&gt;0&lt;/code&gt; probe rows, &lt;code&gt;0&lt;/code&gt; skipped probe rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Go Benchmarks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Samples&lt;/th&gt;
&lt;th&gt;Iterations&lt;/th&gt;
&lt;th&gt;median ns/op&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;median B/op&lt;/th&gt;
&lt;th&gt;median allocs/op&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cache-replication.txt&lt;/td&gt;
&lt;td&gt;BenchmarkClusterCacheReplicationLatency-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;203702&lt;/td&gt;
&lt;td&gt;9,923&lt;/td&gt;
&lt;td&gt;100,775.975&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;1,366&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.txt&lt;/td&gt;
&lt;td&gt;BenchmarkMemoryCacheSet-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4543238&lt;/td&gt;
&lt;td&gt;526.3&lt;/td&gt;
&lt;td&gt;1,900,057.002&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.txt&lt;/td&gt;
&lt;td&gt;BenchmarkMemoryCacheGetHit-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11721363&lt;/td&gt;
&lt;td&gt;206.1&lt;/td&gt;
&lt;td&gt;4,852,013.586&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.txt&lt;/td&gt;
&lt;td&gt;BenchmarkMemoryCacheGetHitParallel-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3237530&lt;/td&gt;
&lt;td&gt;754.3&lt;/td&gt;
&lt;td&gt;1,325,732.467&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.txt&lt;/td&gt;
&lt;td&gt;BenchmarkMemoryCacheGetMiss-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;9804075&lt;/td&gt;
&lt;td&gt;246.5&lt;/td&gt;
&lt;td&gt;4,056,795.132&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.txt&lt;/td&gt;
&lt;td&gt;BenchmarkMemoryCacheDelete-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3477036&lt;/td&gt;
&lt;td&gt;695.4&lt;/td&gt;
&lt;td&gt;1,438,021.283&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;221&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache.txt&lt;/td&gt;
&lt;td&gt;BenchmarkMemoryCacheMixedGetSetDelete-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2619100&lt;/td&gt;
&lt;td&gt;912.2&lt;/td&gt;
&lt;td&gt;1,096,250.822&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;consensus-commit.txt&lt;/td&gt;
&lt;td&gt;BenchmarkConsensusCommitLatency-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;444&lt;/td&gt;
&lt;td&gt;4,925,070&lt;/td&gt;
&lt;td&gt;203.043&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;38,371&lt;/td&gt;
&lt;td&gt;129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;consensus-commit.txt&lt;/td&gt;
&lt;td&gt;BenchmarkConsensusCommitBatch/commands_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;465&lt;/td&gt;
&lt;td&gt;4,921,804&lt;/td&gt;
&lt;td&gt;1,625&lt;/td&gt;
&lt;td&gt;commands/s&lt;/td&gt;
&lt;td&gt;36,157&lt;/td&gt;
&lt;td&gt;248&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;consensus-commit.txt&lt;/td&gt;
&lt;td&gt;BenchmarkConsensusCommitBatch/commands_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;536&lt;/td&gt;
&lt;td&gt;4,846,600&lt;/td&gt;
&lt;td&gt;6,603&lt;/td&gt;
&lt;td&gt;commands/s&lt;/td&gt;
&lt;td&gt;71,038&lt;/td&gt;
&lt;td&gt;615&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;consensus-failover-rolling.txt&lt;/td&gt;
&lt;td&gt;BenchmarkConsensusFailoverTime-8&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;232,251,542&lt;/td&gt;
&lt;td&gt;4.306&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;1,350,348&lt;/td&gt;
&lt;td&gt;1,439&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;consensus-failover-rolling.txt&lt;/td&gt;
&lt;td&gt;BenchmarkConsensusRollingRestartImpact-8&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;652,977,500&lt;/td&gt;
&lt;td&gt;1.535&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;7,163,636&lt;/td&gt;
&lt;td&gt;8,251&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkFastCacheUnaryGet-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4617416&lt;/td&gt;
&lt;td&gt;519.5&lt;/td&gt;
&lt;td&gt;1,924,927.815&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;152&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkFastCacheUnarySet-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1453498&lt;/td&gt;
&lt;td&gt;1,442&lt;/td&gt;
&lt;td&gt;693,481.276&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;508&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkFastCachePipeline/ops_10-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;562264&lt;/td&gt;
&lt;td&gt;4,291&lt;/td&gt;
&lt;td&gt;233,045.91&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;2,680&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkFastCachePipeline/ops_100-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;63175&lt;/td&gt;
&lt;td&gt;38,011&lt;/td&gt;
&lt;td&gt;26,308.174&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;24,104&lt;/td&gt;
&lt;td&gt;340&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkFastCachePipeline/ops_1000-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6235&lt;/td&gt;
&lt;td&gt;376,645&lt;/td&gt;
&lt;td&gt;2,655.02&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;237,992&lt;/td&gt;
&lt;td&gt;3,340&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerLegacyGRPC/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;35274&lt;/td&gt;
&lt;td&gt;67,718&lt;/td&gt;
&lt;td&gt;14,767&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;31,789&lt;/td&gt;
&lt;td&gt;455&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerLegacyGRPC/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;16095&lt;/td&gt;
&lt;td&gt;145,971&lt;/td&gt;
&lt;td&gt;54,805&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;125,030&lt;/td&gt;
&lt;td&gt;1,404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerLegacyGRPC/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5775&lt;/td&gt;
&lt;td&gt;419,392&lt;/td&gt;
&lt;td&gt;76,301&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;450,833&lt;/td&gt;
&lt;td&gt;4,656&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerPipelineGRPC/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;54372&lt;/td&gt;
&lt;td&gt;42,169&lt;/td&gt;
&lt;td&gt;23,714&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;24,564&lt;/td&gt;
&lt;td&gt;323&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerPipelineGRPC/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;18870&lt;/td&gt;
&lt;td&gt;127,845&lt;/td&gt;
&lt;td&gt;62,576&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;116,837&lt;/td&gt;
&lt;td&gt;1,282&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerPipelineGRPC/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5971&lt;/td&gt;
&lt;td&gt;410,559&lt;/td&gt;
&lt;td&gt;77,943&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;439,346&lt;/td&gt;
&lt;td&gt;4,580&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerLegacyGRPCDurable/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;186&lt;/td&gt;
&lt;td&gt;12,503,043&lt;/td&gt;
&lt;td&gt;79.98&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;42,371&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerLegacyGRPCDurable/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;172&lt;/td&gt;
&lt;td&gt;13,340,744&lt;/td&gt;
&lt;td&gt;599.7&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;237,052&lt;/td&gt;
&lt;td&gt;1,841&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerLegacyGRPCDurable/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;151&lt;/td&gt;
&lt;td&gt;14,916,973&lt;/td&gt;
&lt;td&gt;2,145&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;916,063&lt;/td&gt;
&lt;td&gt;6,371&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerPipelineGRPCDurable/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;274&lt;/td&gt;
&lt;td&gt;8,359,229&lt;/td&gt;
&lt;td&gt;119.6&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;37,896&lt;/td&gt;
&lt;td&gt;378&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerPipelineGRPCDurable/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;252&lt;/td&gt;
&lt;td&gt;8,999,789&lt;/td&gt;
&lt;td&gt;888.9&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;240,558&lt;/td&gt;
&lt;td&gt;1,714&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerPipelineGRPCDurable/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;232&lt;/td&gt;
&lt;td&gt;10,140,161&lt;/td&gt;
&lt;td&gt;3,156&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;941,069&lt;/td&gt;
&lt;td&gt;6,288&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;node-resources.txt&lt;/td&gt;
&lt;td&gt;BenchmarkNodeWorkloadResourceSnapshot-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;100567&lt;/td&gt;
&lt;td&gt;23,975&lt;/td&gt;
&lt;td&gt;41,710.115&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerCreateDeleteThroughput-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1882146&lt;/td&gt;
&lt;td&gt;1,310&lt;/td&gt;
&lt;td&gt;763,358.779&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;626&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerDueScanThroughput/schedules_100-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;267440&lt;/td&gt;
&lt;td&gt;8,994&lt;/td&gt;
&lt;td&gt;111,185.235&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerDueScanThroughput/schedules_1000-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;25530&lt;/td&gt;
&lt;td&gt;97,555&lt;/td&gt;
&lt;td&gt;10,250.628&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerDueScanThroughput/schedules_10000-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2628&lt;/td&gt;
&lt;td&gt;913,044&lt;/td&gt;
&lt;td&gt;1,095.237&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerNextRunPrecision/cron_every_minute-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1425688&lt;/td&gt;
&lt;td&gt;1,650&lt;/td&gt;
&lt;td&gt;606,060.606&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;528&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerNextRunPrecision/interval_one_second-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;61358515&lt;/td&gt;
&lt;td&gt;39.21&lt;/td&gt;
&lt;td&gt;25,503,698.036&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduler.txt&lt;/td&gt;
&lt;td&gt;BenchmarkSchedulerExternalWorkerHandoff-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;122958&lt;/td&gt;
&lt;td&gt;21,636&lt;/td&gt;
&lt;td&gt;46,219&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;17,848&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task.txt&lt;/td&gt;
&lt;td&gt;BenchmarkTaskSubmitThroughput-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4456720&lt;/td&gt;
&lt;td&gt;503.9&lt;/td&gt;
&lt;td&gt;1,984,520.738&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;813&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task.txt&lt;/td&gt;
&lt;td&gt;BenchmarkTaskCompletionThroughput-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;647378&lt;/td&gt;
&lt;td&gt;3,560&lt;/td&gt;
&lt;td&gt;280,898.876&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;2,676&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task.txt&lt;/td&gt;
&lt;td&gt;BenchmarkTaskLatencyUnderQueuePressure/depth_0-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5920476&lt;/td&gt;
&lt;td&gt;408&lt;/td&gt;
&lt;td&gt;2,450,980.392&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;679&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task.txt&lt;/td&gt;
&lt;td&gt;BenchmarkTaskLatencyUnderQueuePressure/depth_100-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3600847&lt;/td&gt;
&lt;td&gt;662.3&lt;/td&gt;
&lt;td&gt;1,509,889.778&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;679&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task.txt&lt;/td&gt;
&lt;td&gt;BenchmarkTaskLatencyUnderQueuePressure/depth_1000-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1752414&lt;/td&gt;
&lt;td&gt;1,396&lt;/td&gt;
&lt;td&gt;716,332.378&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;679&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task.txt&lt;/td&gt;
&lt;td&gt;BenchmarkTaskLatencyUnderQueuePressure/depth_10000-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;205513&lt;/td&gt;
&lt;td&gt;11,938&lt;/td&gt;
&lt;td&gt;83,766.125&lt;/td&gt;
&lt;td&gt;ops/s&lt;/td&gt;
&lt;td&gt;676&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAck/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;175946&lt;/td&gt;
&lt;td&gt;13,530&lt;/td&gt;
&lt;td&gt;73,909&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;14,151&lt;/td&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAck/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;25180&lt;/td&gt;
&lt;td&gt;96,016&lt;/td&gt;
&lt;td&gt;83,320&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;111,630&lt;/td&gt;
&lt;td&gt;975&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAck/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6094&lt;/td&gt;
&lt;td&gt;389,568&lt;/td&gt;
&lt;td&gt;82,142&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;448,409&lt;/td&gt;
&lt;td&gt;3,838&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAckParallel-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;140721&lt;/td&gt;
&lt;td&gt;16,821&lt;/td&gt;
&lt;td&gt;59,449&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;14,529&lt;/td&gt;
&lt;td&gt;139&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAckDurable/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;196&lt;/td&gt;
&lt;td&gt;11,486,791&lt;/td&gt;
&lt;td&gt;87.06&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;25,686&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAckDurable/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;184&lt;/td&gt;
&lt;td&gt;12,107,070&lt;/td&gt;
&lt;td&gt;660.8&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;233,102&lt;/td&gt;
&lt;td&gt;1,484&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAckDurable/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;159&lt;/td&gt;
&lt;td&gt;15,315,457&lt;/td&gt;
&lt;td&gt;2,089&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;951,413&lt;/td&gt;
&lt;td&gt;5,850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerClaimAckDurableParallel-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;290&lt;/td&gt;
&lt;td&gt;7,878,707&lt;/td&gt;
&lt;td&gt;126.9&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;28,756&lt;/td&gt;
&lt;td&gt;221&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaim/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;151801&lt;/td&gt;
&lt;td&gt;15,276&lt;/td&gt;
&lt;td&gt;65,463&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;14,870&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaim/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;23196&lt;/td&gt;
&lt;td&gt;106,519&lt;/td&gt;
&lt;td&gt;75,104&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;112,200&lt;/td&gt;
&lt;td&gt;990&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaim/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6026&lt;/td&gt;
&lt;td&gt;391,910&lt;/td&gt;
&lt;td&gt;81,651&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;447,857&lt;/td&gt;
&lt;td&gt;3,877&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaimMixed/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;156730&lt;/td&gt;
&lt;td&gt;15,239&lt;/td&gt;
&lt;td&gt;65,620&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;14,870&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaimMixed/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;15445&lt;/td&gt;
&lt;td&gt;153,380&lt;/td&gt;
&lt;td&gt;52,158&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;117,944&lt;/td&gt;
&lt;td&gt;1,034&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaimMixed/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5070&lt;/td&gt;
&lt;td&gt;575,041&lt;/td&gt;
&lt;td&gt;55,648&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;470,875&lt;/td&gt;
&lt;td&gt;4,053&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaimDurable/batch_1-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;292&lt;/td&gt;
&lt;td&gt;8,133,656&lt;/td&gt;
&lt;td&gt;122.9&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;29,194&lt;/td&gt;
&lt;td&gt;208&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaimDurable/batch_8-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;8,895,192&lt;/td&gt;
&lt;td&gt;899.4&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;245,102&lt;/td&gt;
&lt;td&gt;1,495&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker.txt&lt;/td&gt;
&lt;td&gt;BenchmarkWorkerSettleAndClaimDurable/batch_32-8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;211&lt;/td&gt;
&lt;td&gt;11,005,560&lt;/td&gt;
&lt;td&gt;2,908&lt;/td&gt;
&lt;td&gt;tasks/s&lt;/td&gt;
&lt;td&gt;985,610&lt;/td&gt;
&lt;td&gt;5,878&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Probe Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Operations&lt;/th&gt;
&lt;th&gt;ops/s&lt;/th&gt;
&lt;th&gt;avg ms/op&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Skipped Probes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F72cauo4xje6kty5nlxjt.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%2F72cauo4xje6kty5nlxjt.png" alt="Go benchmark latency" width="800" height="1261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Throughput uses a base-10 logarithmic axis so benchmarks spanning several&lt;br&gt;
orders of magnitude remain visually comparable. Exact values are printed next&lt;br&gt;
to every bar.&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%2F6j2uwlfjtbwtd3coqhwc.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%2F6j2uwlfjtbwtd3coqhwc.png" alt="Benchmark throughput (log scale)" width="800" height="1277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Publish this summary together with the raw &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.jsonl&lt;/code&gt;, and environment files when sharing benchmark results.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Benchmark</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Fri, 31 Jul 2026 20:37:56 +0000</pubDate>
      <link>https://dev.to/tosiiko/benchmark-26nf</link>
      <guid>https://dev.to/tosiiko/benchmark-26nf</guid>
      <description></description>
      <category>programming</category>
      <category>productivity</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>Wetask Is Opening Its Task Runtime to External Workers</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Thu, 30 Jul 2026 23:21:14 +0000</pubDate>
      <link>https://dev.to/tosiiko/wetask-is-opening-its-task-runtime-to-external-workers-1p2a</link>
      <guid>https://dev.to/tosiiko/wetask-is-opening-its-task-runtime-to-external-workers-1p2a</guid>
      <description>&lt;p&gt;Wetask's next update brings durable, fenced external workers, atomic batch settlement, and first-party Go, TypeScript, and Python clients.&lt;/p&gt;

&lt;p&gt;Wetask started with a simple goal: combine the pieces commonly assembled around&lt;br&gt;
a background job system into one Go-based runtime.&lt;/p&gt;

&lt;p&gt;That means a task queue, scheduler, cache, consensus, operational APIs, and&lt;br&gt;
observability without requiring a separate broker, result backend, and schedule&lt;br&gt;
service for every deployment.&lt;/p&gt;

&lt;p&gt;The next Wetask update takes that idea beyond embedded handlers. Applications&lt;br&gt;
will be able to run their own workers in another process, container, language,&lt;br&gt;
or execution environment while Wetask remains responsible for durable task&lt;br&gt;
state.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bring your own workers
&lt;/h2&gt;

&lt;p&gt;External workers are useful when execution cannot or should not happen inside&lt;br&gt;
the Wetask server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python workers running machine-learning jobs&lt;/li&gt;
&lt;li&gt;TypeScript workers calling application services&lt;/li&gt;
&lt;li&gt;Go workers handling high-throughput backend work&lt;/li&gt;
&lt;li&gt;GPU or memory-specialized worker pools&lt;/li&gt;
&lt;li&gt;independently deployed workers with their own release cycle&lt;/li&gt;
&lt;li&gt;workers isolated from the queue and control-plane process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first-party Go, TypeScript, and Python clients expose the worker API, while&lt;br&gt;
HTTP and gRPC remain available for custom integrations.&lt;/p&gt;

&lt;p&gt;Workers can request batches by queue and task type, renew long-running work,&lt;br&gt;
report success, or classify a failure as transient, permanent, or cancelled.&lt;br&gt;
Credentials can be limited to specific queues and task types.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fencing matters more than another Ack endpoint
&lt;/h2&gt;

&lt;p&gt;At-least-once systems must assume a task can be delivered again. A worker can&lt;br&gt;
pause, lose its connection, exceed its lease, and continue running after&lt;br&gt;
another worker has received the same task.&lt;/p&gt;

&lt;p&gt;Wetask gives every claimed attempt a one-time fencing token. Completion is&lt;br&gt;
accepted only when the task, claim, token, principal, attempt, and coordinator&lt;br&gt;
still match. Once an attempt expires or is superseded, the old worker cannot&lt;br&gt;
complete it.&lt;/p&gt;

&lt;p&gt;The token itself is returned only to the worker. Wetask stores its SHA-256 hash.&lt;/p&gt;

&lt;p&gt;This does not make external side effects exactly once. A worker that charges a&lt;br&gt;
card and crashes before acknowledging the task can still cause a retry.&lt;br&gt;
Handlers must use the task ID and attempt to make downstream operations&lt;br&gt;
idempotent.&lt;/p&gt;

&lt;p&gt;What fencing does guarantee is that a stale worker cannot overwrite the state&lt;br&gt;
of the current attempt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Durable and idempotent settlement
&lt;/h2&gt;

&lt;p&gt;Successful and failed settlements are persisted before Wetask returns their&lt;br&gt;
receipts. Retrying the same acknowledgment returns the original receipt instead&lt;br&gt;
of completing or retrying the task twice.&lt;/p&gt;

&lt;p&gt;Failure handling is task-aware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;transient&lt;/code&gt; schedules another attempt when retries remain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;permanent&lt;/code&gt; ends the task and records it in the dead-letter queue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cancelled&lt;/code&gt; ends execution without retrying&lt;/li&gt;
&lt;li&gt;lease expiration applies the configured retry and DLQ policy automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workers can acknowledge or reject a complete batch atomically. Wetask validates&lt;br&gt;
every fence first. If one item is stale or invalid, none of the unsettled items&lt;br&gt;
in that batch are changed.&lt;/p&gt;

&lt;p&gt;Batch settlement also reduces durable storage overhead by recording the batch&lt;br&gt;
with one WAL synchronization.&lt;/p&gt;
&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;We benchmarked the complete in-process lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;submit tasks&lt;/li&gt;
&lt;li&gt;claim them as an external worker&lt;/li&gt;
&lt;li&gt;acknowledge them&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The benchmark does not execute application work, and it does not include HTTP&lt;br&gt;
or gRPC network overhead. It measures the task runtime itself.&lt;/p&gt;

&lt;p&gt;Environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apple M3&lt;/li&gt;
&lt;li&gt;macOS &lt;code&gt;darwin/arm64&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go benchmark process reporting 8-way parallelism&lt;/li&gt;
&lt;li&gt;batch sizes of 1, 8, and 32&lt;/li&gt;
&lt;li&gt;three samples per scenario&lt;/li&gt;
&lt;li&gt;three-second benchmark windows&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./internal/task &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-run&lt;/span&gt; &lt;span class="s1"&gt;'^$'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-bench&lt;/span&gt; &lt;span class="s1"&gt;'^BenchmarkWorkerClaimAck($|Parallel$)'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-benchmem&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-benchtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3s &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  In-memory lifecycle
&lt;/h3&gt;

&lt;p&gt;The table reports median tasks per second:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Earlier baseline&lt;/th&gt;
&lt;th&gt;Current result&lt;/th&gt;
&lt;th&gt;Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch 1&lt;/td&gt;
&lt;td&gt;74,933&lt;/td&gt;
&lt;td&gt;77,123&lt;/td&gt;
&lt;td&gt;+2.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch 8&lt;/td&gt;
&lt;td&gt;84,434&lt;/td&gt;
&lt;td&gt;86,393&lt;/td&gt;
&lt;td&gt;+2.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch 32&lt;/td&gt;
&lt;td&gt;84,661&lt;/td&gt;
&lt;td&gt;86,783&lt;/td&gt;
&lt;td&gt;+2.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel, single-task operations&lt;/td&gt;
&lt;td&gt;64,429&lt;/td&gt;
&lt;td&gt;63,443&lt;/td&gt;
&lt;td&gt;-1.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Batch 32 completes the full lifecycle in about &lt;code&gt;11.52 microseconds&lt;/code&gt; per task.&lt;/p&gt;

&lt;p&gt;The in-memory increase is intentionally modest. There is no disk barrier to&lt;br&gt;
amortize, and every task still needs an ID, claim token, state transitions,&lt;br&gt;
queue bookkeeping, result retention, metrics, and lifecycle events. The&lt;br&gt;
parallel result is effectively flat and is included rather than hidden.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durable lifecycle
&lt;/h3&gt;

&lt;p&gt;Durable mode includes WAL synchronization for accepted submission, claim, and&lt;br&gt;
settlement:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Median throughput&lt;/th&gt;
&lt;th&gt;Relative to earlier 86.34 tasks/s baseline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch 1&lt;/td&gt;
&lt;td&gt;80.08 tasks/s&lt;/td&gt;
&lt;td&gt;0.93x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch 8&lt;/td&gt;
&lt;td&gt;645.7 tasks/s&lt;/td&gt;
&lt;td&gt;7.5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch 32&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,402 tasks/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;27.8x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel, single-task operations&lt;/td&gt;
&lt;td&gt;136.4 tasks/s&lt;/td&gt;
&lt;td&gt;1.58x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At batch 32, durable processing costs approximately &lt;code&gt;416 microseconds&lt;/code&gt; per task.&lt;/p&gt;

&lt;p&gt;The theoretical batching ceiling is 32x because the same durable barriers are&lt;br&gt;
shared by 32 tasks. The measured 27.8x improvement reaches about 87% of that&lt;br&gt;
ceiling. The remaining time covers WAL serialization, task state, metrics, and&lt;br&gt;
the disk synchronization itself.&lt;/p&gt;

&lt;p&gt;These are Wetask-only measurements, not claims that Wetask is faster than every&lt;br&gt;
broker. A fair competitive benchmark must use equivalent payloads, transports,&lt;br&gt;
batch sizes, persistence policies, replication, and acknowledgment semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Wetask compares
&lt;/h2&gt;

&lt;h3&gt;
  
  
  RabbitMQ quorum queues
&lt;/h3&gt;

&lt;p&gt;RabbitMQ quorum queues are mature, Raft-replicated queues designed for data&lt;br&gt;
safety and leader failover. Publisher confirms and manual consumer&lt;br&gt;
acknowledgments provide a strong production foundation.&lt;/p&gt;

&lt;p&gt;RabbitMQ remains ahead of Wetask in replicated queue durability, operational&lt;br&gt;
history, protocol support, and ecosystem size.&lt;/p&gt;

&lt;p&gt;Wetask aims at a more task-specific experience: results, retries, deadlines,&lt;br&gt;
cancellation, dead letters, scheduling, cache facilities, and worker fencing&lt;br&gt;
are available through one runtime and API.&lt;/p&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://www.rabbitmq.com/docs/4.2/quorum-queues" rel="noopener noreferrer"&gt;RabbitMQ quorum queues&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NATS JetStream
&lt;/h3&gt;

&lt;p&gt;JetStream is the closest protocol-level comparison. It offers pull and push&lt;br&gt;
consumers, explicit Ack, Nak, delayed Nak, Term, in-progress acknowledgments,&lt;br&gt;
AckWait, backoff, and maximum delivery counts.&lt;/p&gt;

&lt;p&gt;JetStream is significantly more mature in clustered stream storage, pub/sub,&lt;br&gt;
replication, and high-throughput messaging.&lt;/p&gt;

&lt;p&gt;Wetask's distinction is strict attempt fencing. JetStream documents that a late&lt;br&gt;
acknowledgment can be accepted after a message has already been redelivered to&lt;br&gt;
another subscriber. Wetask rejects settlement from a superseded attempt.&lt;/p&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://docs.nats.io/nats-concepts/jetstream/consumers" rel="noopener noreferrer"&gt;NATS JetStream consumers&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS SQS and Google Cloud Pub/Sub
&lt;/h3&gt;

&lt;p&gt;Managed queues remove most broker operations and provide elastic,&lt;br&gt;
multi-availability-zone infrastructure.&lt;/p&gt;

&lt;p&gt;SQS uses visibility timeouts and receipt handles. Standard queues remain&lt;br&gt;
at-least-once and consumers must tolerate duplicate delivery.&lt;/p&gt;

&lt;p&gt;Google Cloud Pub/Sub's exactly-once pull subscriptions provide a particularly&lt;br&gt;
interesting comparison: only the latest acknowledgment ID remains valid after&lt;br&gt;
redelivery. That is conceptually close to Wetask's fencing model.&lt;/p&gt;

&lt;p&gt;Wetask is for teams that need self-hosting, portability, local deployment, and&lt;br&gt;
task-specific state. Cloud queues are the stronger choice when a managed&lt;br&gt;
service and provider-operated availability are more important.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html" rel="noopener noreferrer"&gt;Amazon SQS visibility timeout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/pubsub/docs/exactly-once-delivery" rel="noopener noreferrer"&gt;Google Cloud Pub/Sub exactly-once delivery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Apache Kafka
&lt;/h3&gt;

&lt;p&gt;Kafka is an ordered, replayable event log. It is the better fit for event&lt;br&gt;
streaming, large retained histories, partition ordering, and many independent&lt;br&gt;
consumer groups.&lt;/p&gt;

&lt;p&gt;Traditional Kafka consumers commit partition offsets rather than independently&lt;br&gt;
settling arbitrary jobs. Retries for one record can affect partition progress,&lt;br&gt;
and task results, per-task leases, and dead-letter behavior normally require&lt;br&gt;
application conventions or additional topics.&lt;/p&gt;

&lt;p&gt;Wetask is the more direct model for background jobs. Kafka is the stronger&lt;br&gt;
model for durable event streams.&lt;/p&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://kafka.apache.org/41/design/design/" rel="noopener noreferrer"&gt;Kafka delivery semantics&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Celery with RabbitMQ
&lt;/h3&gt;

&lt;p&gt;Celery remains a mature choice for Python applications. It has a large&lt;br&gt;
ecosystem, familiar task decorators, retries, Canvas workflows, and years of&lt;br&gt;
operational knowledge.&lt;/p&gt;

&lt;p&gt;Its typical production shape combines Celery workers, a broker such as&lt;br&gt;
RabbitMQ, a result backend, and Celery Beat for schedules.&lt;/p&gt;

&lt;p&gt;Wetask's proposition is a smaller operational surface: one Go runtime provides&lt;br&gt;
the queue, task state, scheduler, cache, APIs, and administration, while&lt;br&gt;
external workers can still be written in Python, TypeScript, or Go.&lt;/p&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://docs.celeryq.dev/en/stable/" rel="noopener noreferrer"&gt;Celery documentation&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Temporal
&lt;/h3&gt;

&lt;p&gt;Temporal solves a larger problem than a task queue. It is designed for durable,&lt;br&gt;
multi-step workflows that resume across process failures and infrastructure&lt;br&gt;
outages.&lt;/p&gt;

&lt;p&gt;Temporal is the stronger choice for long-running business processes, sagas,&lt;br&gt;
durable timers, and workflow histories. Wetask is intentionally simpler for&lt;br&gt;
ordinary asynchronous jobs, scheduled work, and application caching.&lt;/p&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://docs.temporal.io/" rel="noopener noreferrer"&gt;Temporal documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Wetask fits
&lt;/h2&gt;

&lt;p&gt;Wetask is not trying to replace every messaging system.&lt;/p&gt;

&lt;p&gt;Choose Kafka when the log is the product. Choose Temporal when durable workflow&lt;br&gt;
orchestration is the product. Choose a managed cloud queue when outsourcing&lt;br&gt;
operations is the priority. Choose RabbitMQ or JetStream when a mature,&lt;br&gt;
replicated messaging foundation matters more than an integrated application&lt;br&gt;
runtime.&lt;/p&gt;

&lt;p&gt;Wetask is for teams that want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a compact self-hosted task platform&lt;/li&gt;
&lt;li&gt;durable background jobs&lt;/li&gt;
&lt;li&gt;workers outside the server process&lt;/li&gt;
&lt;li&gt;strict stale-worker rejection&lt;/li&gt;
&lt;li&gt;task results, retries, deadlines, and DLQ management&lt;/li&gt;
&lt;li&gt;scheduling and cache capabilities without another service stack&lt;/li&gt;
&lt;li&gt;HTTP, gRPC, Go, TypeScript, and Python integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Availability
&lt;/h2&gt;

&lt;p&gt;External workers are planned for the next Wetask alpha release. The first&lt;br&gt;
release will focus on durable single-node use and controlled production pilots.&lt;br&gt;
Multi-node coordination exists, but task queue WAL ownership is currently&lt;br&gt;
per-node; replicated task ownership and permanent-node-loss recovery remain&lt;br&gt;
future hardening work.&lt;/p&gt;

&lt;p&gt;That boundary is deliberate. The goal of the alpha is to put the complete&lt;br&gt;
worker experience into real projects, publish reproducible evidence, and let&lt;br&gt;
production feedback shape the next stage.&lt;/p&gt;

&lt;p&gt;If you are evaluating background job infrastructure, the most useful feedback&lt;br&gt;
is not "which benchmark number is largest?" It is: which failure semantics,&lt;br&gt;
operational model, and worker experience match the system you actually need to&lt;br&gt;
run?&lt;/p&gt;

</description>
      <category>go</category>
      <category>distributed</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>WeTask v0.1.0-rc.1 vs Redis benchmark report</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Fri, 24 Jul 2026 22:22:51 +0000</pubDate>
      <link>https://dev.to/tosiiko/wetask-v010-rc1-vs-redis-benchmark-report-5bmg</link>
      <guid>https://dev.to/tosiiko/wetask-v010-rc1-vs-redis-benchmark-report-5bmg</guid>
      <description>&lt;p&gt;Run date: 2026-07-25 (Africa/Kampala)&lt;/p&gt;

&lt;h2&gt;
  
  
  Outcome
&lt;/h2&gt;

&lt;p&gt;All recorded benchmark cells completed with zero operation errors. Redis was&lt;br&gt;
faster than WeTask on comparable network cache operations and on the controlled&lt;br&gt;
mixed pipeline matrix. WeTask pipeline throughput improved substantially as the&lt;br&gt;
pipeline size increased, reaching a three-trial median of about 402k operations&lt;br&gt;
per second for 1,000-operation pipelines at concurrency 16; Redis reached about&lt;br&gt;
886k operations per second in the same cell.&lt;/p&gt;

&lt;p&gt;Queue-ingress numbers are not equivalence claims. WeTask &lt;code&gt;SubmitTask&lt;/code&gt; enters its&lt;br&gt;
task lifecycle and the release's fallback handler executes the task. Redis&lt;br&gt;
&lt;code&gt;LPUSH&lt;/code&gt; only appends bytes to a non-persistent list. The repository's Celery&lt;br&gt;
probe also measures producer enqueue only, with no worker, acknowledgement,&lt;br&gt;
retry, or result measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Host: MacBook Air, Apple M3, 8 host cores, 16 GiB RAM&lt;/li&gt;
&lt;li&gt;OS: macOS 15.7.3, Darwin 24.6.0, arm64&lt;/li&gt;
&lt;li&gt;Docker Desktop: 4.66.1; Docker Engine 29.3.1, Linux arm64&lt;/li&gt;
&lt;li&gt;Docker allocation: 4 CPUs, 8,573,693,952 bytes RAM&lt;/li&gt;
&lt;li&gt;WeTask network target: published &lt;code&gt;wetask-io/wetask:v0.1.0-rc.1&lt;/code&gt;
(&lt;code&gt;820859d&lt;/code&gt;, built 2026-07-11), standalone, consensus disabled&lt;/li&gt;
&lt;li&gt;WeTask source benchmarks: worktree commit &lt;code&gt;d6131019163f31c94022eafc65cf318ce163ba73&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Redis: 7.4.9, standalone, &lt;code&gt;save ""&lt;/code&gt;, &lt;code&gt;appendonly no&lt;/code&gt;, no memory limit,
&lt;code&gt;noeviction&lt;/code&gt;, one I/O thread&lt;/li&gt;
&lt;li&gt;Container limits: neither container had a per-container CPU or memory limit&lt;/li&gt;
&lt;li&gt;Clients: Go 1.26.5, &lt;code&gt;go-redis/v9&lt;/code&gt; 9.12.1; Python 3.14.4,
redis-py 6.4.0, Celery 5.6.3; Node 25.4.0&lt;/li&gt;
&lt;li&gt;Network: one Go process on the macOS host to both containers over
Docker-published loopback TCP ports&lt;/li&gt;
&lt;li&gt;Execution: workloads were run serially to avoid intentional cross-load&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Controlled network cache results
&lt;/h2&gt;

&lt;p&gt;Each unary cell used 1,000 measured operations per worker after 25 unmeasured&lt;br&gt;
warm-up calls per worker. Workers reused one distinct hot key each with a&lt;br&gt;
60-second TTL.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Payload&lt;/th&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;WeTask ops/s&lt;/th&gt;
&lt;th&gt;Redis ops/s&lt;/th&gt;
&lt;th&gt;Redis/WeTask&lt;/th&gt;
&lt;th&gt;WeTask p95&lt;/th&gt;
&lt;th&gt;Redis p95&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;64 B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;1,621&lt;/td&gt;
&lt;td&gt;3,532&lt;/td&gt;
&lt;td&gt;2.18x&lt;/td&gt;
&lt;td&gt;1.38 ms&lt;/td&gt;
&lt;td&gt;0.31 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;GET hit&lt;/td&gt;
&lt;td&gt;1,916&lt;/td&gt;
&lt;td&gt;3,498&lt;/td&gt;
&lt;td&gt;1.83x&lt;/td&gt;
&lt;td&gt;1.15 ms&lt;/td&gt;
&lt;td&gt;0.31 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 B&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;6,956&lt;/td&gt;
&lt;td&gt;20,176&lt;/td&gt;
&lt;td&gt;2.90x&lt;/td&gt;
&lt;td&gt;4.18 ms&lt;/td&gt;
&lt;td&gt;1.54 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 B&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;GET hit&lt;/td&gt;
&lt;td&gt;6,412&lt;/td&gt;
&lt;td&gt;18,853&lt;/td&gt;
&lt;td&gt;2.94x&lt;/td&gt;
&lt;td&gt;4.63 ms&lt;/td&gt;
&lt;td&gt;1.52 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;9,581&lt;/td&gt;
&lt;td&gt;30,422&lt;/td&gt;
&lt;td&gt;3.18x&lt;/td&gt;
&lt;td&gt;13.48 ms&lt;/td&gt;
&lt;td&gt;3.55 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;GET hit&lt;/td&gt;
&lt;td&gt;9,294&lt;/td&gt;
&lt;td&gt;32,345&lt;/td&gt;
&lt;td&gt;3.48x&lt;/td&gt;
&lt;td&gt;14.23 ms&lt;/td&gt;
&lt;td&gt;3.20 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024 B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;1,672&lt;/td&gt;
&lt;td&gt;3,288&lt;/td&gt;
&lt;td&gt;1.97x&lt;/td&gt;
&lt;td&gt;1.34 ms&lt;/td&gt;
&lt;td&gt;0.34 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024 B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;GET hit&lt;/td&gt;
&lt;td&gt;1,924&lt;/td&gt;
&lt;td&gt;3,444&lt;/td&gt;
&lt;td&gt;1.79x&lt;/td&gt;
&lt;td&gt;1.18 ms&lt;/td&gt;
&lt;td&gt;0.32 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024 B&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;6,866&lt;/td&gt;
&lt;td&gt;18,861&lt;/td&gt;
&lt;td&gt;2.75x&lt;/td&gt;
&lt;td&gt;4.46 ms&lt;/td&gt;
&lt;td&gt;1.61 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024 B&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;GET hit&lt;/td&gt;
&lt;td&gt;6,582&lt;/td&gt;
&lt;td&gt;17,761&lt;/td&gt;
&lt;td&gt;2.70x&lt;/td&gt;
&lt;td&gt;4.91 ms&lt;/td&gt;
&lt;td&gt;1.64 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024 B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;8,606&lt;/td&gt;
&lt;td&gt;23,802&lt;/td&gt;
&lt;td&gt;2.77x&lt;/td&gt;
&lt;td&gt;15.25 ms&lt;/td&gt;
&lt;td&gt;5.39 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024 B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;GET hit&lt;/td&gt;
&lt;td&gt;6,999&lt;/td&gt;
&lt;td&gt;23,397&lt;/td&gt;
&lt;td&gt;3.34x&lt;/td&gt;
&lt;td&gt;20.21 ms&lt;/td&gt;
&lt;td&gt;4.98 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are single recorded unary samples. Redis was consistently faster, but the&lt;br&gt;
exact ratios should not be treated as stable capacity limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlled mixed pipeline results
&lt;/h2&gt;

&lt;p&gt;Both products executed the same ordered, non-transactional&lt;br&gt;
&lt;code&gt;SET / GET / DELETE&lt;/code&gt; mix using 64-byte values. Each worker issued 100 measured&lt;br&gt;
pipelines per cell after 25 warm-up pipelines (five for size 1,000). Values are&lt;br&gt;
the median of three complete trials; all trials had zero errors.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pipeline operations&lt;/th&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;WeTask ops/s&lt;/th&gt;
&lt;th&gt;Redis ops/s&lt;/th&gt;
&lt;th&gt;Redis/WeTask&lt;/th&gt;
&lt;th&gt;WeTask p95/pipeline&lt;/th&gt;
&lt;th&gt;Redis p95/pipeline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;15,859&lt;/td&gt;
&lt;td&gt;32,554&lt;/td&gt;
&lt;td&gt;2.05x&lt;/td&gt;
&lt;td&gt;1.20 ms&lt;/td&gt;
&lt;td&gt;0.33 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;74,135&lt;/td&gt;
&lt;td&gt;160,198&lt;/td&gt;
&lt;td&gt;2.16x&lt;/td&gt;
&lt;td&gt;3.75 ms&lt;/td&gt;
&lt;td&gt;1.91 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;116,655&lt;/td&gt;
&lt;td&gt;175,639&lt;/td&gt;
&lt;td&gt;1.51x&lt;/td&gt;
&lt;td&gt;1.59 ms&lt;/td&gt;
&lt;td&gt;1.28 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;256,634&lt;/td&gt;
&lt;td&gt;727,032&lt;/td&gt;
&lt;td&gt;2.83x&lt;/td&gt;
&lt;td&gt;13.87 ms&lt;/td&gt;
&lt;td&gt;4.33 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;221,638&lt;/td&gt;
&lt;td&gt;434,694&lt;/td&gt;
&lt;td&gt;1.96x&lt;/td&gt;
&lt;td&gt;6.31 ms&lt;/td&gt;
&lt;td&gt;3.55 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;402,175&lt;/td&gt;
&lt;td&gt;886,477&lt;/td&gt;
&lt;td&gt;2.20x&lt;/td&gt;
&lt;td&gt;87.58 ms&lt;/td&gt;
&lt;td&gt;26.22 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The repository's independent packaged-client pipeline harness also passed all&lt;br&gt;
six cells with zero errors. Its one-run WeTask range was 15.6k ops/s&lt;br&gt;
(size 10, concurrency 1) through 394.2k ops/s (size 1,000, concurrency 16).&lt;/p&gt;

&lt;h2&gt;
  
  
  Queue-ingress probes
&lt;/h2&gt;

&lt;p&gt;The controlled Go probe used a printable 64-byte payload, 500 measured submits&lt;br&gt;
per worker, 25 warm-up submits per worker, a fresh WeTask volume, and a flushed&lt;br&gt;
Redis database.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;WeTask SubmitTask&lt;/th&gt;
&lt;th&gt;Redis LPUSH&lt;/th&gt;
&lt;th&gt;WeTask p95&lt;/th&gt;
&lt;th&gt;Redis p95&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;334 ops/s&lt;/td&gt;
&lt;td&gt;3,340 ops/s&lt;/td&gt;
&lt;td&gt;4.03 ms&lt;/td&gt;
&lt;td&gt;0.34 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;301 ops/s&lt;/td&gt;
&lt;td&gt;19,037 ops/s&lt;/td&gt;
&lt;td&gt;66.51 ms&lt;/td&gt;
&lt;td&gt;1.83 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The documented Celery producer probe submitted 10,000 tasks at 728 tasks/s&lt;br&gt;
sequentially. It used Redis as broker but no Celery worker or result backend.&lt;br&gt;
The documented Redis cache probe completed 30,000 sequential SET/GET/TTL&lt;br&gt;
operations at 2,696 operations/s.&lt;/p&gt;

&lt;h2&gt;
  
  
  WeTask-only documented benchmark coverage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Official five-second Phase 9 baseline: 23 rows, including task,
scheduler, cache, cache replication, consensus commit, failover, rolling
restart, and node resource snapshots.&lt;/li&gt;
&lt;li&gt;In-process task submit: 2.05 million ops/s; task completion: 157.9k ops/s.&lt;/li&gt;
&lt;li&gt;In-process cache SET: 753k ops/s; GET hit: 3.19 million ops/s.&lt;/li&gt;
&lt;li&gt;Consensus commit: 5.44 ms/op; failover: 258 ms; rolling restart: 976 ms.&lt;/li&gt;
&lt;li&gt;Fast-cache server path: unary GET 628 ns/op, unary SET 1.56 microseconds/op;
pipeline service path 4.74 microseconds for 10 operations, 51.1 microseconds
for 100, and 400 microseconds for 1,000.&lt;/li&gt;
&lt;li&gt;Live release HTTP cache, 10,000 operations each:
concurrency 1 produced 1.55k SET, 2.04k GET, and 2.00k DELETE ops/s;
concurrency 64 produced 2.74k SET, 3.42k GET, and 4.39k DELETE ops/s.&lt;/li&gt;
&lt;li&gt;Release task matrix: 12 concurrency/payload/handler-duration cells, with
zero errors. Concurrency was 1/16/64, payload was 0/1,024 bytes, and handler
delay was 0/50 ms.&lt;/li&gt;
&lt;li&gt;Documented competitive suite: 14 Go rows and all three required competitor
probes passed; no probe was skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproducible commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;WETASK_PHASE9_BENCHTIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5s &lt;span class="nv"&gt;WETASK_PHASE9_BENCH_QUIET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  ./scripts/benchmark-phase9.sh /tmp/wetask-redis-comparison-20260725/phase9-baseline-full

&lt;span class="nv"&gt;WETASK_BENCH_QUIET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt; &lt;span class="s1"&gt;'^$'&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt; BenchmarkFastCache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-benchmem&lt;/span&gt; &lt;span class="nt"&gt;-benchtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5s ./internal/network

&lt;span class="nv"&gt;WETASK_HTTP_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:38081 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_LIVE_WORKLOADS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache &lt;span class="nv"&gt;WETASK_LIVE_CACHE_OPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_LIVE_CACHE_CONCURRENCY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="se"&gt;\&lt;/span&gt;
  node scripts/benchmark-phase9-live-http.mjs

go run ./examples/go-client/cache-pipeline-benchmark &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-address&lt;/span&gt; 127.0.0.1:38080 &lt;span class="nt"&gt;-sizes&lt;/span&gt; 10,100,1000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-concurrency&lt;/span&gt; 1,16 &lt;span class="nt"&gt;-iterations&lt;/span&gt; 100 &lt;span class="nt"&gt;-json&lt;/span&gt;

&lt;span class="nv"&gt;WETASK_HTTP_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:38081 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_GRPC_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1:38080 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_PIPELINE_ITERATIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;WETASK_MATRIX_TASK_OPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200 &lt;span class="se"&gt;\&lt;/span&gt;
  ./scripts/benchmark-release-matrix.sh &lt;span class="se"&gt;\&lt;/span&gt;
  /tmp/wetask-redis-comparison-20260725/release-matrix

&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/wetask-bench-venv/bin:/usr/local/go/bin:/opt/homebrew/bin:/usr/bin:/bin &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_PHASE9_BENCHTIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5s &lt;span class="nv"&gt;WETASK_PHASE9_COMPETITOR_OPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_PHASE9_REDIS_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis://127.0.0.1:6380/0 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_PHASE9_CELERY_BROKER_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis://127.0.0.1:6380/0 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;WETASK_PHASE9_REQUIRE_COMPETITORS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  ./scripts/benchmark-phase9-competitive.sh &lt;span class="se"&gt;\&lt;/span&gt;
  /tmp/wetask-redis-comparison-20260725/competitive-documented-full

/tmp/wetask-redis-comparison-20260725/network-compare/network-compare &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-wetask&lt;/span&gt; 127.0.0.1:38080 &lt;span class="nt"&gt;-redis&lt;/span&gt; 127.0.0.1:6380 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-unary-iterations&lt;/span&gt; 1000 &lt;span class="nt"&gt;-pipeline-iterations&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-queue-iterations&lt;/span&gt; 500 &lt;span class="nt"&gt;-warmup&lt;/span&gt; 25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations and conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In-process Go numbers are implementation baselines and must not be compared
directly with network Redis numbers.&lt;/li&gt;
&lt;li&gt;Redis persistence was disabled. This is appropriate for a cache-path speed
baseline but not equivalent to a durable task broker.&lt;/li&gt;
&lt;li&gt;WeTask used standalone mode with consensus disabled. No clustered Redis,
WeTask replication, cross-host latency, TLS/auth, eviction pressure,
permanent-node loss, or multi-tenant load was benchmarked.&lt;/li&gt;
&lt;li&gt;The machine was not lab-isolated, Docker shared four CPUs, and most cells
have one recorded sample. Pipeline cells have three trials and report medians.&lt;/li&gt;
&lt;li&gt;WeTask remains technically functional and error-free in this local run, but
Redis has a clear cache/pipeline performance advantage here. These results do
not remove the integration blocker: the published WeTask release still needs
a supported Python worker/execution bridge before it can replace Celery workers.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>python</category>
      <category>automation</category>
    </item>
    <item>
      <title>Wetask: a unified runtime for distributed tasks, scheduling, and caching</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sat, 11 Jul 2026 07:09:54 +0000</pubDate>
      <link>https://dev.to/tosiiko/wetask-a-unified-runtime-for-distributed-tasks-scheduling-and-caching-3i2m</link>
      <guid>https://dev.to/tosiiko/wetask-a-unified-runtime-for-distributed-tasks-scheduling-and-caching-3i2m</guid>
      <description>&lt;p&gt;Today I’m releasing &lt;strong&gt;Wetask v0.1.0-rc.1&lt;/strong&gt;, the first public release candidate of a system I’ve been building in Go.&lt;/p&gt;

&lt;p&gt;Wetask combines several backend responsibilities into one deployable runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distributed task execution&lt;/li&gt;
&lt;li&gt;Scheduled jobs&lt;/li&gt;
&lt;li&gt;Integrated caching&lt;/li&gt;
&lt;li&gt;Durable queue recovery&lt;/li&gt;
&lt;li&gt;High availability and leader election&lt;/li&gt;
&lt;li&gt;Health checks, metrics, administration, and operational tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is straightforward: reduce the number of separate components teams need to operate for background jobs, scheduling, caching, and failover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current release status
&lt;/h2&gt;

&lt;p&gt;This is a &lt;strong&gt;controlled pilot release candidate&lt;/strong&gt;, not yet a general-availability enterprise release.&lt;/p&gt;

&lt;p&gt;The release includes compiled binaries for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux amd64 and arm64&lt;/li&gt;
&lt;li&gt;macOS Intel and Apple Silicon&lt;/li&gt;
&lt;li&gt;Windows amd64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation source remains private. The public repository contains only compiled releases, installation tooling, checksums, licenses, and package metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Wetask
&lt;/h2&gt;

&lt;p&gt;On macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/tosiiko/wetask-releases/main/install.sh &lt;span class="se"&gt;\&lt;/span&gt;
  | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt; v0.1.0-rc.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer detects the operating system and architecture, downloads the appropriate archive, and verifies its SHA-256 checksum before installation.&lt;br&gt;
You can also download the archives directly:&lt;br&gt;
&lt;a href="https://github.com/tosiiko/wetask-releases/releases/tag/v0.1.0-rc.1" rel="noopener noreferrer"&gt;https://github.com/tosiiko/wetask-releases/releases/tag/v0.1.0-rc.1&lt;/a&gt;&lt;br&gt;
Reliability work&lt;br&gt;
Before this release, I ran unit, integration, race, chaos, upgrade, recovery, load, and direct client-pipeline tests.&lt;br&gt;
The direct gRPC pipeline benchmark processed 8,991,000 mixed cache operations with zero client errors. Peak throughput reached approximately 753,000 operations per second in a container-local benchmark.&lt;br&gt;
I also fixed and regression-tested a lock-order deadlock affecting concurrent durable task submission and result waiting.&lt;br&gt;
What comes next&lt;br&gt;
The next milestones are:&lt;br&gt;
A dedicated Homebrew tap&lt;br&gt;
Multi-architecture container publishing&lt;br&gt;
Secured three-node staging validation&lt;br&gt;
Live failover and restore exercises&lt;br&gt;
Feedback from controlled production pilots&lt;br&gt;
If you test Wetask, installation problems and reproducible binary issues can be reported through the public repository. Please use private vulnerability reporting for security issues.&lt;br&gt;
Release:&lt;br&gt;
&lt;a href="https://github.com/tosiiko/wetask-releases/releases/tag/v0.1.0-rc.1" rel="noopener noreferrer"&gt;https://github.com/tosiiko/wetask-releases/releases/tag/v0.1.0-rc.1&lt;/a&gt;&lt;br&gt;
I’d especially value feedback from teams operating task queues, schedulers, caching systems, or combinations of those components.&lt;/p&gt;

</description>
      <category>go</category>
      <category>devops</category>
      <category>distributed</category>
      <category>release</category>
    </item>
    <item>
      <title>I built Wetask: a unified runtime for distributed tasks, scheduling, and caching</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sat, 11 Jul 2026 07:07:51 +0000</pubDate>
      <link>https://dev.to/tosiiko/i-built-wetask-a-unified-runtime-for-distributed-tasks-scheduling-and-caching-4d0m</link>
      <guid>https://dev.to/tosiiko/i-built-wetask-a-unified-runtime-for-distributed-tasks-scheduling-and-caching-4d0m</guid>
      <description>&lt;p&gt;Today I’m releasing &lt;strong&gt;Wetask v0.1.0-rc.1&lt;/strong&gt;, the first public release candidate of a system I’ve been building in Go.&lt;/p&gt;

&lt;p&gt;Wetask combines several backend responsibilities into one deployable runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distributed task execution&lt;/li&gt;
&lt;li&gt;Scheduled jobs&lt;/li&gt;
&lt;li&gt;Integrated caching&lt;/li&gt;
&lt;li&gt;Durable queue recovery&lt;/li&gt;
&lt;li&gt;High availability and leader election&lt;/li&gt;
&lt;li&gt;Health checks, metrics, administration, and operational tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is straightforward: reduce the number of separate components teams need to operate for background jobs, scheduling, caching, and failover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current release status
&lt;/h2&gt;

&lt;p&gt;This is a &lt;strong&gt;controlled pilot release candidate&lt;/strong&gt;, not yet a general-availability enterprise release.&lt;/p&gt;

&lt;p&gt;The release includes compiled binaries for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux amd64 and arm64&lt;/li&gt;
&lt;li&gt;macOS Intel and Apple Silicon&lt;/li&gt;
&lt;li&gt;Windows amd64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation source remains private. The public repository contains only compiled releases, installation tooling, checksums, licenses, and package metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Wetask
&lt;/h2&gt;

&lt;p&gt;On macOS or Linux:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
curl -fsSL https://raw.githubusercontent.com/tosiiko/wetask-releases/main/install.sh \
  | sh -s -- --yes --version v0.1.0-rc.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>go</category>
      <category>devops</category>
      <category>distributed</category>
      <category>release</category>
    </item>
    <item>
      <title>Every MDL behavior attribute for htmx and TypeScript</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:46:16 +0000</pubDate>
      <link>https://dev.to/tosiiko/every-mdl-behavior-attribute-for-htmx-and-typescript-5401</link>
      <guid>https://dev.to/tosiiko/every-mdl-behavior-attribute-for-htmx-and-typescript-5401</guid>
      <description>&lt;p&gt;MDL has two small behavior paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MDL behavior attributes -&amp;gt; htmx hx-* output
MDL event attributes    -&amp;gt; exported JavaScript or TypeScript handlers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the source can stay compact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@api(post /api/profile)@result(profileResult)@swap(replace)@trigger(submit)@loading(profileBusy):
  .input@id(profileName)@name(name)@required
  .btn-primary@type(submit)(Save)
  status@id(profileBusy):
    Saving.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the htmx adapter enabled, MDL emits validated htmx attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-form"&lt;/span&gt;
  &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/api/profile"&lt;/span&gt;
  &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#profileResult"&lt;/span&gt;
  &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;
  &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
  &lt;span class="na"&gt;hx-indicator=&lt;/span&gt;&lt;span class="s"&gt;"#profileBusy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with configured JavaScript or TypeScript modules, event attributes call&lt;br&gt;
exported functions by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;card@id(plannerPanel)@mount(mountPlanner):
  .btn-primary@click(addTask)(Add task)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;function&lt;/span&gt; &lt;span class="nf"&gt;mountPlanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;
  
  
  Enable the htmx adapter
&lt;/h2&gt;

&lt;p&gt;Configure htmx as the behavior adapter in &lt;code&gt;mdl.json&lt;/code&gt;:&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;"head_scripts"&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="s2"&gt;"https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js"&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;"behavior"&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;"adapter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"htmx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MDL source should use MDL behavior attributes such as &lt;code&gt;@api(...)&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;@result(...)&lt;/code&gt;, and &lt;code&gt;@swap(...)&lt;/code&gt;. Raw &lt;code&gt;@hx-post(...)&lt;/code&gt;, &lt;code&gt;@hx-target(...)&lt;/code&gt;, and&lt;br&gt;
other raw htmx attributes are not emitted.&lt;/p&gt;
&lt;h2&gt;
  
  
  htmx behavior attribute map
&lt;/h2&gt;

&lt;p&gt;These are the MDL behavior attributes supported by the htmx adapter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MDL attribute&lt;/th&gt;
&lt;th&gt;htmx output&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@api(get /api/search)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-get="/api/search"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@result(searchResults)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-target="#searchResults"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Targets &lt;code&gt;this&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;#id&lt;/code&gt;, or &lt;code&gt;.class&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@swap(inner)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-swap="innerHTML"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;See swap values below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@trigger(input)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-trigger="input"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Uses a known trigger event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@loading(searchBusy)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-indicator="#searchBusy"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also accepts &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;#id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@confirm(Save changes?)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-confirm="Save changes?"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plain text prompt, max 200 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@include(csrfToken)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-include="#csrfToken"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also accepts &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;#id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@select(resultFragment)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-select="#resultFragment"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Selects one response fragment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@select-oob(toast)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-select-oob="#toast"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Selects one out-of-band fragment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@swap-oob(true)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-swap-oob="true"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also accepts known swap values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@push(false)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-push-url="false"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, or safe root path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@replace(/profile)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-replace-url="/profile"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, or safe root path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@history(false)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-history="false"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@history-elt(true)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-history-elt="true"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Emits only for &lt;code&gt;true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@boost(true)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-boost="true"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only on safe local links/forms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@disabled(saveButton)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-disabled-elt="#saveButton"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also accepts &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;#id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@disinherit(target swap)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-disinherit="hx-target hx-swap"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allows known htmx attrs or &lt;code&gt;*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@encoding(multipart)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-encoding="multipart/form-data"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also supports urlencoded form encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@inherit(trigger)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-inherit="hx-trigger"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allows known htmx attrs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@params(name email)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-params="name,email"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Explicit names or &lt;code&gt;none&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@preserve(true)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-preserve="true"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Requires a safe &lt;code&gt;@id(...)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@prompt(Security code?)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-prompt="Security code?"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plain text prompt, max 200 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@request(timeout=5000)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-request="{&amp;amp;quot;timeout&amp;amp;quot;:5000}"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timeout from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;60000&lt;/code&gt; ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@sync(profileForm:queue-last)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-sync="#profileForm:queue last"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coordinates concurrent requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@validate(true)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hx-validate="true"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Supported &lt;code&gt;@api(...)&lt;/code&gt; methods
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get
post
put
patch
delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;@api(...)&lt;/code&gt; only emits same-origin-safe paths. External API URLs are not&lt;br&gt;
emitted from this attribute.&lt;/p&gt;
&lt;h2&gt;
  
  
  Supported &lt;code&gt;@swap(...)&lt;/code&gt; values
&lt;/h2&gt;

&lt;p&gt;MDL gives short names to the htmx swap values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MDL&lt;/th&gt;
&lt;th&gt;htmx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;innerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inner-html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;innerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;innerhtml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;innerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outer-html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outerhtml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outerHTML&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;append&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beforeend&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prepend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;afterbegin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;before&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beforebegin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;after&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;afterend&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;@swap-oob(...)&lt;/code&gt; supports &lt;code&gt;true&lt;/code&gt; plus the same known swap values. &lt;code&gt;false&lt;/code&gt; is not&lt;br&gt;
emitted for &lt;code&gt;@swap-oob(...)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Supported &lt;code&gt;@trigger(...)&lt;/code&gt; values
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submit
click
change
input
load
revealed
intersect
keyup
keydown
focus
blur
mouseenter
mouseleave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;MDL intentionally does not emit htmx trigger filters or JavaScript-like trigger&lt;br&gt;
expressions from &lt;code&gt;@trigger(...)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Supported &lt;code&gt;@encoding(...)&lt;/code&gt; values
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multipart
multipart/form-data
form
urlencoded
application/x-www-form-urlencoded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These normalize to either &lt;code&gt;multipart/form-data&lt;/code&gt; or&lt;br&gt;
&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Supported &lt;code&gt;@sync(...)&lt;/code&gt; strategies
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drop
abort
replace
queue
queue-first
queue-last
queue-all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can pass the strategy by itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@sync(queue-last):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or scope it to one explicit target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@sync(profileForm:queue-last):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supported &lt;code&gt;@inherit(...)&lt;/code&gt; and &lt;code&gt;@disinherit(...)&lt;/code&gt; names
&lt;/h2&gt;

&lt;p&gt;These attributes accept MDL names or htmx names. MDL normalizes them to htmx&lt;br&gt;
attribute names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target / result       -&amp;gt; hx-target
swap                  -&amp;gt; hx-swap
trigger               -&amp;gt; hx-trigger
indicator / loading   -&amp;gt; hx-indicator
confirm               -&amp;gt; hx-confirm
include               -&amp;gt; hx-include
select                -&amp;gt; hx-select
select-oob            -&amp;gt; hx-select-oob
swap-oob              -&amp;gt; hx-swap-oob
push / push-url       -&amp;gt; hx-push-url
replace / replace-url -&amp;gt; hx-replace-url
history               -&amp;gt; hx-history
history-elt           -&amp;gt; hx-history-elt
boost                 -&amp;gt; hx-boost
disabled / disabled-elt -&amp;gt; hx-disabled-elt
disinherit            -&amp;gt; hx-disinherit
encoding              -&amp;gt; hx-encoding
inherit               -&amp;gt; hx-inherit
params                -&amp;gt; hx-params
preserve              -&amp;gt; hx-preserve
prompt                -&amp;gt; hx-prompt
request               -&amp;gt; hx-request
sync                  -&amp;gt; hx-sync
validate              -&amp;gt; hx-validate
get                   -&amp;gt; hx-get
post                  -&amp;gt; hx-post
put                   -&amp;gt; hx-put
patch                 -&amp;gt; hx-patch
delete                -&amp;gt; hx-delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@disinherit(*)&lt;/code&gt; is also supported. &lt;code&gt;@inherit(*)&lt;/code&gt; is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure TypeScript handlers
&lt;/h2&gt;

&lt;p&gt;MDL &lt;code&gt;scripts&lt;/code&gt; can point at JavaScript or TypeScript module entries:&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;"scripts"&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="s2"&gt;"scripts/app.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;mdl build&lt;/code&gt;, MDL compiles local &lt;code&gt;.ts&lt;/code&gt; module scripts to browser-ready&lt;br&gt;
JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/app.ts -&amp;gt; dist/scripts/app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated HTML imports the JavaScript URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;mdlModule0&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./scripts/app.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;mdl serve&lt;/code&gt;, the browser still asks for &lt;code&gt;./scripts/app.js&lt;/code&gt;, and the dev&lt;br&gt;
server serves JavaScript compiled from &lt;code&gt;scripts/app.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Configured TypeScript entries can import local TypeScript trees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/app.ts
scripts/state/store.ts
scripts/dom/render.ts
scripts/utils/format.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MDL preserves the folder tree in output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/scripts/app.js
dist/scripts/state/store.js
dist/scripts/dom/render.js
dist/scripts/utils/format.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local TypeScript imports may use &lt;code&gt;.ts&lt;/code&gt;, omit the extension, or use the&lt;br&gt;
browser-facing &lt;code&gt;.js&lt;/code&gt; extension:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./state/store.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderTaskList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./dom/render.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./utils/format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./state/model.ts&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;p&gt;MDL does not create &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, or &lt;code&gt;tsconfig.json&lt;/code&gt; for this.&lt;br&gt;
It is transpile-only behavior support, not a bundler or project type checker.&lt;/p&gt;

&lt;p&gt;Inline TypeScript blocks are not supported yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script ts:
  // not supported yet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use inline JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script js:
  document.body.dataset.ready = "true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or an external configured &lt;code&gt;.ts&lt;/code&gt; module.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript event attributes
&lt;/h2&gt;

&lt;p&gt;Event attributes compile to &lt;code&gt;data-mdl-on-*&lt;/code&gt; markers, and MDL's generated module&lt;br&gt;
runtime calls exported handlers by name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@submit(createTaskFromForm):
  .input@input(updateDraftPreview)
  .btn-primary@click(saveTask)(Save)

canvas@id(scene)@mount(drawScene):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-form"&lt;/span&gt; &lt;span class="na"&gt;data-mdl-on-submit=&lt;/span&gt;&lt;span class="s"&gt;"createTaskFromForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-input"&lt;/span&gt; &lt;span class="na"&gt;data-mdl-on-input=&lt;/span&gt;&lt;span class="s"&gt;"updateDraftPreview"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-btn-primary"&lt;/span&gt; &lt;span class="na"&gt;data-mdl-on-click=&lt;/span&gt;&lt;span class="s"&gt;"saveTask"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-canvas"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"scene"&lt;/span&gt; &lt;span class="na"&gt;data-mdl-on-mount=&lt;/span&gt;&lt;span class="s"&gt;"drawScene"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@mount(handler)&lt;/code&gt; is not a browser event. It runs once after configured modules&lt;br&gt;
are imported and receives the mounted element.&lt;/p&gt;

&lt;p&gt;All other handlers receive the browser event.&lt;/p&gt;
&lt;h2&gt;
  
  
  Complete event alias list
&lt;/h2&gt;

&lt;p&gt;These event attributes can be handled from JavaScript or TypeScript modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@click              @dblclick           @auxclick          @contextmenu
@command
@submit             @reset              @formdata          @beforeinput
@input              @change             @invalid           @search
@select
@compositionstart   @compositionupdate  @compositionend
@focus              @blur               @focusin           @focusout
@keydown            @keypress           @keyup
@mousedown          @mouseup            @mousemove         @mouseover
@mouseout           @mouseenter         @mouseleave
@pointerdown        @pointerup          @pointermove       @pointerover
@pointerout         @pointerenter       @pointerleave      @pointercancel
@pointerrawupdate   @gotpointercapture  @lostpointercapture
@touchstart         @touchmove          @touchend          @touchcancel
@wheel              @scroll             @scrollend
@load               @error              @abort             @resize
@canplay            @canplaythrough     @play              @playing
@pause              @ended              @durationchange    @emptied
@loadeddata         @loadedmetadata     @loadstart         @progress
@ratechange         @seeked             @seeking           @stalled
@suspend            @timeupdate         @volumechange      @waiting
@dragstart          @drag               @dragenter         @dragover
@dragleave          @drop               @dragend
@copy               @cut                @paste
@cuechange          @slotchange
@toggle             @beforetoggle       @beforematch       @close
@cancel             @contextlost        @contextrestored
@securitypolicyviolation
@fullscreenchange   @fullscreenerror
@animationstart     @animationiteration @animationend      @animationcancel
@transitionrun      @transitionstart    @transitioncancel  @transitionend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TypeScript handler example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@id(taskForm)@submit(createTaskFromForm):
  .input@id(taskTitle)@name(title)@type(text)@required@input(updateDraftPreview)
  .btn-primary@type(submit)(Add task)

card@id(plannerPanel)@mount(mountPlanner):
  status@id(boardStatus)@aria-live(polite):
    Waiting for TypeScript.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;function&lt;/span&gt; &lt;span class="nf"&gt;mountPlanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTaskFromForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SubmitEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLFormElement&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;title&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="c1"&gt;// Update app state, then render.&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;function&lt;/span&gt; &lt;span class="nf"&gt;updateDraftPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#draftPreview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  General MDL attribute rules
&lt;/h2&gt;

&lt;p&gt;Normal HTML attributes are still direct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.input@id(email)@name(email)@type(email)@required@autocomplete(email)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That emits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-input"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core rules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@attr(value)&lt;/code&gt; emits &lt;code&gt;attr="value"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@attr&lt;/code&gt; emits a boolean attribute.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@class(...)&lt;/code&gt; appends author classes after the generated &lt;code&gt;mdl-*&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;Raw browser event attributes such as &lt;code&gt;@onclick(...)&lt;/code&gt; are not emitted.&lt;/li&gt;
&lt;li&gt;Raw htmx attributes such as &lt;code&gt;@hx-post(...)&lt;/code&gt; are not emitted.&lt;/li&gt;
&lt;li&gt;Inline &lt;code&gt;@style(...)&lt;/code&gt; and iframe &lt;code&gt;@srcdoc(...)&lt;/code&gt; are not emitted.&lt;/li&gt;
&lt;li&gt;URL attributes are filtered to safe URL shapes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason is boring in the best way: MDL keeps authoring short, but the output&lt;br&gt;
should remain inspectable and hard to accidentally turn into a script sink.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try the examples
&lt;/h2&gt;

&lt;p&gt;The htmx example app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node examples/htmx/server.mjs
target/debug/mdl serve examples/htmx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:4010
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TypeScript example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;examples/typescript
../../bin/mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3996
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://getmdl.site" rel="noopener noreferrer"&gt;https://getmdl.site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/tosiiko/mdl-code" rel="noopener noreferrer"&gt;https://github.com/tosiiko/mdl-code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm package: &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;htmx example: &lt;code&gt;examples/htmx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript example: &lt;code&gt;examples/typescript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Attribute spec: &lt;code&gt;docs/spec/ATTRIBUTES.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Script spec: &lt;code&gt;docs/spec/SCRIPTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MDL is still early, but the shape is already useful: htmx gets validated&lt;br&gt;
hypermedia attributes, TypeScript gets typed behavior modules, and the browser&lt;br&gt;
still receives plain HTML, CSS, and JavaScript.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>htmx</category>
      <category>typescript</category>
      <category>html</category>
    </item>
    <item>
      <title>MDL 0.1.9: optional TypeScript behavior scripts without a TypeScript project setup</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Thu, 11 Jun 2026 20:19:47 +0000</pubDate>
      <link>https://dev.to/tosiiko/mdl-019-optional-typescript-behavior-scripts-without-a-typescript-project-setup-383h</link>
      <guid>https://dev.to/tosiiko/mdl-019-optional-typescript-behavior-scripts-without-a-typescript-project-setup-383h</guid>
      <description>&lt;p&gt;MDL &lt;code&gt;0.1.9&lt;/code&gt; adds optional external TypeScript behavior scripts.&lt;/p&gt;

&lt;p&gt;The important word is optional.&lt;/p&gt;

&lt;p&gt;MDL is still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.mdl  -&amp;gt; structure and content
.css  -&amp;gt; layout and design
.js   -&amp;gt; optional behavior
HTML  -&amp;gt; compiler output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This release does not turn MDL into a JavaScript-app workflow. It does not make&lt;br&gt;
TypeScript a requirement for normal MDL sites. It simply means that if you want&lt;br&gt;
typed behavior modules, MDL can compile local &lt;code&gt;.ts&lt;/code&gt; files for the browser during&lt;br&gt;
build and serve.&lt;/p&gt;
&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;Before this release, an MDL project config could include JavaScript module&lt;br&gt;
scripts:&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;"scripts"&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="s2"&gt;"scripts/app.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mdl build&lt;/code&gt; copied those files unchanged, and generated HTML imported them as&lt;br&gt;
module scripts.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;0.1.9&lt;/code&gt;, the same &lt;code&gt;scripts&lt;/code&gt; array can also point at a local TypeScript entry:&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;"scripts"&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="s2"&gt;"scripts/app.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;mdl build&lt;/code&gt;, MDL transpiles that entry to JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/app.ts -&amp;gt; dist/scripts/app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated document imports the browser-ready JavaScript URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;mdlModule0&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./scripts/app.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;mdl serve&lt;/code&gt;, the page still imports &lt;code&gt;./scripts/app.js&lt;/code&gt;. The dev server&lt;br&gt;
serves that compiled JavaScript from the configured TypeScript source, so the&lt;br&gt;
development URL matches the production URL.&lt;/p&gt;
&lt;h2&gt;
  
  
  TypeScript trees are supported
&lt;/h2&gt;

&lt;p&gt;The first pass is not limited to a single file. A configured TypeScript entry&lt;br&gt;
can import other local TypeScript modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/app.ts
scripts/state/store.ts
scripts/state/model.ts
scripts/dom/forms.ts
scripts/dom/render.ts
scripts/utils/id.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MDL follows the local static import/export graph, preserves the folder tree, and&lt;br&gt;
emits matching JavaScript modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/scripts/app.js
dist/scripts/state/store.js
dist/scripts/state/model.js
dist/scripts/dom/forms.js
dist/scripts/dom/render.js
dist/scripts/utils/id.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local TypeScript imports may use &lt;code&gt;.ts&lt;/code&gt;, omit the extension, or use the&lt;br&gt;
browser-facing &lt;code&gt;.js&lt;/code&gt; extension. MDL rewrites local TypeScript module specifiers&lt;br&gt;
to emitted &lt;code&gt;.js&lt;/code&gt; URLs.&lt;/p&gt;

&lt;p&gt;For example, this is valid in source:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./state/store.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderTaskList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./dom/render.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;byId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./dom/query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TaskFilter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./state/model.ts&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;p&gt;The emitted JavaScript uses browser-safe JavaScript module URLs for the local&lt;br&gt;
TypeScript files.&lt;/p&gt;

&lt;p&gt;Type-only imports stay type-only. They do not become runtime dependencies.&lt;/p&gt;
&lt;h2&gt;
  
  
  What MDL does not do
&lt;/h2&gt;

&lt;p&gt;This is transpile-only support for behavior modules.&lt;/p&gt;

&lt;p&gt;MDL does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scaffold &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create &lt;code&gt;node_modules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create &lt;code&gt;tsconfig.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;require users to install TypeScript&lt;/li&gt;
&lt;li&gt;require deployed sites to carry TypeScript&lt;/li&gt;
&lt;li&gt;bundle npm packages&lt;/li&gt;
&lt;li&gt;type-check the project yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bare package imports and external URLs are left alone. MDL is not trying to be&lt;br&gt;
a bundler in this release.&lt;/p&gt;

&lt;p&gt;The TypeScript parser/transpiler lives inside MDL tooling, so a site can use a&lt;br&gt;
configured &lt;code&gt;.ts&lt;/code&gt; behavior module without becoming a TypeScript project.&lt;/p&gt;
&lt;h2&gt;
  
  
  Inline scripts stay JavaScript for now
&lt;/h2&gt;

&lt;p&gt;Inline JavaScript blocks keep working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script js:
  const form = document.querySelector("#loginForm")
  form?.classList.add("ready")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inline TypeScript is still unsupported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script ts:
  // not supported yet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For TypeScript, use an external configured module:&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;"scripts"&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="s2"&gt;"scripts/app.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Event bindings still work the same way
&lt;/h2&gt;

&lt;p&gt;Full-document MDL output imports configured modules and binds exported functions&lt;br&gt;
to MDL behavior attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@id(taskForm)@submit(createTaskFromForm):
  .input@id(taskTitle)@name(title)@type(text)@input(updateDraftPreview)
  .btn-primary@type(submit)(Add task)

card@id(plannerPanel)@mount(mountPlanner):
  status@id(boardStatus):
    Waiting for the TypeScript module tree.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your TypeScript module exports the handlers:&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;function&lt;/span&gt; &lt;span class="nf"&gt;mountPlanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Planner mounted from compiled TypeScript.&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTaskFromForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SubmitEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// read the form, update state, re-render&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;function&lt;/span&gt; &lt;span class="nf"&gt;updateDraftPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// update live preview while the user types&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MDL handles the wiring. The browser receives JavaScript modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  New example: &lt;code&gt;examples/typescript&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This release adds a more advanced TypeScript example:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;It is a small typed task planner with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a configured &lt;code&gt;scripts/app.ts&lt;/code&gt; entry&lt;/li&gt;
&lt;li&gt;nested state, DOM, and utility modules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;import type&lt;/code&gt; usage&lt;/li&gt;
&lt;li&gt;imports that use &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.js&lt;/code&gt;, and extensionless specifiers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@mount&lt;/code&gt;, &lt;code&gt;@submit&lt;/code&gt;, &lt;code&gt;@input&lt;/code&gt;, &lt;code&gt;@change&lt;/code&gt;, and &lt;code&gt;@click&lt;/code&gt; handlers&lt;/li&gt;
&lt;li&gt;dashboard metrics&lt;/li&gt;
&lt;li&gt;task filters&lt;/li&gt;
&lt;li&gt;form parsing&lt;/li&gt;
&lt;li&gt;toast updates&lt;/li&gt;
&lt;li&gt;generated &lt;code&gt;dist/scripts/*.js&lt;/code&gt; output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;examples/typescript
../../bin/mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3996
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;../../bin/mdl build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example intentionally has no &lt;code&gt;package.json&lt;/code&gt;, no &lt;code&gt;node_modules&lt;/code&gt;, and no&lt;br&gt;
&lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this shape?
&lt;/h2&gt;

&lt;p&gt;I want MDL to keep a narrow architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MDL source + CSS + optional behavior scripts -&amp;gt; static browser output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some pages only need HTML and CSS. Some need a little inline JavaScript. Some&lt;br&gt;
need external JavaScript modules. And some behavior is easier to maintain with&lt;br&gt;
TypeScript types.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0.1.9&lt;/code&gt; supports that last case without changing the default model.&lt;/p&gt;

&lt;p&gt;The output is still plain browser assets. The project is still inspectable. The&lt;br&gt;
site is still deployable as static files.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try MDL
&lt;/h2&gt;

&lt;p&gt;Install locally in a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-mdl-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-mdl-site
npm &lt;span class="nb"&gt;install&lt;/span&gt; @tosiiko/mdl
npm &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; mdl init
&lt;span class="nb"&gt;source &lt;/span&gt;bin/activate
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @tosiiko/mdl
mdl new my-mdl-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-mdl-site
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mdl check
mdl format &lt;span class="nt"&gt;--check&lt;/span&gt;
mdl build
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://getmdl.site" rel="noopener noreferrer"&gt;https://getmdl.site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/tosiiko/mdl-code" rel="noopener noreferrer"&gt;https://github.com/tosiiko/mdl-code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm package: &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript example: &lt;code&gt;examples/typescript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Script spec: &lt;code&gt;docs/spec/SCRIPTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MDL is still early, but &lt;code&gt;0.1.9&lt;/code&gt; makes optional behavior code easier to grow:&lt;br&gt;
JavaScript still works exactly as before, TypeScript can live in local external&lt;br&gt;
modules, and deployed sites only need the compiled JavaScript.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>html</category>
      <category>rust</category>
    </item>
    <item>
      <title>MDL 0.1.8: htmx examples, safer npm binaries, and portable static builds</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sat, 06 Jun 2026 11:53:19 +0000</pubDate>
      <link>https://dev.to/tosiiko/mdl-018-htmx-examples-safer-npm-binaries-and-portable-static-builds-20p4</link>
      <guid>https://dev.to/tosiiko/mdl-018-htmx-examples-safer-npm-binaries-and-portable-static-builds-20p4</guid>
      <description>&lt;p&gt;MDL &lt;code&gt;0.1.8&lt;/code&gt; is a release focused on two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Making hypermedia workflows easier to see and copy.&lt;/li&gt;
&lt;li&gt;Making the npm CLI package easier to inspect and trust.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MDL is a small authoring language for building clean HTML without writing raw&lt;br&gt;
HTML by hand. You write &lt;code&gt;.mdl&lt;/code&gt; files, regular CSS, and optional JavaScript; the&lt;br&gt;
CLI checks, builds, and serves the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.mdl  -&amp;gt; structure and content
.css  -&amp;gt; layout and design
HTML  -&amp;gt; compiler output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  New: Complete htmx Example App
&lt;/h2&gt;

&lt;p&gt;This release adds a complete htmx example under:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The app demonstrates MDL behavior attributes compiled through the &lt;code&gt;htmx&lt;/code&gt;&lt;br&gt;
adapter. Instead of writing raw &lt;code&gt;hx-*&lt;/code&gt; attributes in MDL source, you describe&lt;br&gt;
the intent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@api(get /api/search)@result(searchResults)@swap(inner)@trigger(input)@loading(searchBusy):
  field:
    label@for(searchQuery):
      Search cookbook entries
    .input@id(searchQuery)@name(q)@type(search)@placeholder(Type card, form, swap, or history)
  status@id(searchBusy)@class(indicator):
    Searching.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the htmx adapter enabled, MDL emits the corresponding &lt;code&gt;hx-*&lt;/code&gt; output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-form"&lt;/span&gt;
  &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"/api/search"&lt;/span&gt;
  &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#searchResults"&lt;/span&gt;
  &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"innerHTML"&lt;/span&gt;
  &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;
  &lt;span class="na"&gt;hx-indicator=&lt;/span&gt;&lt;span class="s"&gt;"#searchBusy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example app includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;live search&lt;/li&gt;
&lt;li&gt;todo list swaps&lt;/li&gt;
&lt;li&gt;cart updates&lt;/li&gt;
&lt;li&gt;profile form validation&lt;/li&gt;
&lt;li&gt;out-of-band toast updates&lt;/li&gt;
&lt;li&gt;request indicators&lt;/li&gt;
&lt;li&gt;disabled submit buttons&lt;/li&gt;
&lt;li&gt;parameter filtering&lt;/li&gt;
&lt;li&gt;request timeouts&lt;/li&gt;
&lt;li&gt;sync behavior&lt;/li&gt;
&lt;li&gt;prompted requests&lt;/li&gt;
&lt;li&gt;preserved islands&lt;/li&gt;
&lt;li&gt;boosted safe forms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the API server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node examples/htmx/server.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;target/debug/mdl serve examples/htmx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:4010
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses htmx &lt;code&gt;2.0.10&lt;/code&gt; from a pinned CDN URL in &lt;code&gt;mdl.json&lt;/code&gt;, and you can&lt;br&gt;
vendor that script locally if you want an offline demo.&lt;/p&gt;
&lt;h2&gt;
  
  
  Behavior Adapters
&lt;/h2&gt;

&lt;p&gt;MDL now has a clearer adapter story:&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;"behavior"&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;"adapter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"htmx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supported behavior adapters are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;static&lt;/code&gt;: normal HTML behavior where possible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mdl&lt;/code&gt;: &lt;code&gt;data-mdl-api-*&lt;/code&gt; attributes for future MDL runtimes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;htmx&lt;/code&gt;: validated &lt;code&gt;hx-*&lt;/code&gt; output for htmx v2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important bit is that MDL source stays framework-neutral. You write&lt;br&gt;
attributes such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api(post /api/profile)
@result(profileResult)
@swap(replace)
@trigger(submit)
@loading(profileBusy)
@select-oob(toast)
@request(timeout=5000)
@sync(profileForm:queue-last)
@validate(true)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter decides how that intent becomes HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safer npm Package
&lt;/h2&gt;

&lt;p&gt;The npm package is also hardened in this release.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@tosiiko/mdl&lt;/code&gt; ships a small Node launcher and prebuilt Rust binaries for&lt;br&gt;
supported platforms. That is convenient, but native binaries deserve a clear&lt;br&gt;
audit trail.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0.1.8&lt;/code&gt; adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SECURITY.md&lt;/code&gt; in the npm package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BINARY-PROVENANCE.md&lt;/code&gt; in the npm package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SHASUMS256.txt&lt;/code&gt; in the staged public package&lt;/li&gt;
&lt;li&gt;runtime checksum verification before executing the selected binary&lt;/li&gt;
&lt;li&gt;explicit &lt;code&gt;shell: false&lt;/code&gt; process spawning in the launcher&lt;/li&gt;
&lt;li&gt;provenance-enabled GitHub publishing with &lt;code&gt;npm publish --provenance&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package still has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no runtime npm dependencies&lt;/li&gt;
&lt;li&gt;no npm lifecycle or install scripts&lt;/li&gt;
&lt;li&gt;no install-time binary downloads&lt;/li&gt;
&lt;li&gt;no runtime binary downloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The launcher selects a binary from &lt;code&gt;vendor/&amp;lt;platform&amp;gt;/&lt;/code&gt;, verifies its SHA-256&lt;br&gt;
checksum when &lt;code&gt;SHASUMS256.txt&lt;/code&gt; is present, and only then starts the CLI.&lt;/p&gt;
&lt;h2&gt;
  
  
  Portable Static Builds
&lt;/h2&gt;

&lt;p&gt;This release also includes &lt;code&gt;routing.links&lt;/code&gt; in &lt;code&gt;mdl.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use root links when deploying from a site root:&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;"routing"&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;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use relative links when publishing &lt;code&gt;dist/&lt;/code&gt; under a subfolder or opening the&lt;br&gt;
generated files directly:&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;"routing"&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;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"relative"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relative mode rewrites local page and asset links so the output is easier to&lt;br&gt;
move between static hosts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try MDL
&lt;/h2&gt;

&lt;p&gt;Install locally in a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-mdl-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-mdl-site
npm &lt;span class="nb"&gt;install&lt;/span&gt; @tosiiko/mdl
npm &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; mdl init
&lt;span class="nb"&gt;source &lt;/span&gt;bin/activate
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @tosiiko/mdl
mdl new my-mdl-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-mdl-site
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mdl check
mdl format &lt;span class="nt"&gt;--check&lt;/span&gt;
mdl build
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://getmdl.site" rel="noopener noreferrer"&gt;https://getmdl.site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/tosiiko/mdl-code" rel="noopener noreferrer"&gt;https://github.com/tosiiko/mdl-code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm package: &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;htmx examples: &lt;code&gt;examples/htmx&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MDL is still early, but &lt;code&gt;0.1.8&lt;/code&gt; makes the project much easier to evaluate: you&lt;br&gt;
can inspect the generated HTML, run a real htmx example, check the package&lt;br&gt;
contents, and build static output that fits more hosting setups.&lt;/p&gt;

</description>
      <category>html</category>
      <category>htmx</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title># Next MDL Update: Security-First Adapters and HTMX Support</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sat, 06 Jun 2026 09:35:46 +0000</pubDate>
      <link>https://dev.to/tosiiko/-next-mdl-update-security-first-adapters-and-htmx-support-loe</link>
      <guid>https://dev.to/tosiiko/-next-mdl-update-security-first-adapters-and-htmx-support-loe</guid>
      <description>&lt;p&gt;In the last update, I introduced &lt;strong&gt;MDL&lt;/strong&gt; as an HTML-first language for building websites and apps with less noise.&lt;/p&gt;

&lt;p&gt;This update is about the next layer: &lt;strong&gt;adapters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MDL source should describe intent. Adapters decide how that intent becomes deployable HTML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why adapters?
&lt;/h2&gt;

&lt;p&gt;I don’t want MDL to become locked into one frontend framework.&lt;/p&gt;

&lt;p&gt;Instead, the same MDL structure should be able to target different deployment styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;static HTML&lt;/li&gt;
&lt;li&gt;MDL-native runtime attributes&lt;/li&gt;
&lt;li&gt;HTMX&lt;/li&gt;
&lt;li&gt;future template or framework adapters&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form@api(post /api/login)@result(loginResult)@swap(replace):
  .input@type(email)@required
  .btn-primary@type(submit)(Sign in)

  status@id(loginResult):
    Waiting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can become plain HTML:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/api/login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or HTMX:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/api/login"&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#loginResult"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security before convenienceThe important part is that MDL does not pass behavior attributes through blindly.&lt;br&gt;
Raw HTMX attributes like this are blocked:&lt;br&gt;
&lt;/p&gt;

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

form@hx-post(/api/login):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, MDL uses intent-based attributes:&lt;br&gt;
&lt;/p&gt;

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

form@api(post /api/login):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the adapter validates and translates it.&lt;br&gt;
Current safety rules include:&lt;br&gt;
external api(...) URLs are rejected&lt;br&gt;
raw @hx-* attributes are blocked&lt;br&gt;
raw browser events like onclick(...) are blocked&lt;br&gt;
unsafe URL schemes like javascript: are blocked&lt;br&gt;
broad form inclusion like @params(&lt;em&gt;) is blocked&lt;br&gt;
@inherit(&lt;/em&gt;) is blocked&lt;br&gt;
@disinherit(*) is allowed because disabling inherited behavior is safer&lt;br&gt;
That means MDL can support HTMX without making MDL source depend directly on HTMX’s full raw surface area.&lt;br&gt;
New HTMX adapter attributesThe HTMX v2 adapter now supports more behavior attributes:&lt;br&gt;
&lt;/p&gt;

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

@select-oob(...)
@swap-oob(...)
@disabled(...)
@disinherit(...)
@encoding(...)
@history-elt(...)
@inherit(...)
@params(...)
@preserve(...)
@prompt(...)
@replace(...)
@request(...)
@sync(...)
@validate(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

form@api(post /api/profile)@result(profileResult)@swap(replace)@params(email csrfToken)@disabled(saveButton):
  .input@name(email):
  .input@name(csrfToken):
  .btn-primary@id(saveButton)@type(submit)(Save)

  status@id(profileResult):
    Waiting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the HTMX adapter, MDL can emit:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/api/profile"&lt;/span&gt;
  &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#profileResult"&lt;/span&gt;
  &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;
  &lt;span class="na"&gt;hx-params=&lt;/span&gt;&lt;span class="s"&gt;"email,csrfToken"&lt;/span&gt;
  &lt;span class="na"&gt;hx-disabled-elt=&lt;/span&gt;&lt;span class="s"&gt;"#saveButton"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that @params(email csrfToken) is explicit. MDL does not allow @params(*), because accidentally sending extra form fields is exactly the kind of thing a language layer should help prevent.&lt;/p&gt;

</description>
      <category>htmx</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built MDL: A Small Language for Writing Static Websites as Readable Outlines</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Mon, 01 Jun 2026 18:10:50 +0000</pubDate>
      <link>https://dev.to/tosiiko/i-built-mdl-a-small-language-for-writing-static-websites-as-readable-outlines-1o5n</link>
      <guid>https://dev.to/tosiiko/i-built-mdl-a-small-language-for-writing-static-websites-as-readable-outlines-1o5n</guid>
      <description>&lt;p&gt;HTML is an excellent output format.&lt;/p&gt;

&lt;p&gt;As an authoring format, though, it can become noisy when you're building simple documentation sites, landing pages, examples, or other static content.&lt;/p&gt;

&lt;p&gt;I wanted something that let me write pages as readable outlines while still producing normal HTML at build time.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;MDL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MDL is a small authoring language that compiles to clean static HTML.&lt;/p&gt;

&lt;p&gt;It does not replace HTML, CSS, or JavaScript.&lt;/p&gt;

&lt;p&gt;Instead, it sits one step above them and lets you write page structure in a format that is easier to scan and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MDL Looks Like
&lt;/h2&gt;

&lt;p&gt;A simple MDL page might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page:
  hero:
    .badge(New)
    # Build clean static sites

    Write readable page structure,
    then compile to HTML.

    actions:
      .href@href(/docs.html)@class(primary)
      (Read the docs)

  section:
    ## Why MDL?

    Keep content readable.
    Keep styling in CSS.
    Add JavaScript only when behavior belongs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it compiles to ordinary HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-hero"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;New&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Build clean static sites&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is intentionally boring.&lt;/p&gt;

&lt;p&gt;That is the goal.&lt;/p&gt;

&lt;p&gt;No runtime.&lt;br&gt;
No framework lock-in.&lt;br&gt;
No custom browser behavior.&lt;/p&gt;

&lt;p&gt;Just static HTML.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;I kept finding myself wanting a format that felt closer to writing a structured note than writing a template.&lt;/p&gt;

&lt;p&gt;For many sites I don't need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Component systems&lt;/li&gt;
&lt;li&gt;Hydration&lt;/li&gt;
&lt;li&gt;Build-time JavaScript frameworks&lt;/li&gt;
&lt;li&gt;Large configuration layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I just want to describe the page structure clearly and generate predictable HTML.&lt;/p&gt;

&lt;p&gt;MDL is my attempt at that.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Browser Platform Stays Visible
&lt;/h2&gt;

&lt;p&gt;One thing that mattered to me from the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS is still CSS&lt;/li&gt;
&lt;li&gt;JavaScript is still JavaScript&lt;/li&gt;
&lt;li&gt;HTML is still the final artifact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MDL is only the authoring layer.&lt;/p&gt;

&lt;p&gt;You can inspect the generated pages and immediately understand what is happening.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install from npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-mdl-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-mdl-site

npm &lt;span class="nb"&gt;install&lt;/span&gt; @tosiiko/mdl

npm &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; mdl init

&lt;span class="nb"&gt;source &lt;/span&gt;bin/activate

mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CLI Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mdl init
mdl new my-site
mdl check
mdl format
mdl build
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recent Improvements
&lt;/h2&gt;

&lt;p&gt;Current release: &lt;strong&gt;0.1.7&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public documentation site is now live&lt;/li&gt;
&lt;li&gt;Improved static hosting support&lt;/li&gt;
&lt;li&gt;Routing control via &lt;code&gt;mdl.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Better package and editor metadata alignment&lt;/li&gt;
&lt;li&gt;Expanded documentation&lt;/li&gt;
&lt;li&gt;Deployment guidance and examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example routing configuration:&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;"routing"&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;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"relative"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes generated sites work cleanly when hosted under subfolders or served directly as static files.&lt;/p&gt;

&lt;p&gt;For root-domain hosting:&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;"routing"&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;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where I Think MDL Fits
&lt;/h2&gt;

&lt;p&gt;MDL is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation sites&lt;/li&gt;
&lt;li&gt;Product pages&lt;/li&gt;
&lt;li&gt;Landing pages&lt;/li&gt;
&lt;li&gt;Project websites&lt;/li&gt;
&lt;li&gt;Examples and demos&lt;/li&gt;
&lt;li&gt;Small static sites&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're already happy writing HTML directly, that's great.&lt;/p&gt;

&lt;p&gt;If you've ever wished your page source looked more like an outline and less like markup, MDL might be interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback Welcome
&lt;/h2&gt;

&lt;p&gt;The project is still early and I'm actively improving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Editor support&lt;/li&gt;
&lt;li&gt;Templates&lt;/li&gt;
&lt;li&gt;Diagnostics&lt;/li&gt;
&lt;li&gt;Real-world examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love feedback from people who enjoy static sites, small tools, Markdown-style authoring, and clean HTML.&lt;/p&gt;

&lt;p&gt;Documentation:&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Documentation: &lt;a href="https://getmdl.site" rel="noopener noreferrer"&gt;https://getmdl.site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm package: &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/tosiiko/mdl-code" rel="noopener noreferrer"&gt;https://github.com/tosiiko/mdl-code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @tosiiko/mdl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Questions, criticism, feature requests, and ideas are all welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>staticweb</category>
      <category>markdown</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
