I got tired of ad-heavy JSON formatters so I built my own — here’s what I learned

I use JSON formatters every day. Paste messy API response, get clean output. Simple.
But every tool I used was either covered in ads, required signup, or sent my data to some server I knew nothing about. One day I was pasting an API key into one of these tools and stopped myself – why am I trusting a random website with this?
So I built my own: getpdfmint.com/tools/json-formatter
What it does differently
Everything runs in the browser. No server, no logs, no account. You can paste API keys, internal configs, production data — nothing leaves your machine.
It also tells you exactly where your JSON is broken. Not just “invalid JSON” – the exact line and character. Trailing comma on line 47? It’ll find it.
Auto-fix
This is the part I’m most proud of. Most formatters tell you your JSON is broken and leave you to figure it out. Auto-fix actually tries to repair it – single quotes swapped to double quotes, unquoted keys wrapped, trailing commas stripped, missing brackets closed. One click and it tries everything it can before giving up.
It won’t fix everything. Deeply malformed JSON sometimes can’t be recovered automatically. But for the 90% of cases where someone copy-pasted from JavaScript or a Slack message, it just works.
Schema fix
This one is for when your JSON is technically valid but the types are wrong. Classic example: your API returns {“port”: “8080”, “retries”: “3”} – valid JSON, but both values should be numbers, not strings. Schema fix detects string-wrapped numbers and booleans and converts them to their proper types automatically.
It’s a small thing but it saves a surprising amount of time when you’re debugging why your config isn’t being read correctly.
What I learned building it
Syntax highlighting without a library is surprisingly doable with a regex pass over the output. The hard part is error recovery – JSON.parse throws one error and stops. Building something that keeps trying, finds multiple issues, and explains them in plain English took most of the time.
The auto-fix logic is essentially a series of educated guesses applied in order, each one checked against JSON.parse until something passes. Ugly but effective.
The other thing: people paste enormous JSON. Files that would choke a server-side tool work fine because the browser has plenty of memory and no upload limit.
It’s free, forever
No ads on the tool pages. No signup. It runs on goodwill – there’s a support button if you find it useful, but no pressure.
If you work with JSON daily, give it a try. And if you build something similar, the client-side approach is worth considering.

I Built a Universal CLI Scaffolding Tool — Scaffy v1.0.1 is Live

I Built a Universal CLI Scaffolding Tool — Scaffy v1.0.1 is Live

Every time I started a new project, I found myself doing the same thing —
Googling “how to install Laravel”, “create-next-app flags”, “Django setup
from scratch”. Not because I didn’t know how, but because I couldn’t
remember the exact commands every single time.

So I built Scaffy.

What is Scaffy?

Scaffy is an open source CLI tool that scaffolds any framework in one command
using the framework’s official installer — never stale templates, always fresh.

npm install -g scaffy-tool

scaffy laravel
scaffy django
scaffy next
scaffy gin

You pick the framework, answer a few questions, and Scaffy handles the rest.

How it works

Scaffy is built around a simple plugin system. Every framework is exactly
3 files:

  • plugin.json — metadata, requirements, version info
  • questions.js — what to ask the user
  • scaffold.js — what to run

That’s it. Anyone can add a new framework in 3 files and open a PR.

What’s in v1.0.1

After 3 sprints of development, v1.0.1 ships with 10 frameworks across
4 languages:

Language Frameworks
PHP Laravel v13, v12, v11 · Symfony v7
Python Django v5 · FastAPI
Go Gin v1
JavaScript ExpressJS v4 · NextJS v14 · NestJS v11 · VueJS v3

What makes it different

It uses official installers. Scaffy never ships its own templates.
scaffy laravel runs composer create-project laravel/laravel.
scaffy next runs npx create-next-app@14. You always get the freshest,
most up-to-date project structure the framework team ships.

It checks requirements first. Before running anything, Scaffy checks
that you have the right tools installed at the right versions. No more
cryptic errors halfway through setup.

It detects your package manager. For JavaScript frameworks, Scaffy
checks which of npm, yarn, or pnpm you have installed and offers only
the ones available on your system.

The tech stack

  • Node.js 18+ with full ESM
  • Commander.js for CLI parsing
  • Inquirer.js for interactive prompts
  • Jest for testing (150+ tests)
  • GitHub Actions CI on Node 18, 20, 22

What’s next

  • scaffy add github:user/plugin — install community plugins
  • AI mode — describe what you want, Scaffy picks the framework
  • VS Code extension
  • Plugin marketplace

Try it

npm install -g scaffy-tool
scaffy list

GitHub: https://github.com/TanvirHossen112/scaffy
npm: https://www.npmjs.com/package/scaffy-tool

Would love feedback, issues, and PRs. If you want to add a framework,
the plugin system makes it straightforward — open an issue and let’s
build it together.

7 Mac Apps That Help Developers Stay Creative and Inspired in 2026

Most developer tool lists focus on shipping faster or debugging better. But what about the apps that keep you wanting to build things?

Creativity is the part of development nobody optimizes for. We fine-tune our editors, our CI pipelines, our deploy scripts — but the spark that makes you start a new project on a Saturday morning? That gets neglected.

Here are 7 Mac apps that keep the creative engine running — tools that reduce friction, protect your headspace, and make the act of building software feel good again.

1. Bear

What it does: A beautiful, markdown-first note-taking app with nested tags and a distraction-free writing mode.

Bear is where half my projects start. Not in a code editor — in a note. The interface is so clean it almost tricks you into writing things down. I use it for brainstorming feature ideas, jotting down API designs, and keeping a running “someday” list. The nested tag system means I can organize by project without creating folder hell.

🔗 bear.app

2. Raycast

What it does: A launcher and productivity tool that replaces Spotlight with something actually powerful.

Raycast removes friction from everything. Quick calculations, clipboard history, window management, snippet expansion — all from one keyboard shortcut. When you can go from idea to action in two keystrokes, you stay in flow instead of getting derailed by the mechanics of using your computer. The extensions ecosystem is massive, so it grows with your workflow.

🔗 raycast.com

3. Arc Browser

What it does: A browser that organizes your tabs into spaces and keeps your research from becoming chaos.

Arc changed how I research before building. I create a Space per project — one for the docs I’m reading, one for design inspiration, one for competitive research. Instead of drowning in 80 tabs, each project gets its own clean context. The split-view feature is perfect for having docs open next to a live preview.

🔗 arc.net

4. TokenBar

What it does: A menu bar app that tracks your LLM token usage and costs in real time.

This one sounds utilitarian, but hear me out — it’s actually a creativity enabler. When I’m exploring ideas with Claude or GPT, I used to hesitate because I had no idea what the session was costing me. TokenBar sits in the menu bar showing live spend, so I can brainstorm freely without that nagging “am I burning money?” feeling. It removes the anxiety from AI-assisted ideation. $5 lifetime.

🔗 tokenbar.site

5. Monk Mode

What it does: Blocks distracting feeds at the content level — not the app level.

Every creative person knows the feeling: you sit down to build something, open your phone “for one second,” and 45 minutes vanish into a feed. Monk Mode doesn’t block Twitter or Reddit entirely — it blocks the feed while leaving DMs, search, and notifications intact. This distinction matters. You stay connected but you don’t get sucked into the scroll. The creative headspace survives. $15 lifetime.

🔗 mac.monk-mode.lifestyle

6. CleanShot X

What it does: The best screenshot and screen recording tool for Mac, period.

CleanShot X is indispensable for capturing and sharing ideas quickly. Annotate screenshots, record quick GIF walkthroughs of a UI concept, blur sensitive data, pin screenshots to your desktop as reference — the feature set is ridiculous. When you can capture and share a visual idea in seconds, the feedback loop tightens and creative momentum builds.

🔗 cleanshot.com

7. Obsidian

What it does: A local-first knowledge base with bi-directional linking between notes.

If Bear is where ideas start, Obsidian is where they grow. The graph view alone is worth it — watching connections form between seemingly unrelated notes surfaces creative ideas you’d never find in a flat folder structure. I use it as a second brain for longer-term thinking: architecture patterns, project retrospectives, random insights that might connect later.

🔗 obsidian.md

The Common Thread

None of these apps are “creative tools” in the traditional sense — no drawing, no design, no music. They’re environment tools. They shape the space around your work so that creative thinking happens more naturally.

The best creative output comes from reducing friction, protecting focus, and making it easy to capture fleeting ideas before they disappear. These 7 apps do exactly that.

What’s in your creative stack? Drop your recommendations below — always looking for new tools that keep the spark alive. 🔥

Paperless-ngx Has a Free API: Self-Hosted Document Management with OCR and Full-Text Search

What is Paperless-ngx?

Paperless-ngx is a self-hosted document management system that transforms physical documents into a searchable online archive. It OCRs your documents, extracts text, and lets you search everything via API.

Scanned receipts, invoices, letters — all searchable in seconds.

Quick Start

mkdir paperless && cd paperless
wget https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.sqlite.yml -O docker-compose.yml
docker compose up -d
docker compose run --rm webserver createsuperuser

Open http://localhost:8000.

The REST API

export PL_URL="http://localhost:8000/api"
export PL_TOKEN="your-token"

Upload Documents

curl -X POST "$PL_URL/documents/post_document/" 
  -H "Authorization: Token $PL_TOKEN" 
  -F "[email protected]" 
  -F "title=March Invoice" 
  -F "correspondent=Acme Corp" 
  -F "tags=2,5"

Paperless automatically: OCRs the document, extracts text, classifies it, and makes it searchable.

Search Documents

# Full-text search
curl -s "$PL_URL/documents/?query=invoice+march+2026" 
  -H "Authorization: Token $PL_TOKEN" | jq '.results[] | {title, correspondent, created}'

# Filter by tags
curl -s "$PL_URL/documents/?tags__id__in=2,5" 
  -H "Authorization: Token $PL_TOKEN"

# Filter by date range
curl -s "$PL_URL/documents/?created__date__gt=2026-01-01&created__date__lt=2026-04-01" 
  -H "Authorization: Token $PL_TOKEN"

# Filter by correspondent
curl -s "$PL_URL/documents/?correspondent__name=Acme" 
  -H "Authorization: Token $PL_TOKEN"

Get Document

# Get metadata
curl -s "$PL_URL/documents/DOC_ID/" 
  -H "Authorization: Token $PL_TOKEN" | jq '{title, content, tags, correspondent, created}'

# Download original
curl -o document.pdf "$PL_URL/documents/DOC_ID/download/" 
  -H "Authorization: Token $PL_TOKEN"

# Download thumbnail
curl -o thumb.png "$PL_URL/documents/DOC_ID/thumb/" 
  -H "Authorization: Token $PL_TOKEN"

# Get OCR'd text
curl -s "$PL_URL/documents/DOC_ID/" 
  -H "Authorization: Token $PL_TOKEN" | jq -r '.content'

Tags, Correspondents, Document Types

# Create tag
curl -X POST "$PL_URL/tags/" 
  -H "Authorization: Token $PL_TOKEN" 
  -d '{"name": "Tax 2026", "color": "#3498db", "is_inbox_tag": false}'

# Create correspondent
curl -X POST "$PL_URL/correspondents/" 
  -H "Authorization: Token $PL_TOKEN" 
  -d '{"name": "Acme Corp", "matching_algorithm": 1, "match": "acme"}'

# Create document type
curl -X POST "$PL_URL/document_types/" 
  -H "Authorization: Token $PL_TOKEN" 
  -d '{"name": "Invoice", "matching_algorithm": 1, "match": "invoice"}'

Auto-Matching

Paperless can automatically assign tags, correspondents, and types:

# Set matching rules
curl -X PATCH "$PL_URL/tags/TAG_ID/" 
  -H "Authorization: Token $PL_TOKEN" 
  -d '{"matching_algorithm": 3, "match": "receipt OR invoice"}'

Matching algorithms: exact, any word, all words, regex, fuzzy, auto (ML).

Features

  • OCR: Tesseract-based text extraction (100+ languages)
  • Full-text search: Find any word in any document
  • Auto-classification: ML-based tag/type assignment
  • Email consumption: Scan email attachments automatically
  • Mobile scanning: Upload from phone camera
  • Workflows: Rules for automatic processing

Paperless vs Google Drive

Feature Paperless-ngx Google Drive
OCR Automatic Manual
Full-text search Yes (OCR’d) Yes (native PDFs)
Auto-classify ML-based No
Self-hosted Yes No
API Full REST Yes
Storage Unlimited 15 GB

Need document automation or data extraction tools?

📧 [email protected]
🔧 My tools on Apify Store

How do you manage documents? Physical or digital-first?

Why AI Agents Fail Silently — And How to Fix It

I spent three hours debugging an agent pipeline last week that wasn’t broken.

No errors. No exceptions. The logs looked fine. The agent ran, did its thing, and returned a response. The response just happened to be completely wrong. Nothing in my stack told me that.

That’s the problem. And if you’re building anything with LLMs right now, you’ve almost certainly hit it already.

The failure mode nobody talks about

When a traditional API call breaks, you know. You get a 500, a timeout, an exception. Your monitoring catches it. Your retry logic kicks in. The system is designed around the assumption that failures are loud.

LLMs don’t work that way.

An LLM can receive your prompt, process it fully, and return a confident, well-formatted, completely hallucinated response. No error code. No signal that anything went wrong. Just bad output delivered with the same confidence as good output.

Now chain a few of those together in an agent pipeline — where the output of one step becomes the input of the next — and you have a system that can fail catastrophically while every individual component reports green.

This isn’t a hypothetical. It’s the default behavior of every agent framework I’ve worked with.

Three ways agents fail silently

  1. Empty or malformed output

You ask the agent to extract structured data. It returns an empty object, or it returns JSON that’s one closing bracket short of valid. Your code tries to parse it, gets None or throws a quiet exception that gets swallowed somewhere upstream, and the pipeline just… continues. With nothing.

This one is insidious because it often only happens on edge cases — unusual inputs, long contexts, prompts that are slightly ambiguous. Your happy path tests pass. Production fails on the 7% of inputs you didn’t think to test.

  1. Hallucinated success

The agent was supposed to do something — call an API, write a file, complete a task — and it responds as if it did. “Done! I’ve updated the record.” It didn’t update anything. There was no tool call. It just said the words.

This is especially common when you’re using a model that’s been fine-tuned to be helpful and agreeable. It wants to give you what you asked for. If it can’t do the actual thing, sometimes it just reports that it did.

  1. Cascading failures

This is the expensive one. Step 1 produces subtly wrong output. Step 2 runs on that output and produces plausible-looking but also wrong output. By step 4 or 5, you’re so far from correct that there’s no recovering — and you have no idea where the chain broke.

The frustrating part is that each individual step looks fine in isolation. The bug isn’t in any one component. It’s in the gaps between them.

Why the standard tooling doesn’t catch this

Most error handling is built around the assumption that errors are exceptional. You wrap things in try/catch, you set up Sentry, you watch your logs for stack traces.

But an LLM returning bad output isn’t an exception. It’s a valid response. Your infrastructure has no concept of “the agent succeeded technically but failed semantically.” That distinction doesn’t exist in HTTP status codes or Python exception hierarchies.

So you end up with monitoring that tells you your system is healthy while your users are getting garbage.

The fix isn’t more logging. It’s a different mental model: assume the output is wrong until you can verify it isn’t.

What good error handling looks like in an agent pipeline

A few things that actually work:

Validate outputs structurally. If you’re expecting JSON, parse it immediately and fail loudly if it’s not valid. If you’re expecting a specific schema, validate against it. Don’t let bad structure propagate downstream.

Validate outputs semantically. This is harder, but often doable. If the agent is supposed to return a URL, check that it’s a real URL. If it’s supposed to return a number in a certain range, check that. Simple assertions catch a surprising percentage of failures.

Capture the full context when something fails. Not just the error — the prompt, the model, the parameters, the raw response, the timestamp. You need all of it to reproduce and debug the failure later.

Retry with context. When something fails, don’t just retry the same call. Pass the failure back to the model: “Your previous response had this problem. Try again.” A lot of LLMs will self-correct when you tell them specifically what was wrong.

Here’s what this looks like in practice with graceful-fail:

from graceful_fail import agent_call, RetryConfig

retry_config = RetryConfig(
max_attempts=3,
capture_context=True,
self_heal=True # passes failure context back on retry
)

@agent_call(retry=retry_config)
def extract_data(text: str) -> dict:
response = llm.complete(f”Extract structured data from: {text}”)
return parse_json(response) # raises if malformed — triggers retry

When parse_json fails, graceful-fail captures the full context — the input, the raw LLM response, the exception — and on the next attempt, it appends that context to the prompt so the model knows what it did wrong. In practice this recovers about 70% of failures that would otherwise silently propagate.

The bigger picture

Here’s what I keep coming back to: agents are about to get a lot more autonomous. The things we’re building today are doing relatively contained tasks — summarizing, extracting, classifying. What’s coming is agents that take real actions: sending emails, writing code, making purchases, managing infrastructure.

In that world, silent failures aren’t just annoying. They’re dangerous.

The time to build proper error handling into your agent pipelines is now, while the tasks are still low-stakes enough that bad output is embarrassing rather than catastrophic. The patterns are the same whether you’re building a document parser or an autonomous business process. Get the foundations right early.

I built selfheal.dev because I got burned by this problem enough times that I finally sat down and built the thing I wished existed. It’s open source — the package is graceful-fail on PyPI and npm. If you’re building agent pipelines and you’re not thinking about this yet, I hope this saves you the three hours I lost to a pipeline that wasn’t broken.

GHSA-H8R8-WCCR-V5F2: GHSA-H8R8-WCCR-V5F2: Mutation-XSS via Re-Contextualization in DOMPurify

GHSA-H8R8-WCCR-V5F2: Mutation-XSS via Re-Contextualization in DOMPurify

Vulnerability ID: GHSA-H8R8-WCCR-V5F2
CVSS Score: 6.5
Published: 2026-03-27

DOMPurify versions prior to 3.3.2 are susceptible to a Mutation Cross-Site Scripting (mXSS) vulnerability. The flaw occurs due to discrepancies in browser parsing contexts when handling specific raw-text or RCDATA elements, allowing attackers to bypass sanitization.

TL;DR

DOMPurify < 3.3.2 fails to properly neutralize specific raw-text elements like <noscript>. Attackers can inject payloads that bypass initial sanitization but mutate into executable JavaScript when re-inserted into the DOM.

⚠️ Exploit Status: POC

Technical Details

  • Vulnerability Type: Mutation Cross-Site Scripting (mXSS)
  • CWE ID: CWE-79
  • CVSS v3.1 Score: 6.5 Medium
  • Attack Vector: Network
  • User Interaction: None
  • Exploit Status: Proof of Concept Available
  • Affected Component: Raw Text/RCDATA Parser Constraints

Affected Systems

  • DOMPurify (NPM Package)
  • Client-side web applications utilizing DOMPurify < 3.3.2
  • dompurify: < 3.3.2 (Fixed in: 3.3.2)

Exploit Details

  • Advisory PoC: Proof of concept demonstrating <noscript> mutation via innerHTML assignment.

Mitigation Strategies

  • Upgrade DOMPurify to version 3.3.2 or higher
  • Implement strict Content Security Policy (CSP) rules restricting unsafe-inline scripts
  • Avoid re-parsing sanitized content in differing browser contexts
  • Audit dependency trees for transitive inclusions of vulnerable DOMPurify builds

Remediation Steps:

  1. Identify all projects utilizing the DOMPurify NPM package.
  2. Execute dependency updates to force installation of DOMPurify >= 3.3.2.
  3. Validate the fix by executing test suites against known mXSS payloads.
  4. Deploy the updated application builds to production environments.

References

Read the full report for GHSA-H8R8-WCCR-V5F2 on our website for more details including interactive diagrams and full exploit analysis.

Bun Has a Free HTTP Server API: Build Blazing-Fast APIs Without Express

Why Bun’s HTTP Server?

Bun includes a built-in HTTP server that’s 5-10x faster than Node.js + Express. No npm install needed — just Bun.serve() and you have a production-ready server.

Quick Start

Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello World!");
  },
});
console.log("Server running on http://localhost:3000");

Run: bun run server.ts

Routing

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/api/users" && req.method === "GET") {
      const users = await db.query("SELECT * FROM users");
      return Response.json(users);
    }

    if (url.pathname === "/api/users" && req.method === "POST") {
      const body = await req.json();
      const user = await db.query("INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *", [body.name, body.email]);
      return Response.json(user, { status: 201 });
    }

    if (url.pathname.startsWith("/api/users/") && req.method === "GET") {
      const id = url.pathname.split("/")[3];
      const user = await db.query("SELECT * FROM users WHERE id = $1", [id]);
      return user ? Response.json(user) : new Response("Not found", { status: 404 });
    }

    return new Response("Not Found", { status: 404 });
  },
});

File Serving

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    const filePath = `./public${url.pathname}`;
    const file = Bun.file(filePath);

    if (await file.exists()) {
      return new Response(file);
    }
    return new Response("Not found", { status: 404 });
  },
});

Bun.file() streams files efficiently — zero-copy for large files.

WebSocket Server

Bun.serve({
  port: 3000,
  fetch(req, server) {
    if (server.upgrade(req)) return;
    return new Response("Upgrade failed", { status: 500 });
  },
  websocket: {
    open(ws) { console.log("Client connected"); },
    message(ws, message) {
      ws.send(`Echo: ${message}`);
      // Broadcast to all
      ws.publish("chat", message);
    },
    close(ws) { console.log("Client disconnected"); },
  },
});

Built-in WebSocket with pub/sub — no ws or socket.io needed.

SQLite (Built-In)

import { Database } from "bun:sqlite";

const db = new Database("myapp.sqlite");
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)");

Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/users") {
      const users = db.query("SELECT * FROM users").all();
      return Response.json(users);
    }
    return new Response("OK");
  },
});

SQLite is built into Bun — no npm packages.

Benchmark

Framework Requests/sec (hello world)
Bun.serve() ~150,000
Node.js http ~50,000
Express ~15,000
Fastify ~45,000
Deno.serve() ~80,000

Error Handling

Bun.serve({
  port: 3000,
  fetch(req) {
    // Your handler
  },
  error(error) {
    return new Response(`Error: ${error.message}`, { status: 500 });
  },
});

Need high-performance APIs or server optimization?

📧 [email protected]
🔧 My tools on Apify Store

Bun, Deno, or Node — which runtime do you pick?

I Asked Two AIs to Build a Guitar. Now I Want to See What You Do With It.

Six words. Two models. One interactive fretboard. And a wide open challenge.

I typed six words into a duel on VibeCode Arena.

“Design a guitar with all the key notes represented on strings.”

That was it. No extra context. No design specs. No colour palette. No instructions about interactivity or layout or what clicking a note should do.

Just six words. And then I watched two AI models interpret them completely on their own.

What came back genuinely surprised me.

What actually happened

Both models built something called an Interactive Guitar Fretboard.

All twelve notes laid out across the top. C, C#, D, D#, E, F, F#, G, G#, A, A#, B. Six strings running horizontally. Click a note and green highlights appear showing its positions across the fretboard.

Not what I expected from six words.

But here is where I have to be honest, because this is the part that actually makes it an interesting challenge.

When I clicked A# the interaction worked. A# lit up in multiple positions across all six strings, which is correct — it does appear at different frets on every string in standard tuning. The concept is right and the underlying logic is mostly there.

But a guitarist looking at this would immediately spot what is missing.

No fret numbers. You can see a green dot on a string but you have no idea if that is fret 1 or fret 11. For a tool that is supposed to show you where notes live on the neck, that is a significant gap. A guitarist needs to know “A# is at fret 6 on the low E string” — not just see a highlight floating somewhere on a line.

No sound whatsoever. You click a note and it highlights visually but you hear nothing. For a guitar tool that is a pretty fundamental gap — half the point of learning the fretboard is training your ear alongside your eye. Right now it is a purely visual reference with no audio feedback at all.

No fret markers either. Real guitars have dot markers at frets 3, 5, 7, 9, and 12. Visual landmarks that help you orient yourself on the neck. Without them the fretboard is harder to read than it needs to be.

The neck also looks slightly short. Standard guitars run to at least 12 frets. A few of the higher positions on certain strings might be getting cut off entirely.

So the AI got the concept right. The interaction works. But the output has four clear problems that make it not quite ready for real use.

No sound whatsoever. You click a note and it highlights visually but you hear nothing. For a guitar tool that is a fundamental missing piece — the whole point of learning the fretboard is training your ear and your eye at the same time. Right now it is a silent visual map.

The note positions are not accurate. This is the most important one. When you click a note the highlighted positions across the strings do not always match where that note actually sits in standard tuning. For something that is supposed to teach you the fretboard, getting the positions wrong is a pretty serious problem.

No fret numbers. You can see a green dot on a string but you have no idea if that is fret 1 or fret 11. A guitarist needs to know “A# is at fret 6 on the low E string” — not just see a highlight floating somewhere on a line.

No fret markers. Real guitars have dot markers at frets 3, 5, 7, 9, and 12. Visual landmarks that help you orient yourself on the neck. Without them the fretboard is harder to read than it needs to be.

The neck also looks slightly short. Standard guitars run to at least 12 frets. A few of the higher positions on certain strings might be getting cut off entirely.

Concept right. Execution incomplete. That is exactly what makes it the right kind of open challenge.

Why this prompt specifically is interesting

A guitar fretboard is one of those things that sounds simple until you actually think about what it requires.

You need to understand music theory enough to place notes correctly. You need to think about visual hierarchy — what is a string, what is a fret, what is a note position. You need to handle interactivity in a way that feels intuitive. You need the whole thing to fit in a browser window without feeling cramped.

Six words produced something that handles all of that.

That is not nothing. That is actually impressive. And it opens up a lot of questions about what a better version of this could look like with more iteration, more perspective, more people bringing different ideas to the same starting point.

Which is exactly why I made it a challenge.

The challenge is live and the fretboard is yours to improve

I have opened this up on VibeCode Arena. Anyone can jump in, take the base fretboard output, direct AI to improve it, and submit their version.

Think about what this could become with the right iteration:

Sound — click a note and actually hear it play. The fretboard goes from visual to musical instantly.

Chord diagrams — select a chord and see all the finger positions light up simultaneously.

Scale highlighter — toggle a scale and watch the relevant notes across all strings illuminate.

Dark mode — because every developer tool looks better in dark mode. This is not negotiable.

Visual redesign — make it look like an actual guitar instead of a grid. Wood grain, tuning pegs, the whole thing.

Practice mode — the fretboard calls out a note and you have to find it. Simple quiz mechanic but immediately turns it into a learning tool.

Any one of these takes the base output from functional to genuinely impressive. And you do not need to implement it yourself — you direct AI to do it and the platform scores what comes back.

Come build the version I could not build alone.

Join the challenge here: https://vibecodearena.ai/share/afc85b04-2031-4b33-8a70-caf14197ac6d

What the duel format taught me about this specific prompt

I have been using AI tools for a while now. I have a set of models I default to, a way of prompting I have settled into, a rough mental model of what each tool handles well.

The blind duel broke all of that in a single session again.

Codestral-2508 handled the music theory layout with real precision. The note positions were accurate. The string spacing felt right. I would not have reached for Codestral for something like this without the blind format forcing me to evaluate it on merit.

That is the pattern I keep finding on VibeCode Arena. My default models are not always the right models. My habits are not always serving me. The blind vote is a fast, low stakes way to find out where your assumptions are wrong.

One duel per day on a prompt you actually care about is probably one of the highest value things you can do for your AI judgment right now.

Come make the fretboard better

The challenge is open. The base output is solid. The ceiling is high.

Whether you add sound, redesign the visuals, build a practice mode, or do something I have not thought of — I want to see what you make.

Challenge link: https://vibecodearena.ai/share/afc85b04-2031-4b33-8a70-caf14197ac6d

Six words got us here. Imagine what the right iteration gets us to.

Cloudflare Workers AI Has a Free API: Run AI Models at the Edge with Zero Infrastructure

What is Workers AI?

Workers AI lets you run AI models on Cloudflare’s edge network — text generation, image classification, embeddings, speech-to-text, translation, and more. No GPU provisioning, no model hosting.

Free tier: 10,000 neurons/day (enough for ~100+ requests).

Quick Start

npm create cloudflare@latest my-ai-app -- --template worker-typescript
cd my-ai-app
# wrangler.toml
[ai]
binding = "AI"

Text Generation (LLM)

export default {
  async fetch(request: Request, env: Env) {
    const response = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
      messages: [
        { role: "system", content: "You are a helpful assistant." },
        { role: "user", content: "Explain WebAssembly in 3 sentences." },
      ],
      max_tokens: 256,
    });

    return Response.json(response);
  },
};

Streaming Responses

const stream = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
  messages: [{ role: "user", content: "Write a poem about coding" }],
  stream: true,
});

return new Response(stream, {
  headers: { "Content-Type": "text/event-stream" },
});

Text Embeddings

const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
  text: ["How to deploy a web app", "Best practices for CI/CD"],
});

console.log(embeddings.data[0].length); // 768 dimensions

Combine with Vectorize for semantic search.

Image Classification

const imageData = await request.arrayBuffer();
const result = await env.AI.run("@cf/microsoft/resnet-50", {
  image: [...new Uint8Array(imageData)],
});

console.log(result); // [{label: "cat", score: 0.95}, ...]

Image Generation

const image = await env.AI.run("@cf/stabilityai/stable-diffusion-xl-base-1.0", {
  prompt: "A futuristic city skyline at sunset, cyberpunk style",
});

return new Response(image, {
  headers: { "Content-Type": "image/png" },
});

Translation

const translated = await env.AI.run("@cf/meta/m2m100-1.2b", {
  text: "Hello, how are you?",
  source_lang: "english",
  target_lang: "spanish",
});
// {translated_text: "Hola, ¿cómo estás?"}

Speech-to-Text

const audioData = await request.arrayBuffer();
const transcription = await env.AI.run("@cf/openai/whisper", {
  audio: [...new Uint8Array(audioData)],
});
console.log(transcription.text);

Text Summarization

const summary = await env.AI.run("@cf/facebook/bart-large-cnn", {
  input_text: longArticleText,
  max_length: 150,
});

Available Models

Category Model Use Case
LLM Llama 3.1 8B Chat, code gen
Embeddings BGE Base Semantic search
Image Gen SDXL Image creation
Vision ResNet-50 Image classification
Translation M2M-100 100+ languages
Speech Whisper Audio transcription
Summarization BART Text summarization

REST API

curl "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/ai/run/@cf/meta/llama-3.1-8b-instruct" 
  -H "Authorization: Bearer $CF_TOKEN" 
  -d '{"messages": [{"role": "user", "content": "Hello"}]}'

Need AI integration or edge computing setup?

📧 [email protected]
🔧 My tools on Apify Store

Edge AI or centralized GPU — what’s your approach?

Deno KV Has a Free API: Built-In Key-Value Database with Zero Configuration

What is Deno KV?

Deno KV is a built-in key-value database that ships with the Deno runtime. No installation, no configuration, no connection strings. Just Deno.openKv() and you have a globally distributed database.

Free on Deno Deploy: unlimited reads, 1 GB storage.

Quick Start

const kv = await Deno.openKv();

// Set
await kv.set(["users", "user_123"], {
  name: "Alice",
  email: "[email protected]",
  plan: "pro",
});

// Get
const user = await kv.get(["users", "user_123"]);
console.log(user.value); // {name: "Alice", ...}

// Delete
await kv.delete(["users", "user_123"]);

That’s it. No npm install, no database setup.

Hierarchical Keys

// Keys are arrays — naturally hierarchical
await kv.set(["posts", "2026", "03", "my-post"], { title: "Hello", body: "..." });
await kv.set(["posts", "2026", "03", "another"], { title: "World", body: "..." });

// List all March 2026 posts
const posts = kv.list({ prefix: ["posts", "2026", "03"] });
for await (const entry of posts) {
  console.log(entry.key, entry.value.title);
}

// List all posts ever
const allPosts = kv.list({ prefix: ["posts"] });

Atomic Operations

// Transfer money atomically
const sender = await kv.get(["accounts", "alice"]);
const receiver = await kv.get(["accounts", "bob"]);

const result = await kv.atomic()
  .check(sender) // Ensure no concurrent modification
  .check(receiver)
  .set(["accounts", "alice"], { balance: sender.value.balance - 100 })
  .set(["accounts", "bob"], { balance: receiver.value.balance + 100 })
  .commit();

if (!result.ok) {
  console.log("Conflict — retry");
}

Queues (Built-In)

// Enqueue a task
await kv.enqueue({
  type: "send_email",
  to: "[email protected]",
  subject: "Welcome!",
});

// Process queue
kv.listenQueue(async (message) => {
  if (message.type === "send_email") {
    await sendEmail(message.to, message.subject);
  }
});

Secondary Indexes

// Store user with email index
async function createUser(user) {
  const result = await kv.atomic()
    .set(["users", user.id], user)
    .set(["users_by_email", user.email], user.id)
    .commit();
  return result;
}

// Lookup by email
async function getUserByEmail(email) {
  const entry = await kv.get(["users_by_email", email]);
  if (!entry.value) return null;
  const user = await kv.get(["users", entry.value]);
  return user.value;
}

HTTP Server with KV

const kv = await Deno.openKv();

Deno.serve(async (req) => {
  const url = new URL(req.url);

  if (url.pathname === "/api/visits") {
    // Atomic counter
    const current = await kv.get(["visits"]);
    const count = (current.value as number || 0) + 1;
    await kv.set(["visits"], count);
    return Response.json({ visits: count });
  }

  if (url.pathname.startsWith("/api/users/")) {
    const id = url.pathname.split("/")[3];
    const user = await kv.get(["users", id]);
    if (!user.value) return new Response("Not found", { status: 404 });
    return Response.json(user.value);
  }

  return new Response("OK");
});

Deploy to Deno Deploy

deployctl deploy --project=my-app main.ts

Your KV database automatically becomes globally distributed — replicated across 35+ regions.

Deno KV vs Redis vs DynamoDB

Feature Deno KV Redis DynamoDB
Setup Zero (built-in) Install/Docker AWS Console
Transactions Atomic ops MULTI/EXEC TransactWriteItems
Queues Built-in Separate (Bull) SQS
Global replication Automatic Manual Global Tables
Free tier 1 GB Self-host 25 GB
Latency <10ms edge <1ms local <10ms

Need edge-first backend or Deno deployment help?

📧 [email protected]
🔧 My tools on Apify Store

Redis or Deno KV — which key-value store do you prefer?