<?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 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>
    <item>
      <title>What's coming in MDL 0.1.3 — source maps, metadata, diagnostics, and more</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sun, 31 May 2026 18:55:26 +0000</pubDate>
      <link>https://dev.to/tosiiko/whats-coming-in-mdl-013-source-maps-metadata-diagnostics-and-more-4o29</link>
      <guid>https://dev.to/tosiiko/whats-coming-in-mdl-013-source-maps-metadata-diagnostics-and-more-4o29</guid>
      <description>&lt;p&gt;I've been building MDL — a tiny authoring language that compiles to clean HTML. If you haven't seen it yet:&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:
    ## Sign in
    Welcome back.

  form@id(loginForm)@submit(handleLogin):
    field:
      label:
        Email
      .input@type(email)@id(email)@required

    actions:
      .btn-primary@id(loginBtn)(Sign in)
      .btn-ghost@click(handleForgot)(Forgot password?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiles to clean, semantic HTML. No angle brackets written by hand. CSS owns all layout. JS handles all logic.&lt;/p&gt;

&lt;p&gt;Install: &lt;code&gt;npm install -g @tosiiko/mdl&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's already shipped in 0.1.x
&lt;/h2&gt;

&lt;p&gt;Before getting into what's coming — here's what's already working:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Indentation-based sections that compile to semantic HTML (&lt;code&gt;page:&lt;/code&gt; → &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, &lt;code&gt;nav:&lt;/code&gt; → &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;form:&lt;/code&gt; → &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Full CommonMark Markdown inside every section — headings, lists, tables, code blocks, blockquotes&lt;/li&gt;
&lt;li&gt;Dot inline elements — &lt;code&gt;.badge(text)&lt;/code&gt;, &lt;code&gt;.btn-primary(text)&lt;/code&gt;, &lt;code&gt;.input@type(email)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Attributes — &lt;code&gt;@id(value)&lt;/code&gt;, &lt;code&gt;@required&lt;/code&gt;, &lt;code&gt;@placeholder(text)&lt;/code&gt;, &lt;code&gt;@submit(fn)&lt;/code&gt;, &lt;code&gt;@click(fn)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CSS bundling — multiple CSS files merged into one &lt;code&gt;app.css&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JavaScript event wiring — &lt;code&gt;@submit(handleLogin)&lt;/code&gt; compiles to a proper &lt;code&gt;addEventListener&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Live reload dev server — &lt;code&gt;mdl serve&lt;/code&gt; at &lt;code&gt;localhost:3999&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Multi-page projects via &lt;code&gt;mdl.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mdl init&lt;/code&gt; and &lt;code&gt;mdl new&lt;/code&gt; — scaffold a full project in one command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mdl check&lt;/code&gt; and &lt;code&gt;mdl format&lt;/code&gt; — lint and auto-format&lt;/li&gt;
&lt;li&gt;VS Code extension with syntax highlighting — already on the marketplace&lt;/li&gt;
&lt;li&gt;Prebuilt Rust binary distributed via npm — no Rust toolchain needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's 0.1. Here's what's coming next.&lt;/p&gt;




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

&lt;p&gt;Every compiled HTML page will write a JSON sidecar map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pages/login.mdl  →  dist/login.html
                 →  dist/login.html.mdlmap.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The map links every generated HTML element back to the MDL source line that produced it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pages/login.mdl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"login.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"generated"&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;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end_column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&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;"source"&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;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end_column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;39&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;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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 unlocks editor navigation, error overlays with exact MDL line and column, and eventually go-to-source from generated HTML back to the &lt;code&gt;.mdl&lt;/code&gt; file that produced it — the same way browser source maps let you debug TypeScript instead of compiled JS.&lt;/p&gt;

&lt;p&gt;A project-level &lt;code&gt;dist/mdl-manifest.json&lt;/code&gt; links every route to its source and source map:&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;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"login.mdl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"login.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_map"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"login.html.mdlmap.json"&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;
  
  
  Document metadata
&lt;/h2&gt;

&lt;p&gt;Right now metadata lives in &lt;code&gt;mdl.json&lt;/code&gt;. The next release formalises and extends this with a complete metadata system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&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;"lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Site"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A small MDL site."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"canonical"&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://example.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"favicon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets/favicon.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"social"&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;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets/social-card.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"twitter_site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@mysite"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;social&lt;/code&gt; preset derives Open Graph and Twitter card tags automatically from &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;canonical&lt;/code&gt;. One config, full social sharing coverage:&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;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"My Site"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A small MDL site."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"assets/social-card.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:site"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"@mysite"&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;mdl check validates all local file references — favicon, social image, manifest links — at build time, not when a user finds a broken image in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Better diagnostics
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mdl check&lt;/code&gt; is getting significantly smarter. The next release adds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility checks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing &lt;code&gt;alt&lt;/code&gt; on images&lt;/li&gt;
&lt;li&gt;Missing &lt;code&gt;title&lt;/code&gt; on iframes&lt;/li&gt;
&lt;li&gt;Duplicate IDs&lt;/li&gt;
&lt;li&gt;Unlabeled form controls&lt;/li&gt;
&lt;li&gt;Invalid or empty &lt;code&gt;aria-*&lt;/code&gt; attributes&lt;/li&gt;
&lt;li&gt;Missing ARIA references&lt;/li&gt;
&lt;li&gt;Landmark ordering issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Form checks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Label and input pairing mistakes&lt;/li&gt;
&lt;li&gt;Missing label targets&lt;/li&gt;
&lt;li&gt;Ungrouped radio buttons&lt;/li&gt;
&lt;li&gt;Missing autocomplete hints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Asset checks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Broken &lt;code&gt;.link()&lt;/code&gt; and &lt;code&gt;.href()&lt;/code&gt; targets&lt;/li&gt;
&lt;li&gt;Missing local images and media files&lt;/li&gt;
&lt;li&gt;Recursive CSS &lt;code&gt;url()&lt;/code&gt; and &lt;code&gt;@import&lt;/code&gt; validation&lt;/li&gt;
&lt;li&gt;Font file references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Machine-readable output:&lt;/strong&gt;&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 &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs structured diagnostics with 1-based line and column ranges — ready for editor integrations, CI pipelines, and language server consumers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-app workspaces
&lt;/h2&gt;

&lt;p&gt;One install, multiple apps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mdl init &lt;span class="nt"&gt;--app&lt;/span&gt; admin &lt;span class="nt"&gt;--port&lt;/span&gt; 4001
mdl serve apps/admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each app owns its own &lt;code&gt;mdl.json&lt;/code&gt;, pages, CSS, and scripts. They share the same MDL install and environment. Useful for projects with a public site and a separate admin panel, or a marketing page and a docs site.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advanced browser primitives
&lt;/h2&gt;

&lt;p&gt;The next release adds first-class MDL sections for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;canvas:      →  &amp;lt;canvas&amp;gt;   WebGL, charts, custom drawing
frame:       →  &amp;lt;iframe&amp;gt;   embedded content
picture:     →  &amp;lt;picture&amp;gt;  responsive images
progress:    →  &amp;lt;progress&amp;gt; loading bars
meter:       →  &amp;lt;meter&amp;gt;    scalar measurements
template:    →  &amp;lt;template&amp;gt; inert DOM templates
slot:        →  &amp;lt;slot&amp;gt;     web component slots
island:      →  &amp;lt;div&amp;gt;      hydrated JS island host
component:   →  &amp;lt;div&amp;gt;      custom component host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;@mount(handler)&lt;/code&gt; wiring for JS initialisation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;canvas@id(scene)@mount(drawScene):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiles to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; that calls &lt;code&gt;drawScene()&lt;/code&gt; from your configured scripts on mount.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'm building toward
&lt;/h2&gt;

&lt;p&gt;MDL's goal is simple: you should be able to build a complete, production-quality web page without writing a single HTML tag. Structure in &lt;code&gt;.mdl&lt;/code&gt;. Design in &lt;code&gt;.css&lt;/code&gt;. Logic in &lt;code&gt;.js&lt;/code&gt;. The compiler handles everything else.&lt;/p&gt;

&lt;p&gt;The next release gets closer to that by making the tooling smarter — better errors, better source maps, better metadata, better workspace support.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it now
&lt;/h2&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-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-site
mdl serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;npm — &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl/v/0.1.2" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl/v/0.1.2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;VS Code — &lt;a href="https://marketplace.visualstudio.com/items?itemName=MDLLanguageSupport.mdl-language" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=MDLLanguageSupport.mdl-language&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;Would love to hear what you think — and what you'd want to see in MDL before you'd use it on a real project.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>rust</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>npm i @tosiiko/mdl</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sat, 30 May 2026 10:44:07 +0000</pubDate>
      <link>https://dev.to/tosiiko/npm-i-tosiikomdl-280n</link>
      <guid>https://dev.to/tosiiko/npm-i-tosiikomdl-280n</guid>
      <description>&lt;p&gt;The parser is written in Rust using pulldown-cmark for Markdown. It ships as a prebuilt binary via npm so no Rust toolchain is needed.&lt;/p&gt;

&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl/v/0.1.2" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl/v/0.1.2&lt;/a&gt;&lt;br&gt;
VS Code: &lt;a href="https://marketplace.visualstudio.com/items?itemName=MDLLanguageSupport.mdl-language" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=MDLLanguageSupport.mdl-language&lt;/a&gt;&lt;br&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;/p&gt;

</description>
      <category>programming</category>
      <category>rust</category>
      <category>showdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>I built MDL — a tiny authoring language so you never write raw HTML again</title>
      <dc:creator>Tosiiko</dc:creator>
      <pubDate>Sat, 30 May 2026 10:35:39 +0000</pubDate>
      <link>https://dev.to/tosiiko/i-built-mdl-a-tiny-authoring-language-so-you-never-write-raw-html-again-1mkf</link>
      <guid>https://dev.to/tosiiko/i-built-mdl-a-tiny-authoring-language-so-you-never-write-raw-html-again-1mkf</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every web project starts the same way. You open a blank file and start typing angle brackets.&lt;/p&gt;

&lt;p&gt;HTML is fine. But writing it by hand is noisy, repetitive, and forces you to think about tags instead of content.&lt;/p&gt;

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

&lt;p&gt;MDL (Markdown Language) is a tiny authoring language that compiles to clean, semantic HTML.&lt;/p&gt;

&lt;p&gt;The idea is three rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.mdl&lt;/code&gt; files handle structure and content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.css&lt;/code&gt; files handle layout and design&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.js&lt;/code&gt; files handle logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You never write a single HTML tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page:
  hero:
    ## Sign in
    Welcome back.

  form@id(loginForm)@submit(handleLogin):
    field:
      label:
        Email
      .input@type(email)@id(email)@required

    actions:
      .btn-primary@id(loginBtn)(Sign in)
      .btn-ghost@click(handleForgot)(Forgot password?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That compiles to:&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;h2&amp;gt;&lt;/span&gt;Sign in&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Welcome back.&lt;span class="nt"&gt;&amp;lt;/p&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;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;id=&lt;/span&gt;&lt;span class="s"&gt;"loginForm"&lt;/span&gt; &lt;span class="na"&gt;data-mdl-on-submit=&lt;/span&gt;&lt;span class="s"&gt;"handleLogin"&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-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&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;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&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;required&lt;/span&gt;&lt;span class="nt"&gt;&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mdl-actions"&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;id=&lt;/span&gt;&lt;span class="s"&gt;"loginBtn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign in&lt;span class="nt"&gt;&amp;lt;/button&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-ghost"&lt;/span&gt; &lt;span class="na"&gt;data-mdl-on-click=&lt;/span&gt;&lt;span class="s"&gt;"handleForgot"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Forgot password?&lt;span class="nt"&gt;&amp;lt;/button&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;/form&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;Clean, semantic HTML. No divs you didn't ask for. No inline styles. Every element has a predictable &lt;code&gt;.mdl-*&lt;/code&gt; class that CSS can target.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CSS and JS talk to it
&lt;/h2&gt;

&lt;p&gt;CSS targets &lt;code&gt;.mdl-*&lt;/code&gt; classes directly. No configuration needed.&lt;/p&gt;

&lt;p&gt;JS uses standard DOM APIs. The &lt;code&gt;@id()&lt;/code&gt; attribute compiles to a real HTML &lt;code&gt;id&lt;/code&gt;. The &lt;code&gt;@submit()&lt;/code&gt; and &lt;code&gt;@click()&lt;/code&gt; attributes wire events to functions in your external JS file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/auth.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;email&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="s1"&gt;#email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="c1"&gt;// your logic here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No framework. No runtime. No magic. Just the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install
&lt;/h2&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 &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI is a prebuilt Rust binary. Fast, no dependencies, works on Mac, Linux, and Windows.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;npm — &lt;a href="https://www.npmjs.com/package/@tosiiko/mdl/v/0.1.2" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@tosiiko/mdl/v/0.1.2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;VS Code extension — &lt;a href="https://marketplace.visualstudio.com/items?itemName=MDLLanguageSupport.mdl-language" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=MDLLanguageSupport.mdl-language&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;Would love to hear what you think.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>rust</category>
      <category>css</category>
    </item>
  </channel>
</rss>
