<?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>Forem: Eduard Maghakyan</title>
    <description>The latest articles on Forem by Eduard Maghakyan (@eduardmaghakyan).</description>
    <link>https://forem.com/eduardmaghakyan</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F113916%2Fe7f663e0-7b71-480a-9cd2-4b528c788f84.jpeg</url>
      <title>Forem: Eduard Maghakyan</title>
      <link>https://forem.com/eduardmaghakyan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/eduardmaghakyan"/>
    <language>en</language>
    <item>
      <title>IPE v0.1.17 - Keyboard Shortcuts, Crash Recovery &amp; macOS Fix</title>
      <dc:creator>Eduard Maghakyan</dc:creator>
      <pubDate>Thu, 16 Apr 2026 10:30:55 +0000</pubDate>
      <link>https://forem.com/eduardmaghakyan/ipe-v0117-keyboard-shortcuts-crash-recovery-macos-fix-2k9</link>
      <guid>https://forem.com/eduardmaghakyan/ipe-v0117-keyboard-shortcuts-crash-recovery-macos-fix-2k9</guid>
      <description>&lt;p&gt;A quick update on &lt;a href="https://github.com/EduardMaghakyan/ipe" rel="noopener noreferrer"&gt;IPE&lt;/a&gt; - the local PR-review UI for Claude Code plans. If you missed the original announcement, &lt;a href="https://dev.to/eduardmaghakyan/building-a-local-pr-review-interface-for-claude-code-plans-57o2"&gt;check it out here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's what's new since v0.1.15:&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard Shortcuts
&lt;/h2&gt;

&lt;p&gt;You can now navigate the review flow without touching the mouse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift+Tab&lt;/strong&gt; - Accept the plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;x&lt;/strong&gt; - Request changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;c&lt;/strong&gt; - Jump to the comment box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;?&lt;/strong&gt; - Toggle the shortcuts overlay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shortcut hints are displayed directly on the buttons, so you don't have to memorize them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Crash Recovery
&lt;/h2&gt;

&lt;p&gt;If the IPE server dies unexpectedly (killed process, laptop sleep, etc.), the client now recovers gracefully instead of hanging forever. New sessions also correctly open the browser when connecting to an already-running server.&lt;/p&gt;

&lt;h2&gt;
  
  
  macOS Binary Fix
&lt;/h2&gt;

&lt;p&gt;If you upgraded to v0.1.15 or v0.1.16 and IPE suddenly stopped launching (getting &lt;code&gt;killed&lt;/code&gt; in the terminal), this was caused by macOS Gatekeeper blocking the unsigned binary. v0.1.17 fixes this - the release binary is now properly ad-hoc signed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're affected&lt;/strong&gt;, just re-run the installer:&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/eduardmaghakyan/ipe/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;More improvements coming soon. If you have feedback or ideas, &lt;a href="https://github.com/EduardMaghakyan/ipe/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or drop a comment below!&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Generate a PDF from JSON with one API call</title>
      <dc:creator>Eduard Maghakyan</dc:creator>
      <pubDate>Sat, 11 Apr 2026 23:43:51 +0000</pubDate>
      <link>https://forem.com/eduardmaghakyan/generate-a-pdf-from-json-with-one-api-call-5g08</link>
      <guid>https://forem.com/eduardmaghakyan/generate-a-pdf-from-json-with-one-api-call-5g08</guid>
      <description>&lt;p&gt;If you've ever needed to email a quarterly report from a cron job, attach an invoice to a Zapier workflow, or ship a weekly digest from an n8n trigger, you've hit the same wall I've been hitting for years: &lt;strong&gt;generating a PDF programmatically is weirdly hard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The options today are all flavors of the same compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exists in 2026
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PDFMonkey&lt;/strong&gt; - Starter €5/mo for 300 documents, Pro €15/mo for 3,000. You design your template in their visual editor, then POST variables to fill it in. Solid editor, reasonable pricing. The friction is that every new document shape means a trip back to the visual tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APITemplate.io&lt;/strong&gt; - PDF Basic $19/mo (annual) for 3,000 PDFs. Same core idea as PDFMonkey: design a template in a web editor, then POST JSON data to render it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DocRaptor&lt;/strong&gt; - from $15/mo, powered by Prince. HTML-in, PDF-out - so no template editor, but you own the problem of producing print-ready HTML and CSS (page breaks, headers, footers, paged media rules). Powerful if you already have a styled HTML pipeline; more work if you don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rolling your own&lt;/strong&gt; - Puppeteer or Chromium in a Lambda, &lt;code&gt;@react-pdf/renderer&lt;/code&gt;, or &lt;code&gt;wkhtmltopdf&lt;/code&gt; in a Docker image. Works until you need it to not-work: font loading, CJK support, memory limits, cold starts, timeouts, and the joy of debugging headless Chrome in CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern&lt;/strong&gt;: most hosted PDF APIs either hand you a template editor (design once, fill slots later) or hand you an HTML-to-PDF pipe (bring your own rendered HTML). Both are fine when the layout is known ahead of time. Both are friction when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The shape of your data is determined at runtime (e.g., an LLM decides what to render)&lt;/li&gt;
&lt;li&gt;You want multiple outputs from the same pipeline - a PDF, a web view, and a raw data table&lt;/li&gt;
&lt;li&gt;You need a dashboard with metrics + charts + tables composed together, not a static invoice&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one-API-call approach
&lt;/h2&gt;

&lt;p&gt;A few months ago I started building &lt;a href="https://genui.sh" rel="noopener noreferrer"&gt;genui.sh&lt;/a&gt; for the workflows I couldn't make the existing tools fit. The pitch is simple: POST a JSON payload describing what you want, get back a signed URL.&lt;/p&gt;

&lt;p&gt;Here's the smallest version - a markdown document rendered to a hosted PDF:&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;-X&lt;/span&gt; POST https://www.genui.sh/api/artifacts/share &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$GENUI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "template": "pdf",
    "title": "Q4 Report",
    "content": {
      "text": "# Q4 2025 Performance\n\nRevenue grew **23%** year-over-year.\n\n## Highlights\n- Launched mobile app\n- Expanded to 3 new markets",
      "pageSize": "A4"
    },
    "expiresIn": "7d"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"data"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"550e8400-e29b-41d4-a716-446655440000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&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://genui.sh/a/550e8400-e29b-41d4-a716-446655440000?token=eyJhbGc..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-19T14:30:45.123Z"&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;strong&gt;See it live:&lt;/strong&gt; &lt;a href="https://www.genui.sh/a/311f0511-376f-4221-8c4b-6193a3f0a822?token=eyJhbGciOiJIUzI1NiJ9.eyJhcnRpZmFjdElkIjoiMzExZjA1MTEtMzc2Zi00MjIxLThjNGItNjE5M2EzZjBhODIyIiwidXNlcklkIjoiZTQ2MDdjYjUtNjBlMi00YjRjLWI2MWYtMWE1YjE5YWNkZWM2IiwiaWF0IjoxNzc1OTQ3NTQ3LCJzdWIiOiIzMTFmMDUxMS0zNzZmLTQyMjEtOGM0Yi02MTkzYTNmMGE4MjIifQ.CimDhX4kiZ7Sh5I-TX-977Fm43pytkVofIjYEQ9YhyY" rel="noopener noreferrer"&gt;Q4 report PDF →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmw0hi0owom22we7pg4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmw0hi0owom22we7pg4v.png" alt="Q4 report PDF preview" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the whole workflow. No template to design first, no visual editor to maintain, no data-shape gymnastics. The &lt;code&gt;content&lt;/code&gt; field is just markdown - the same kind you'd write in a README. The &lt;code&gt;expiresIn&lt;/code&gt; field is optional: on Pro it defaults to 30 days, so pass &lt;code&gt;"never"&lt;/code&gt; explicitly if you want a permanent link; on Free and Starter it's clamped to the plan max (7 and 30 days respectively). And &lt;code&gt;Authorization&lt;/code&gt; accepts either &lt;code&gt;Bearer &amp;lt;key&amp;gt;&lt;/code&gt; or the raw key, whichever your HTTP client prefers.&lt;/p&gt;

&lt;p&gt;If you want a table instead of prose:&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="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "template": "table",
  "title": "Recent orders",
  "content": {
    "columns": [
      {"header": "Order ID", "accessorKey": "id"},
      {"header": "Customer", "accessorKey": "customer"},
      {"header": "Amount", "accessorKey": "amount"}
    ],
    "rows": [
      {"id": "ORD-001", "customer": "Acme Corp", "amount": "$1,250"},
      {"id": "ORD-002", "customer": "Globex", "amount": "$890"},
      {"id": "ORD-003", "customer": "Initech", "amount": "$3,420"},
      {"id": "ORD-004", "customer": "Umbrella", "amount": "$560"},
      {"id": "ORD-005", "customer": "Stark Industries", "amount": "$7,800"}
    ],
    "config": {
      "enableSearch": true,
      "enablePagination": true,
      "enableExport": true
    }
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;See it live:&lt;/strong&gt; &lt;a href="https://www.genui.sh/a/13162dc6-b76b-4a9b-b96f-df827adddedf?token=eyJhbGciOiJIUzI1NiJ9.eyJhcnRpZmFjdElkIjoiMTMxNjJkYzYtYjc2Yi00YTliLWI5NmYtZGY4MjdhZGRkZWRmIiwidXNlcklkIjoiZTQ2MDdjYjUtNjBlMi00YjRjLWI2MWYtMWE1YjE5YWNkZWM2IiwiaWF0IjoxNzc1OTQ3ODgxLCJzdWIiOiIxMzE2MmRjNi1iNzZiLTRhOWItYjk2Zi1kZjgyN2FkZGRlZGYifQ.Bg0vpqU5jr2bj7gLs3Sr7oHQ5TlSN8-ABjDI1Cs7l6s" rel="noopener noreferrer"&gt;Orders table →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczbysvy7znpp2h2br4iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczbysvy7znpp2h2br4iw.png" alt="Orders table preview" width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or a bar chart:&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="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "template": "chart",
  "title": "Monthly revenue",
  "content": {
    "type": "bar",
    "data": [
      {"name": "Jan", "value": 84000},
      {"name": "Feb", "value": 92000},
      {"name": "Mar", "value": 108000},
      {"name": "Apr", "value": 121000},
      {"name": "May", "value": 134000},
      {"name": "Jun", "value": 142000}
    ],
    "config": {
      "showGrid": true,
      "showLegend": true,
      "colors": ["#3b82f6"]
    }
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;See it live:&lt;/strong&gt; &lt;a href="https://www.genui.sh/a/dd315369-983e-409a-9ad0-4055484e87c1?token=eyJhbGciOiJIUzI1NiJ9.eyJhcnRpZmFjdElkIjoiZGQzMTUzNjktOTgzZS00MDlhLTlhZDAtNDA1NTQ4NGU4N2MxIiwidXNlcklkIjoiZTQ2MDdjYjUtNjBlMi00YjRjLWI2MWYtMWE1YjE5YWNkZWM2IiwiaWF0IjoxNzc1OTQ3ODgzLCJzdWIiOiJkZDMxNTM2OS05ODNlLTQwOWEtOWFkMC00MDU1NDg0ZTg3YzEifQ.HJ1eOjNs0Moer0uPOSv80G4-2ce8bIlu-oGiLe1SWTY" rel="noopener noreferrer"&gt;Monthly revenue chart →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbc0zliye64w1zumejif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbc0zliye64w1zumejif.png" alt="Monthly revenue chart preview" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same URL, same auth header, same response shape. Five output types (markdown, table, chart, pdf, and a composable dashboard format called &lt;code&gt;@std/dynamic&lt;/code&gt;) all sharing one endpoint. The chart template supports four sub-types - &lt;code&gt;line&lt;/code&gt;, &lt;code&gt;bar&lt;/code&gt;, &lt;code&gt;area&lt;/code&gt;, &lt;code&gt;pie&lt;/code&gt; - so swapping from a bar to a line chart is a one-word change. That's the one-call, multi-format part: the same POST body shape gives you a PDF, a hosted web view, or a live dashboard depending on &lt;code&gt;template&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When things go wrong, errors come back in a consistent shape too:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;monthly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;artifact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;quota&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Monthly artifact limit reached (50). Upgrade your plan for more."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FORBIDDEN"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;limited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;req/min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;per&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key)&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Too many requests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RATE_LIMITED"&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;
  
  
  Composing a dashboard
&lt;/h2&gt;

&lt;p&gt;The interesting template is &lt;code&gt;@std/dynamic&lt;/code&gt;. Instead of a single content blob, you POST a tree of components - Metric, Chart, Table, Card, Stack - and the renderer composes them into a hosted page:&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="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "template": "@std/dynamic",
  "title": "Weekly snapshot",
  "content": {
    "component": "Stack",
    "props": { "gap": "md" },
    "children": [
      {
        "component": "Metric",
        "props": { "label": "Revenue", "value": "$284,000", "delta": "+12%" }
      },
      {
        "component": "Chart",
        "props": {
          "type": "line",
          "data": [
            {"day": "Mon", "visits": 1200},
            {"day": "Tue", "visits": 1450},
            {"day": "Wed", "visits": 1380}
          ],
          "config": {
            "categoryKey": "day",
            "series": [{"key": "visits", "label": "Visits"}]
          }
        }
      }
    ]
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;See it live:&lt;/strong&gt; &lt;a href="https://www.genui.sh/a/b644e015-af79-4095-a925-2964daf1bec0?token=eyJhbGciOiJIUzI1NiJ9.eyJhcnRpZmFjdElkIjoiYjY0NGUwMTUtYWY3OS00MDk1LWE5MjUtMjk2NGRhZjFiZWMwIiwidXNlcklkIjoiZTQ2MDdjYjUtNjBlMi00YjRjLWI2MWYtMWE1YjE5YWNkZWM2IiwiaWF0IjoxNzc1OTQ3NTY4LCJzdWIiOiJiNjQ0ZTAxNS1hZjc5LTQwOTUtYTkyNS0yOTY0ZGFmMWJlYzAifQ.WV99zndYXRlZp4ha-jB2lutsGOO4rr36rwmDZQ9WtUw" rel="noopener noreferrer"&gt;Weekly snapshot dashboard →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xwjmypblcxt5lf7taqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xwjmypblcxt5lf7taqn.png" alt="Weekly snapshot dashboard preview" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the actual payoff: the same tree can be served as a web view today and re-POSTed as &lt;code&gt;"template": "pdf"&lt;/code&gt; tomorrow to get a printable version, without re-designing anything. If an LLM is deciding the shape of the output at runtime, this is the format you want it writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you don't have to think about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosting the output.&lt;/strong&gt; The returned URL is a genui.sh page that renders the artifact. No S3 bucket, no CDN config, no expired-link headache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signing.&lt;/strong&gt; The URL comes pre-signed with a token you can set to expire in 1h, 30d, or never.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fonts, print styles, headers, footers.&lt;/strong&gt; The renderer handles A4/Letter layout, page breaks, and PDF metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View counts, if you care.&lt;/strong&gt; Each hosted link tracks opens; you can see them in the dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple formats from the same data.&lt;/strong&gt; If you want a PDF and a web dashboard of the same report, you just POST twice with different &lt;code&gt;template&lt;/code&gt; values. No duplicated template design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Works cleanly as an HTTP Request node in n8n, a Webhooks step in Zapier, or an HTTP module in Make — the single POST plus signed-URL response maps to what those tools already expect, so you don't need a custom function to glue anything together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Artifacts / month&lt;/th&gt;
&lt;th&gt;Max payload&lt;/th&gt;
&lt;th&gt;Link expiry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;1MB&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$7/mo&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;5MB&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$19/mo&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;td&gt;10MB&lt;/td&gt;
&lt;td&gt;No expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To be upfront: on raw PDF-per-dollar, you can match or beat these numbers on the big template-editor APIs - PDFMonkey's Pro is €15/mo for 3,000 documents, APITemplate.io's PDF Basic is $19/mo (annual) for the same. genui.sh isn't trying to be the cheapest PDF factory; it's trying to be the one call you make when you want a PDF &lt;em&gt;and&lt;/em&gt; a web view &lt;em&gt;and&lt;/em&gt; a dashboard from the same JSON, without designing a template first.&lt;/p&gt;

&lt;p&gt;The Free tier needs no credit card. Starter and Pro are managed through Stripe's hosted customer portal, so you can change plan, update payment, or cancel at any time without talking to anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this is &lt;em&gt;not&lt;/em&gt; the right tool
&lt;/h2&gt;

&lt;p&gt;Being honest: if your use case is a heavily-branded invoice PDF with an exact legal layout your accountant approves annually, you probably want PDFMonkey's template editor. genui.sh is optimized for the case where the data is dynamic and the layout doesn't need to match a pixel-perfect brand document.&lt;/p&gt;

&lt;p&gt;If you need PDF/A compliance, digital signatures, or long-term archival metadata, none of the hosted APIs (including this one) will fit - you want a library like iText or Prince with full control.&lt;/p&gt;

&lt;p&gt;Everything else - reports, dashboards, invoices that don't need legal compliance, data exports, AI-generated summaries, automation workflow outputs - one POST is probably enough.&lt;/p&gt;

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

&lt;p&gt;Grab a free key at &lt;a href="https://genui.sh" rel="noopener noreferrer"&gt;genui.sh&lt;/a&gt; - 50 artifacts/month, no credit card. Ping me at &lt;a href="mailto:support@genui.sh"&gt;support@genui.sh&lt;/a&gt; with feedback, or open an issue if the docs are unclear.&lt;/p&gt;

&lt;p&gt;If you want to see what the composable dashboard format looks like, there's a &lt;a href="https://genui.sh/#pricing" rel="noopener noreferrer"&gt;live example&lt;/a&gt; on the landing page rendering a real dashboard from a single POST body.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>showdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Building a Local GitHub-style Review Interface for Claude Code Plans</title>
      <dc:creator>Eduard Maghakyan</dc:creator>
      <pubDate>Wed, 04 Mar 2026 00:27:49 +0000</pubDate>
      <link>https://forem.com/eduardmaghakyan/building-a-local-pr-review-interface-for-claude-code-plans-57o2</link>
      <guid>https://forem.com/eduardmaghakyan/building-a-local-pr-review-interface-for-claude-code-plans-57o2</guid>
      <description>&lt;p&gt;Plan mode in Claude Code feels like reviewing a pull request with no comments, no diff, and no history.&lt;/p&gt;

&lt;p&gt;Claude thinks for a moment (well much longer than a "moment"), then dumps a wall of text into your terminal - It then asks for approval with many variations of "Yes, ...."&lt;/p&gt;

&lt;p&gt;Then I open the Markdown file, start taking notes while reading through it. After doing this for a while I felt there should be a better way.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/EduardMaghakyan/ipe" rel="noopener noreferrer"&gt;IPE&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8u13z72zpsq99v8xhyoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8u13z72zpsq99v8xhyoz.png" alt="interactive-claude-code-plan-review" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is IPE?
&lt;/h2&gt;

&lt;p&gt;IPE intercepts Claude Code's &lt;code&gt;ExitPlanMode&lt;/code&gt; hook and opens a browser tab showing the plan in a GitHub-style code review interface. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add inline comments&lt;/strong&gt; on any block or text selection — just like leaving a review comment on a PR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click any file reference&lt;/strong&gt; (e.g. &lt;code&gt;`src/index.ts`&lt;/code&gt;) to pop open a syntax-highlighted side drawer showing the actual file contents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare plan versions&lt;/strong&gt; side-by-side when Claude revises after your feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch between sessions&lt;/strong&gt; if you're running multiple Claude Code instances at once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approve or request changes&lt;/strong&gt; — clicking "Request Changes" bundles your inline comments and sends them back to Claude&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Problems I wanted to fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You can't annotate.&lt;/strong&gt; If step 3 looks wrong and step 8 is fine but needs a tweak, you're writing one blob of feedback hoping Claude parses it all correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You lose context across revisions.&lt;/strong&gt; Claude revises the plan based on your feedback - but there's no diff. Did it actually address your concern? You're re-reading the whole thing from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;IPE registers itself as a Claude Code hook on &lt;code&gt;PermissionRequest&lt;/code&gt; with the &lt;code&gt;ExitPlanMode&lt;/code&gt; matcher. When Claude finishes planning and tries to proceed, the hook fires.&lt;/p&gt;

&lt;p&gt;The binary spins up a local HTTP server, opens your browser, and &lt;strong&gt;blocks&lt;/strong&gt; - Claude is sitting there waiting for your response. You review at your own pace (the timeout is 4 days, so no rush). When you approve or request changes, the server sends the response back to the hook and the browser tab closes automatically.&lt;/p&gt;

&lt;p&gt;The whole thing is a self-contained binary built with Bun.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install in One Line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&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;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/eduardmaghakyan/ipe/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/eduardmaghakyan/ipe/main/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The script downloads the binary and registers the hook in your Claude Code settings automatically. Run it again to update.&lt;/p&gt;

&lt;p&gt;Verify it's wired up by running &lt;code&gt;/hooks&lt;/code&gt; inside Claude Code — you should see the &lt;code&gt;ExitPlanMode&lt;/code&gt; hook listed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Workflow in Practice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Work with Claude Code as normal&lt;/li&gt;
&lt;li&gt;Claude generates a plan and calls &lt;code&gt;ExitPlanMode&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your browser opens with the plan displayed&lt;/li&gt;
&lt;li&gt;Read through it, click file references to inspect code, leave inline comments where needed&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Accept&lt;/strong&gt; → Claude proceeds&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Request Changes&lt;/strong&gt; → your comments go back to Claude, it revises, you review the diff&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;The project is open source: &lt;a href="https://github.com/EduardMaghakyan/ipe" rel="noopener noreferrer"&gt;github.com/EduardMaghakyan/ipe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use Claude Code in plan mode regularly, give it a spin and let me know what you think. Issues and PRs welcome - there's a lot of room to grow this (comment threads, keyboard shortcuts, plan history persistence...).&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
