Yellorn: The Browser Tab I Wanted for Dirty Data and Webhook Debugging
Tour Yellorn: repair broken JSON, XML, YAML, and CSV, publish mock webhooks, send HTTP requests — all private and local in your browser.
The Short Version
Yellorn is the browser tab I wanted whenever an integration session starts with “this should only take five minutes” and ends with six formatter websites, a dead tunnel, and a CSV file that looks innocent until Excel gets involved.
It repairs broken JSON, XML, YAML, and CSV. It converts between formats. It can publish the current payload as a temporary mock webhook API. It records incoming webhook requests. It also sends outbound HTTP requests with saved templates and history.
Basically, it handles the small plumbing jobs around integration work. Not the heroic architecture work. The other kind. The kind that quietly eats your afternoon while everyone thinks you are “just testing the API.”
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());The Five-Minute Task That Was Not Five Minutes
A familiar story: you ask an LLM for a JSON example. It gives you something that looks like JSON, except it is wrapped in Markdown, has a friendly sentence at the top, and ends an object with a trailing comma because apparently commas have feelings too.
It is usually something like this:
Here is the payload you asked for:
```json
\{
user_id: 42,
"email": "[email protected]",
"active": True,
\}
```
Then a backend log gives you a Python dict:
{'ok': True, 'price': Decimal('3.14'), 'created_at': Timestamp('2026-05-02')}
Then someone sends a CSV from Excel. It has a UTF-8 BOM, semicolons instead of commas, and smart quotes. Nobody admits touching anything. The file simply “came like that”, which is also how most production incidents introduce themselves.
id;name;status
1;"Alice";active
2;"Bob";failed
And while you are cleaning that up, a vendor asks for a webhook URL. Your local tunnel has changed. Again. You paste the new URL into their dashboard, click test, get nothing, and spend ten minutes debugging the ancient distributed system known as “did I remember to press Save?”
None of this is hard. That is what makes it annoying. Hard problems at least have dignity. This is just losing focus one paper cut at a time.
Yellorn is built for that part.
First, Fix the Data Without Pretending It Was Fine
Strict parsers are correct to be strict. JSON.parse should not become a therapist for whatever came out of Slack, Claude, Excel, and someone’s clipboard history.
But a developer tool can still help before giving up.
Yellorn starts strict. If the input is valid, it formats it and moves on. If it fails, Data Doctor tries a documented recovery path across JSON, XML, YAML, and CSV. There are 29 auto-fix passes today: 15 for JSON, 5 for XML, 6 for YAML, and 3 for CSV.
The JSON repairs cover the usual suspects: Markdown fences, comments, Python literals, single quotes, unquoted keys, trailing commas, missing commas, auto-closing brackets, timestamps, datetime, Decimal, tuples, and sets. CSV gets help with things like BOM cleanup, delimiter weirdness, and quote normalization. XML and YAML get their own repair passes too, because apparently every format found a unique way to ruin a morning.
The important part is not just that Yellorn fixes things. It tells you what it fixed.
For the messy LLM paste above, the useful result is not drama. It is just clean data you can inspect:
{
"user_id": 42,
"email": "[email protected]",
"active": true
}
If it strips a Markdown fence, normalizes smart quotes, removes a trailing comma, or closes a bracket, the fix is labeled. That matters. A tool may help with syntax. It should not quietly rewrite meaning and then smile at you like everything is fine.
Bad commas are Yellorn’s problem. Bad business logic is still yours. Sorry. Software remains unfair.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());Then Convert It Without Rebuilding the Shape by Hand
Once the payload is parsed, the next question is usually: “Can I see this as another format?”
Backend wants Python. Frontend wants JSON. Ops wants YAML. Business sends CSV. A legacy system still speaks XML, possibly out of principle. Nobody is wrong, exactly. But the person in the middle still has to move data around.
Yellorn converts between JSON, XML, YAML, CSV, and Python-style output. CSV to JSON becomes rows of objects. XML to JSON keeps conventions like @_attr, #text, and #cdata. Python export can preserve useful types such as datetime.fromisoformat(...), Decimal, tuples, and sets.
Some conversions are naturally lossy. Deeply nested JSON does not become a nice CSV just because we ask politely. When a conversion may reshape or lose information, Yellorn warns before doing it.
Also, undo is atomic: one undo can restore both the text and the active format. That sounds small until the day a tool changes the data and the UI state separately and you discover that Ctrl+Z has a sense of humor.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());Publish a Webhook When You Just Need a Real URL
Sometimes you do not need a new service. You need a URL.
With Yellorn, you paste a payload, click Publish, and get a Cloudflare-edge HTTPS endpoint:
https://yellorn.com/api/webhook/<slug>
Which means the next test can be as boring as it should be:
curl -X POST "https://yellorn.com/api/webhook/<slug>" \
-H "Content-Type: application/json" \
-d '{"event":"invoice.paid","customer_id":"cus_123"}'
All HTTP methods are accepted at the slug. You can configure the response status to simulate 200, 401, 429, or 500, which is useful when you need to test success, auth failure, rate limiting, or the classic “server is having a moment” path.
The endpoint is public to anyone with the slug. That is intentional. Webhooks need to be callable from outside. The request log portal at /webhook/<slug> is different: it is owner-only. People can send requests in; they cannot read your logs.
The portal shows method, headers, body, source IP, and timestamp for incoming requests. Yellorn also serves the right content type for the format you publish: XML as application/xml, CSV as text/csv, and so on.
The free tier gives you 5 active URLs with a 24-hour TTL. Pro and Team raise the limits for heavier usage.
This is not a production backend. It is the thing you use before writing a backend, or when writing one would be a very elaborate way to return a sample payload.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());Send the Other Half of the Integration
Webhook debugging has two directions. Something calls you, and you call something else.
Yellorn includes a Request Sender for the second half. It supports GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS, with configurable URL, headers, auth, and body.
You can save requests as templates, then come back later without reconstructing the same headers from memory. Each dispatch records history: status, timing, response headers, and response body truncated up to 1 MB.
That history is surprisingly useful. Humans are bad log stores. We compress everything into “I think it returned 401?” and then confidently waste twenty minutes.
The sender also has guardrails: SSRF protection, loop protection, and an X-Yellorn-Internal-Dispatch header to avoid recursively calling your own Yellorn webhook. Convenience tools should still have brakes. A car with only an accelerator is called a postmortem.
Look at the Payload Like a Human
Raw text is great until the payload becomes six levels deep and you start counting braces with your eyes like a medieval punishment.
Yellorn gives you a few ways to inspect the same data:
- Tree view for nested JSON-like structures, with inline editing and quick copy actions.
- Table view for row-shaped data like CSV or arrays of objects.
- Graph view for parent-child relationships.
- JMESPath search when “find the failed users over 30” should not require expanding 200 nodes by hand.
For example:
users[?age > `30`].name
The point is not to make data look fancy. The point is to stop burning working memory on bracket navigation.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());The Small Workflow Stuff Matters Too
Yellorn also includes a few features that are not flashy, but save repeated setup:
- Compare mode opens two tabs in a read-only Monaco diff editor, with JSON sorted before diffing so key order does not create fake drama.
- Smart Format does heavier recovery and type conversion.
- Simple Format pretty-prints while preserving meaning.
- Safe Format adjusts layout without reparsing.
- Compress removes whitespace.
- Cloud Backup can sync tabs, palette, and layout across signed-in devices, with quota visibility.
None of these features deserves a keynote. Together, they make the workspace feel less like a random tool pile and more like a place where you can finish the debugging loop.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());What It Replaces, and What It Does Not
Yellorn replaces the annoying shuffle between a formatter, converter, webhook bin, tunnel, request sender, and diff tool for many everyday debugging tasks.
It does not replace Postman for complex team API workflows. It does not replace integration tests, schema agreements, observability, or a real backend. It also cannot make semantically wrong data correct. If the customer ID is wrong, Yellorn will not meditate over the payload and discover your domain model.
That boundary is deliberate. Yellorn is for the practical middle: clean the payload, convert it, publish a temporary endpoint, inspect incoming requests, send outbound ones, and keep moving.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());If that is the kind of work you keep doing between “real” tasks, try Yellorn. It was built for the small integration chores that are too common to ignore and too boring to keep doing by hand.
Related posts
-
Production Google OAuth In One Prompt: I Let Cursor Drive Chrome DevTools MCP
I asked an AI agent to wire production Google OAuth. One prompt later it clicked through Cloud Console, configured scopes, and shipped to prod.
-
MoneyPrinterV2: What 18,000 Stars Worth of Automated Content Actually Looks Like
An assembly line for AI content — local LLMs write the script, KittenTTS reads it, Gemini paints the pictures. The video uploads itself.
-
Project N.O.M.A.D.: The Knowledge Bunker You Build for a Rainless Day
Project N.O.M.A.D. is an offline knowledge bunker — local LLMs, Wikipedia, maps, and education tools that survive when the cloud goes dark.
-
ruanyf/weekly: The Digital Lighthouse in a Sea of Algorithmic Slop
At the end of the infinite scroll, one man is still standing.