<?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: Mateusz Sadowski</title>
    <description>The latest articles on DEV Community by Mateusz Sadowski (@mateuszmr).</description>
    <link>https://dev.to/mateuszmr</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%2F1070857%2F471df85d-659d-4ac3-9ab6-f28b576d13ca.jpeg</url>
      <title>DEV Community: Mateusz Sadowski</title>
      <link>https://dev.to/mateuszmr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mateuszmr"/>
    <language>en</language>
    <item>
      <title>Recognition tooling for engineering teams (and why we built our own)</title>
      <dc:creator>Mateusz Sadowski</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:50:35 +0000</pubDate>
      <link>https://dev.to/mateuszmr/recognition-tooling-for-engineering-teams-and-why-we-built-our-own-56ip</link>
      <guid>https://dev.to/mateuszmr/recognition-tooling-for-engineering-teams-and-why-we-built-our-own-56ip</guid>
      <description>&lt;h2&gt;
  
  
  The morale problem nobody puts in the sprint board
&lt;/h2&gt;

&lt;p&gt;Distributed engineering teams have a blind spot. You can see build times, PR throughput, incident counts, and cycle time on a dashboard. You cannot see the senior engineer who quietly stopped caring three sprints before they resigned.&lt;/p&gt;

&lt;p&gt;Recognition is the usual answer, and most teams do it badly. It lives in a Slack channel that goes quiet after two weeks, or in a once-a-quarter manager shoutout that everyone forgets by Monday. For engineers specifically, that gap is expensive: O.C. Tanner found 79% of people who quit cite lack of appreciation as a reason (&lt;a href="https://www.octanner.com/global-culture-report" rel="noopener noreferrer"&gt;source&lt;/a&gt;), and replacing a senior engineer runs 6 to 9 months of their salary before you count lost context (SHRM).&lt;/p&gt;

&lt;p&gt;We are &lt;a href="https://themobilereality.com" rel="noopener noreferrer"&gt;Mobile Reality&lt;/a&gt;, a distributed software house. We hit this exact wall on our own team, tried the off-the-shelf options, and eventually built the tool we wanted. This post is the honest version: what recognition tooling should do for an engineering org, what we learned, and where our own product fits. Disclosure up front so you can weight it accordingly: the tool at the end is ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "recognition tooling" should actually do for engineers
&lt;/h2&gt;

&lt;p&gt;Skip the HR-brochure framing. For an engineering team, a recognition tool is worth installing only if it clears a few bars:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It has to live where the work lives.&lt;/strong&gt; Engineers are in Slack, in the terminal, in the PR. A recognition tool that requires opening a separate SaaS portal is dead on arrival. If it is not one message away, nobody uses it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It has to be peer-to-peer, not top-down.&lt;/strong&gt; The most meaningful recognition on an engineering team comes sideways: the person who unblocked your deploy at 6pm, the reviewer who caught the race condition. Manager-only recognition misses 90% of what actually happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It has to produce a signal, not just warm feelings.&lt;/strong&gt; The reason to instrument recognition at all is the same reason you instrument anything: so you can see a trend before it becomes an incident. A recognition channel with no analytics is a nicer version of nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It cannot be gimmicky in a way that engineers reject.&lt;/strong&gt; Points and leaderboards are fine when they are self-aware and optional. They become poison the moment they feel like a performance-review surveillance layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we tried first
&lt;/h2&gt;

&lt;p&gt;Before building anything, we ran the obvious playbook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dedicated Slack channel.&lt;/strong&gt; Free, zero setup, and it worked for about a month. Then it decayed into the same three extroverts posting and everyone else lurking. No history you could query, no way to tell if participation was actually broad or just loud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A points bot bolted onto Slack.&lt;/strong&gt; Better engagement for a while, but the tools we tried were built for generic office teams. The card catalog was cheesy, there was no real analytics layer, and pricing assumed a 5,000-person enterprise, not a lean team in the 50 to 400 band where most software shops actually sit.&lt;/p&gt;

&lt;p&gt;The gap was consistent: either free-and-shapeless, or paid-and-bloated-for-enterprise. Nothing sat in the middle and treated a mid-size distributed team as the primary user.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we ended up building
&lt;/h2&gt;

&lt;p&gt;So we built &lt;a href="https://flaree.app" rel="noopener noreferrer"&gt;Flaree&lt;/a&gt;, and we run our own ~100-person distributed team on it. It is an employee recognition and engagement platform aimed squarely at the 50 to 400 band. A few things we deliberately got right, because they were the things that annoyed us elsewhere:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack-native, web-first.&lt;/strong&gt; Recognition (a "Flaree") is one message inside &lt;a href="https://flaree.app/integration/slack-hr-app" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;, and there is a full web app for everything else. No mandatory separate portal to nag people into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://flaree.app/features/peer-to-peer-recognition" rel="noopener noreferrer"&gt;Peer-to-peer&lt;/a&gt; by default.&lt;/strong&gt; Anyone can recognize anyone. The interesting signal, sideways appreciation between engineers, is the default path, not an afterthought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An &lt;a href="https://flaree.app/integration/api" rel="noopener noreferrer"&gt;API and webhooks&lt;/a&gt;, plus &lt;a href="https://flaree.app/integration/zapier" rel="noopener noreferrer"&gt;Zapier&lt;/a&gt;.&lt;/strong&gt; This is the part engineers actually care about. One of our G2 reviewers, a PM, wired up JIRA automation so recognition fires on ticket events. If you can hit a webhook, you can automate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An engagement dashboard that flags disengagement early.&lt;/strong&gt; Participation and trend data over time, so a manager sees a team going quiet before it turns into a resignation, not after. That was the whole point: turn recognition from a vibe into a measurable habit.&lt;/p&gt;

&lt;p&gt;We kept a permanent free tier (Free Forever, includes Slack) because the Slack-channel crowd should be able to graduate without a sales call, and a 90-day full-feature trial with no credit card for teams that want to pilot the analytics. Paid is a flat few dollars per user per month; the current numbers live on the &lt;a href="https://flaree.app/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; rather than here, since I would rather you check the source than trust a blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it move the needle?
&lt;/h2&gt;

&lt;p&gt;Honestly, recognition tooling is not magic and I am not going to pretend it is. The research is real though: Gallup finds teams with regular recognition see meaningfully higher engagement, and Bersin/Deloitte tied strong recognition cultures to 31% lower voluntary turnover. Our own internal target is 60%+ monthly participation, which is the number we watch because participation breadth is what separates a real culture signal from three loud people in a channel.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://www.g2.com/products/flaree/reviews" rel="noopener noreferrer"&gt;G2&lt;/a&gt; we sit at 4.6/5, all from teams in the mid-size band we built for. The recurring theme in the reviews is that it fits into the daily workflow instead of adding a chore, which, for an engineering audience, is the only review that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways, tool-agnostic
&lt;/h2&gt;

&lt;p&gt;Even if you never touch our product, the pattern holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put recognition where the work already is (Slack, the PR, the terminal), not in a portal.&lt;/li&gt;
&lt;li&gt;Make it peer-to-peer so you capture the sideways appreciation that manager-only tools miss.&lt;/li&gt;
&lt;li&gt;Instrument it. If you cannot see participation trending down, you cannot act before someone leaves.&lt;/li&gt;
&lt;li&gt;Do not over-gamify. Optional and self-aware, never surveillance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see how we approached it, the tool is &lt;a href="https://flaree.app" rel="noopener noreferrer"&gt;Flaree&lt;/a&gt;, and there is a longer writeup of running our own team on it in the &lt;a href="https://flaree.app/blog/case-study-mobile-reality-employee-engagement" rel="noopener noreferrer"&gt;case study&lt;/a&gt;. Happy to answer questions in the comments about the engineering side of building it.&lt;/p&gt;

</description>
      <category>employeeexperience</category>
      <category>flaree</category>
      <category>hr</category>
    </item>
    <item>
      <title>How We Built a Framework That Turns LLM Output Into Interactive UIs</title>
      <dc:creator>Mateusz Sadowski</dc:creator>
      <pubDate>Fri, 27 Mar 2026 21:15:55 +0000</pubDate>
      <link>https://dev.to/mateuszmr/how-we-built-a-framework-that-turns-llm-output-into-interactive-uis-26mo</link>
      <guid>https://dev.to/mateuszmr/how-we-built-a-framework-that-turns-llm-output-into-interactive-uis-26mo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MDMA (Markdown Document with Mounted Applications)&lt;/strong&gt; is an open-source TypeScript framework we built at Mobile Reality to solve a problem that kept showing up in our AI projects: models generate great text, but people need interactive components to actually do something with it. This article explains what problem we hit, how we designed the solution, and what the framework looks like under the hood. If you're building AI-powered tools and tired of writing custom frontends for every use case, this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: AI Output That Nobody Can Act On
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.gartner.com/en/newsroom/press-releases/2025-08-26-gartner-predicts-40-percent-of-enterprise-apps-will-feature-task-specific-ai-agents-by-2026-up-from-less-than-5-percent-in-2025" rel="noopener noreferrer"&gt;Gartner predicts&lt;/a&gt; that 40% of enterprise applications will feature task-specific AI agents by the end of 2026, up from less than 5% in 2025. But there's a gap between what these agents produce and what people can do with the output.&lt;/p&gt;

&lt;p&gt;Here's what we kept seeing across our projects: an LLM generates a loan recommendation, a risk assessment, or a support triage. The output is accurate. Then someone copy-pastes it into a spreadsheet, manually fills a form in another system, and emails a screenshot to their manager for approval.&lt;/p&gt;

&lt;p&gt;The intelligence is there. The interface is not.&lt;/p&gt;

&lt;p&gt;We tried three approaches before building MDMA, and each one failed in its own way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copy-paste workflows.&lt;/strong&gt; The AI generates a summary. Someone re-enters the data into a CRM or ERP. Every transfer introduces errors and breaks audit trails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom frontends per use case.&lt;/strong&gt; Engineering teams build a dedicated form for loan approvals, a separate dashboard for risk assessments, another UI for ticket escalation. According to &lt;a href="https://www.businesswire.com/news/home/20260217548274/en/Retools-2026-Build-vs.-Buy-Report-Reveals-35-of-Enterprises-Have-Already-Replaced-SaaS-With-Custom-Software" rel="noopener noreferrer"&gt;Retool's 2026 Build vs. Buy report&lt;/a&gt;, 78% of enterprises expect to build more custom internal tools this year — but each one still takes weeks of engineering time. Ten AI use cases means ten separate frontends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Just ship a chatbot."&lt;/strong&gt; Wrap the model in a conversation window and call it done. But chat interfaces fail for structured data collection, multi-step workflows, and anything requiring approvals. A &lt;a href="https://www.mdpi.com/2078-2489/16/12/1078" rel="noopener noreferrer"&gt;2024 study published in MDPI Information&lt;/a&gt; found that implementations with dynamic interactive components reduced task completion time by 45.9% compared to conversation-only experiences.&lt;/p&gt;

&lt;p&gt;None of these scale. We needed a single layer that turns any LLM output into actionable UI components — without writing a new frontend every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Markdown, Not JSON
&lt;/h2&gt;

&lt;p&gt;The first design decision was the output format. Most structured output approaches force the model into JSON schemas. We went the opposite direction: extended Markdown.&lt;/p&gt;

&lt;p&gt;The reasoning is practical. According to &lt;a href="https://david-gilbertson.medium.com/llm-output-formats-why-json-costs-more-than-tsv-ebaf590bd541" rel="noopener noreferrer"&gt;analysis by David Gilbertson&lt;/a&gt;, JSON uses roughly twice as many tokens as simpler formats for identical data. Markdown uses approximately 16% fewer tokens compared to JSON. That's real money at scale — and &lt;a href="https://www.cloudidr.com/blog/llm-pricing-comparison-2026" rel="noopener noreferrer"&gt;output tokens cost 3-10x more than input tokens&lt;/a&gt; across major providers.&lt;/p&gt;

&lt;p&gt;But the bigger issue is reasoning quality. GPT-4 scored 81.2% on reasoning tasks with Markdown prompts versus 73.9% with JSON — a 7.3-point gap. JSON wrapping can reduce code generation performance by up to 26%. When you force a model to simultaneously reason about a problem and conform to a rigid schema, both suffer.&lt;/p&gt;

&lt;p&gt;MDMA lets the model write natural Markdown — the format it was trained on — and embed interactive components as YAML blocks inside fenced code sections. The model stays in its comfort zone. The framework handles everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How MDMA Works: The Architecture
&lt;/h2&gt;

&lt;p&gt;MDMA is a monorepo with 8 packages. Each layer has a single job and zero knowledge of the layers above it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec          → Zod schemas defining all 9 component types
parser        → remark plugin: Markdown → AST with validated MDMA blocks
validator     → 17 static analysis rules + 6 auto-fix strategies
runtime       → Headless state management, event log, policy engine
attachables   → 7 component handlers (form, button, approval-gate, etc.)
renderer-react → React components + hooks
prompt-pack   → System prompts that teach LLMs the MDMA format
cli           → Interactive prompt builder + document validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a concrete example. A model generates this response for a loan triage workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Based on the submitted documents, this application qualifies for review.

&lt;span class="sb"&gt;` ``mdma
id: loan-assessment
type: form
fields:
  - name: applicant_name
    type: text
    label: Applicant Name
    required: true
    sensitive: true
  - name: risk_score
    type: select
    label: Risk Classification
    options:
      - { label: "Low Risk", value: low }
      - { label: "Medium Risk", value: medium }
      - { label: "High Risk", value: high }
onSubmit: submit-assessment
`&lt;/span&gt; &lt;span class="sb"&gt;``

` ``&lt;/span&gt;mdma
id: manager-approval
type: approval-gate
title: Senior Manager Approval
requiredApprovers: 1
allowedRoles:
&lt;span class="p"&gt;  -&lt;/span&gt; senior-manager
onApprove: proceed-to-underwriting
onDeny: return-to-analyst
requireReason: true
&lt;span class="sb"&gt;` `&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser extracts those YAML blocks, validates them against Zod schemas, and produces a typed AST. The renderer turns them into interactive form fields and an approval gate. The runtime captures every field change and approval decision in a tamper-evident event log with automatic PII redaction.&lt;/p&gt;

&lt;p&gt;No custom frontend was written. One renderer handles every document.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 9 Component Types
&lt;/h2&gt;

&lt;p&gt;MDMA ships with nine built-in component types. Each one solves a specific interaction pattern we hit repeatedly in production:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Form&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-field data collection with validation, required flags, and PII sensitivity markers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Button&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Action trigger with optional confirmation dialog (primary, secondary, danger variants)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tasklist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Checklist where items can be individually checked off, with an onComplete action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Table&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sortable, filterable data display with pagination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alert banners (info, warning, error, success) — dismissible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Approval Gate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Workflow blocker requiring N approvers with role restrictions and denial reasons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Webhook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP trigger with retries, timeout, and policy-gated execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data visualization (line, bar, area, pie) — renders as table by default to avoid a 400KB charting dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Thinking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Collapsible AI reasoning block showing chain-of-thought&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every component shares a base schema: a unique &lt;code&gt;id&lt;/code&gt;, a &lt;code&gt;type&lt;/code&gt;, and optional &lt;code&gt;sensitive&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;, and &lt;code&gt;visible&lt;/code&gt; flags. The &lt;code&gt;disabled&lt;/code&gt; and &lt;code&gt;visible&lt;/code&gt; properties accept binding expressions like &lt;code&gt;"{{form-id.field-name}}"&lt;/code&gt;, so components react to each other without custom code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes This Different From Chat UIs
&lt;/h2&gt;

&lt;p&gt;Tools like Open WebUI provide an excellent environment for working with models — managing conversations, configuring parameters, switching between providers. But they focus on the conversation experience itself.&lt;/p&gt;

&lt;p&gt;MDMA operates one layer below. It defines how model output becomes actionable interface components regardless of which shell or application sits on top. You could embed MDMA-rendered documents inside Open WebUI, inside a custom agent dashboard, or inside an internal tool — the documents remain the same.&lt;/p&gt;

&lt;p&gt;The difference matters for three reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured data capture.&lt;/strong&gt; A chat response says "the risk score is medium." An MDMA form lets the analyst select "medium" from a dropdown, which writes structured data to your system of record. No copy-paste, no re-entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approval workflows.&lt;/strong&gt; Chat can't enforce that a senior manager signs off before a process continues. An MDMA approval gate blocks the workflow until someone with the right role approves or denies — with a required reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit trails.&lt;/strong&gt; Every interaction in MDMA is logged with timestamps, actor IDs, and component references. The &lt;code&gt;ChainedEventLog&lt;/code&gt; adds hash chaining for tamper-evident records. When compliance asks "who approved what, and when?" — you have a verifiable answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Features We Had to Build
&lt;/h2&gt;

&lt;p&gt;Three features turned out to be non-negotiable for production use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PII redaction.&lt;/strong&gt; Fields marked &lt;code&gt;sensitive: true&lt;/code&gt; are automatically redacted before logging. The runtime detects five PII categories — email, phone, SSN, credit card, name patterns — and applies one of three strategies: hash (default), mask, or omit. The event log never contains plain PII for sensitive fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy engine.&lt;/strong&gt; We needed to prevent dangerous operations in non-production environments. The policy engine evaluates rules per environment: block webhooks in preview, block emails in staging, allow everything in production. One line of config prevents a developer from accidentally firing a live API call during testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webhook_call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;environments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deny&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="na"&gt;defaultEffect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tamper-evident event log.&lt;/strong&gt; Every event — field change, approval, button click, webhook call — is recorded with a sequence number, a hash of the current entry, and the hash of the previous entry. Verification is a single method call: &lt;code&gt;log.verifyIntegrity()&lt;/code&gt; returns &lt;code&gt;{ valid: true }&lt;/code&gt; or points to the broken link. This was a hard requirement from our fintech clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching LLMs to Write MDMA
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't the parser or the renderer — it was getting models to produce valid MDMA documents reliably. We solved this with the &lt;code&gt;prompt-pack&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;The MDMA author prompt is 327 lines of structured instructions that teach any LLM the exact syntax: document format, all nine component types with YAML examples, binding syntax with quoting rules, and a self-check checklist the model runs before finalizing output.&lt;/p&gt;

&lt;p&gt;Usage is simple:&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;buildSystemPrompt&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="s1"&gt;@mobile-reality/mdma-prompt-pack&lt;/span&gt;&lt;span class="dl"&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;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a bug-tracking assistant. When a user reports
    a bug, generate a form with severity, steps, expected, actual.&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 function always includes the full MDMA spec regardless of what custom instructions you provide. This prevents the model from "forgetting" MDMA rules in long conversations.&lt;/p&gt;

&lt;p&gt;We validate generation quality with a &lt;code&gt;promptfoo&lt;/code&gt;-based evaluation suite — 25+ test cases covering structural correctness, semantic appropriateness, and multi-turn consistency across multiple models.&lt;/p&gt;

&lt;p&gt;We also went one step further and fine-tuned our own model for the job: &lt;a href="https://huggingface.co/MobileReality/mdma-gemma4-26b-dsl-unsloth-v1" rel="noopener noreferrer"&gt;&lt;code&gt;mdma-gemma4-26b-dsl-unsloth-v1&lt;/code&gt;&lt;/a&gt; (Gemma-based, tuned for the MDMA DSL) is published on Hugging Face, so you can generate MDMA without depending on a frontier API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install the packages you need:&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; @mobile-reality/mdma-parser @mobile-reality/mdma-runtime &lt;span class="se"&gt;\&lt;/span&gt;
  @mobile-reality/mdma-renderer-react @mobile-reality/mdma-prompt-pack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parse and render a document:&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;unified&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="s1"&gt;unified&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="nx"&gt;remarkParse&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remark-parse&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;remarkMdma&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="s1"&gt;@mobile-reality/mdma-parser&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;createDocumentStore&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="s1"&gt;@mobile-reality/mdma-runtime&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;MdmaRoot&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="s1"&gt;@mobile-reality/mdma-spec&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;MdmaDocument&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="s1"&gt;@mobile-reality/mdma-renderer-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Parse markdown into a typed MDMA AST (the parser is a remark plugin)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unified&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remarkParse&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remarkMdma&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;ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdownString&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;MdmaRoot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Create a reactive document store&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDocumentStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-doc&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="c1"&gt;// 3. Render it in React&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MdmaDocument&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository includes 10+ examples (from basic forms to approval workflows), 5 production blueprints (incident triage, KYC, clinical ops, customer escalation, change management), and the interactive CLI for building custom prompts.&lt;/p&gt;

&lt;p&gt;If you use an MCP-compatible assistant (Claude Desktop, Cursor, VS Code), the &lt;code&gt;@mobile-reality/mdma-mcp&lt;/code&gt; server exposes the spec, authoring prompts, and docs directly to the model — so it can write valid MDMA with zero setup. Full docs live at &lt;a href="https://mdma.software" rel="noopener noreferrer"&gt;mdma.software&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We built MDMA because we kept solving the same problem on every AI project: the model output was good, but the last mile to the user was broken. Instead of building a new frontend for each use case, we needed one framework that turns any LLM response into interactive components.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Markdown-native format&lt;/strong&gt; — models write what they're trained on, with 16% fewer tokens than JSON and measurably better reasoning quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nine built-in components&lt;/strong&gt; — forms, approval gates, tables, webhooks, and more — covering the interaction patterns that come up in real enterprise workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise-ready from the start&lt;/strong&gt; — tamper-evident audit logs, automatic PII redaction, and environment-based policy enforcement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider-agnostic&lt;/strong&gt; — works with OpenAI, Anthropic, Google, or local models through Ollama. Switching is a config change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layered architecture&lt;/strong&gt; — use the parser without the renderer, the runtime without React, or the prompt-pack standalone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framework is open-source, MIT-licensed, and ready to use — the project site is &lt;a href="https://mdma.software" rel="noopener noreferrer"&gt;mdma.software&lt;/a&gt;, and the code lives at &lt;a href="https://github.com/MobileReality/mdma" rel="noopener noreferrer"&gt;github.com/MobileReality/mdma&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to try it, the fastest path is: install the packages, use the CLI prompt builder to generate a system prompt for your use case, point it at your model, and render the output. The 10 included examples cover everything from a simple contact form to a multi-step approval workflow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm &lt;a href="https://www.linkedin.com/in/ms-mobilereality/" rel="noopener noreferrer"&gt;Matt Sadowski&lt;/a&gt;, CEO at &lt;a href="https://themobilereality.com/services/ai-automation-agency" rel="noopener noreferrer"&gt;Mobile Reality&lt;/a&gt;. We build AI agents and automation systems for fintech and proptech companies. If you're working on a similar problem — turning AI output into something people can act on — I'd be happy to compare notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Five server-side rendering frameworks that you should consider in 2023</title>
      <dc:creator>Mateusz Sadowski</dc:creator>
      <pubDate>Thu, 28 Sep 2023 13:37:17 +0000</pubDate>
      <link>https://dev.to/mateuszmr/five-server-side-rendering-frameworks-that-you-should-consider-in-2023-3m2b</link>
      <guid>https://dev.to/mateuszmr/five-server-side-rendering-frameworks-that-you-should-consider-in-2023-3m2b</guid>
      <description>&lt;p&gt;&lt;strong&gt;GatsbyJS: The Static Site Generator Turned SSR Powerhouse&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://themobilereality.com/blog/gatsbyjs-the-ultimate-guideline"&gt;GatsbyJS&lt;/a&gt; is known for its blazing-fast performance and seamless integration with GraphQL.&lt;/li&gt;
&lt;li&gt;It enables developers to build websites that are not only SSR-enabled but also optimized for SEO out of the box.&lt;/li&gt;
&lt;li&gt;The robust ecosystem of plugins and themes simplifies development.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use Cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://themobilereality.com/blog/next-js-vs-gatsby"&gt;GatsbyJS&lt;/a&gt; is ideal for creating static websites, blogs, e-commerce sites, and documentation sites.&lt;/li&gt;
&lt;li&gt;Its SSR capabilities make it a great choice for content-heavy applications that require fast initial loading times.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;NextJS: The SSR Framework of Choice for React Developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developed by Vercel, &lt;a href="https://themobilereality.com/blog/nextjs-server-sider-rendering-guidline"&gt;Next.js&lt;/a&gt; is a React framework that offers SSR as a core feature.&lt;/li&gt;
&lt;li&gt;It provides an intuitive API for server-side rendering and dynamic routing.&lt;/li&gt;
&lt;li&gt;Automatic code splitting and prefetching improve performance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use Cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://themobilereality.com/blog/next-js-vs-gatsby"&gt;Next.js&lt;/a&gt; is particularly suitable for building modern web applications, including e-commerce platforms, dashboards, and interactive websites.&lt;/li&gt;
&lt;li&gt;Its flexibility allows developers to choose between full SSR or hybrid rendering (SSR for critical pages and client-side rendering for the rest).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Nuxt.js: The SSR Framework for Vue.js Enthusiasts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nuxt.js brings server-side rendering to Vue.js applications with minimal setup.&lt;/li&gt;
&lt;li&gt;It offers powerful features like Vuex, Vue Router, and automatic code splitting.&lt;/li&gt;
&lt;li&gt;Nuxt's module system simplifies integration of third-party functionality.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use Cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dotcommagazine.com/2023/07/nuxtjs-top-five-powerful-things-you-need-to-know/"&gt;Nuxt.js&lt;/a&gt; is the go-to choice for developers building SSR-enabled Vue.js applications.&lt;/li&gt;
&lt;li&gt;It's well-suited for creating various web applications, from single-page apps to larger projects with multiple routes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Sapper (SvelteKit): The SSR Solution for the Svelte Ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sapper, which has evolved into SvelteKit, is a framework for building SSR-enabled web applications using the Svelte framework.&lt;/li&gt;
&lt;li&gt;It offers an efficient way to create SSR applications with minimal boilerplate code.&lt;/li&gt;
&lt;li&gt;Svelte's reactive programming model enhances performance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use Cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://learn.habilelabs.io/sveltekit-a-new-svelte-framework-to-supersede-sapperjs-246e3eaa9fd7"&gt;Sapper/SvelteKit&lt;/a&gt; is an excellent choice for developers who prefer the Svelte framework and want to build SSR applications.&lt;/li&gt;
&lt;li&gt;It's suitable for a wide range of projects, from simple web apps to complex enterprise solutions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Angular Universal: SSR for Angular Applications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://blog.angular.io/whats-next-for-server-side-rendering-in-angular-2a6f27662b67"&gt;Angular Universal&lt;/a&gt; is the official solution for server-side rendering in the Angular ecosystem.&lt;/li&gt;
&lt;li&gt;It allows developers to build SEO-friendly Angular applications with minimal effort.&lt;/li&gt;
&lt;li&gt;Angular's robust tooling and dependency injection are available on the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use Cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you are committed to using Angular for your frontend, Angular Universal is the natural choice for adding SSR capabilities.&lt;/li&gt;
&lt;li&gt;It's well-suited for enterprise-level applications and large-scale projects.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gatsby</category>
      <category>nextjs</category>
      <category>sapper</category>
      <category>nuxt</category>
    </item>
  </channel>
</rss>
