Category: Uncategorized

  • Our recipe roundup

    We tried six recipes this week. Here are the ones that lasted past lunch.

    Click the title of any recipe to see the full post. The list reorders as you and other visitors read.

  • Writing Better Commit Messages

    Why your future self will thank you.

  • Roasting Coffee at Home

    From green beans to drinkable cup in 12 minutes.

  • How I Tune My Suspension

    Sag, rebound, low-speed compression – the order matters.

  • Mountain Biking the Coast Ranges

    Three days, 200 miles, two flat tires.

  • Apps as Abilities

    Apps as Abilities

    I want to talk about the part of DesignSetGo Apps that I think is the most underrated, because it took me a while to fully see it myself.

    WordPress 7.0 (May 2026) brought three pieces together: the Connectors API (one place to configure your AI provider), the WP AI Client (a provider-agnostic PHP API for calling the model), and the client-side half of the Abilities API, a way to register functions the site’s AI agent can invoke. The server-side Abilities API actually landed earlier in WP 6.9 (Nov 2025), and Yoast SEO Premium 27.5 was already shipping abilities like analyze_page_seo and suggest_internal_links by April 2026. Plugins are becoming things you talk to, not just things you click around in.

    DSGo Apps plugs into all three of those. Apps you build can prompt the model via dsgo.ai.prompt(). No API keys to manage; the site’s configured Connector handles it. That part’s table stakes for any plugin in the WP 7.0 era.

    The interesting part is the other direction.

    Apps that publish abilities

    A DSGo App can declare in its manifest that it publishes one or more abilities:

    {
      "id": "recipe-converter",
      "isolation": "iframe",
      "abilities": {
        "publishes": [
          {
            "name": "recipe-converter/convert-units",
            "label": "Convert recipe units",
            "description": "Convert recipe measurements between metric and imperial.",
            "category": "content",
            "input_schema": { "type": "object", "properties": { "recipe": { "type": "string" } }, "required": ["recipe"] },
            "output_schema": { "type": "object" },
            "annotations": { "readonly": true, "destructive": false, "idempotent": true }
          }
        ]
      }
    }
    

    A few rules worth knowing: ability names are namespaced under your app’s id (recipe-converter/*, never another app’s), and publishing is iframe-mode only in v1, because inline-mode apps run too entangled with the page to host an isolated handler. Max eight publishes per app.

    When the app installs, the plugin registers each ability with WordPress’s Abilities API. From that point on, the site’s AI agent (running in wp-admin via @wordpress/abilities) can discover recipe-converter/convert-units and invoke it without knowing or caring that it’s implemented by a sandboxed iframe. The publisher mounts a hidden iframe of your app on demand, dispatches the call across the bridge, and returns the result.

    The user asks the site’s agent: “Convert this recipe to metric.” The agent finds recipe-converter/convert-units, calls it, your handler runs in its sandbox, returns structured data, the agent uses the data to answer.

    You wrote a few hundred lines of HTML and JS. You got back a function the site’s AI agent can call.

    (One honest caveat for v1: the live handler runs in a browser context, because that’s where executeAbility is async and can wait for the iframe roundtrip. Server-side callers like WP-CLI, cron, or PHP-context AI Client invocations get a structured client_only_ability error pointing them at the browser-side surface. The launch demo is the in-admin AI agent, which is browser-context.)

    Why this is structural, not a feature

    A static iframe app is useful but limited. It’s something a person navigates to. It has a URL. Maybe it’s embedded in a post. It’s a destination.

    An app that publishes abilities is something an agent can use. It’s not a destination; it’s a verb. It has a function signature. It composes with other abilities. The site’s AI doesn’t need to know the app exists; it just needs to know recipe-converter/convert-units is a thing it can do, and the rest gets handled.

    That’s the inversion. Apps stop being things users find and start being things agents reach for.

    A few concrete shapes this enables:

    • Vertical packs. A real-estate-themed bundle ships ten apps, each publishing 2-3 abilities (listings search, mortgage math, local market data). Drop the bundle in. Suddenly the site’s agent knows everything about the niche.
    • Customer-specific tools. A freelancer builds an agency client a tool that knows their internal product taxonomy, their pricing logic, their bespoke quirks. The client’s site agent calls it the same way it calls SEO abilities, with no special integration.
    • Agent compositions. Two apps publishing complementary abilities can be chained by the agent without the apps knowing about each other. You don’t build the integration; the agent does.

    The same plugin that installs a calculator can install an ability. The runtime is identical. The trust model (manifest-declared, user-approved at install) is identical. The deploy command is identical.

    What this means for DSGo’s strategy

    WordPress is going to host a lot of agentic workflows over the next few years. The plugins that win are the ones that make it easy to publish into that surface, not just consume from it. DSGo Apps is built for that. Every app you ship is a potential ability. Every developer comfortable in Claude Code is a potential ability author. Every WP site running the plugin is a potential agent surface.

    I didn’t fully appreciate this when I started. The original brief was “let people host vibe-coded apps inside WordPress.” The Abilities side was almost a check-the-box addition during the WP 7.0 spec work. But once it shipped, the implications kept widening. An app isn’t a thing you put on your site anymore. It’s a thing your site can do.

    That’s the moat I’m betting on.

    Justin


    For the manifest fields and validation rules around abilities.publishes, see the manifest reference. For the consume side (dsgo.abilities.list/invoke and dsgo.ai.prompt), see the bridge API reference.

  • Building a WordPress App with Claude Code

    Building a WordPress App with Claude Code

    This is the local-to-WordPress workflow end to end. By the time you’re done, you’ll have a real app running at https://yoursite.com/apps/your-slug, built in Claude Code, with no clicking around in wp-admin.

    You’ll need: Node 20+, a WordPress site with the DesignSetGo Apps plugin installed and activated, an application password for that site, and Claude Code (or any agent; the workflow is the same).

    1. Scaffold the project

    npx designsetgo apps init my-first-app
    cd my-first-app
    

    apps init creates a starter project with:

    • dsgo-app.json manifest with sensible defaults
    • A typed bridge client (@designsetgo/app-client) already wired up
    • A minimal index.html that proves the bridge works
    • CLAUDE.md that teaches the agent your manifest format, the bridge API surface, and the constraints of the sandboxed runtime

    That last file is the unlock. It means when you start a Claude Code session in this directory, the agent already knows what it’s allowed to call, what permissions to declare, and what shape the manifest takes. You don’t have to explain it.

    2. Authenticate the CLI

    npx designsetgo apps login --site https://yoursite.com
    

    You’ll be prompted for your WordPress username and an application password (generate one in Users → Profile → Application Passwords in wp-admin). Credentials get stored at ~/.config/designsetgo/credentials.json, keyed by site URL. For CI, set DSGO_USER and DSGO_APP_PASSWORD instead and skip the login step.

    3. Open Claude Code and describe what you want

    claude
    

    Once you’re in a session, just ask. Something like:

    Build me a tool that lists my five most recent published posts in a clean card layout, with the title, date, and excerpt. Use the bridge client.

    Claude reads CLAUDE.md, sees the bridge methods, sees the manifest format, and writes the app. It’ll know to:

    • Add "posts" to the manifest’s permissions.read array
    • Import the bridge client and call dsgo.posts.list({ per_page: 5 }) after await dsgo.ready
    • Render the response into your HTML

    If you’ve used Claude Code on a typical project, the difference here is how little hand-holding it needs. The starter and the CLAUDE.md give it everything: typed APIs, runnable dev loop, clear constraints. That’s exactly the environment Claude Code is best in.

    4. Iterate locally

    The starter is an Astro project, so:

    npm run dev
    

    …spins up the standard Astro dev server for editing layout, copy, and any non-bridge UI. Bridge calls won’t connect locally (the bridge only exists when your bundle is rendered inside the WordPress runtime), so plan to deploy early and iterate against the real site. Deploys are fast.

    5. Deploy

    When the app does what you want:

    npx designsetgo apps deploy --build
    

    --build runs npm run build first so Astro produces a static dist/, then the CLI zips that, validates the manifest, posts the bundle to your site’s REST endpoint, and prints the URL. Open https://yoursite.com/apps/my-first-app and the app’s live.

    That’s it. No Vercel, no Netlify, no DNS, no separate auth. Your WordPress site is the deploy target.

    6. List, update, ship again

    npx designsetgo apps list             # see what's installed
    npx designsetgo apps deploy --build   # updates the existing app at the same slug
    

    Deploys are idempotent: same slug, same URL, new bundle. You can iterate freely without polluting your site with abandoned apps.

    What this unlocks

    The reason this workflow matters isn’t the 15 minutes. It’s that the same runtime that ran the bundle you built locally is the runtime your client’s site has. You’re not “publishing to production” in the dramatic sense. You’re just putting a static bundle inside a sandboxed iframe on someone’s WordPress site. The blast radius is one app’s iframe.

    That changes how you think about shipping. Worth getting wrong? Sure, ship it, iterate. Worth handing to a non-technical site owner? Sure, the manifest tells them exactly what permissions it asked for. Worth letting Claude Code build five variants and picking the best one? Sure, deploy each to a different slug.

    Static bundles plus a permissioned bridge plus a real deploy target turns out to be a surprisingly powerful surface.

    Where to go next

    • The manifest reference for the full dsgo-app.json schema
    • The bridge API reference for every bridge method
    • The next post in this series covers Apps as Abilities: how the app you just built can publish functions your site’s AI agent can invoke
  • Why We Run Apps in a Sandbox

    Why We Run Apps in a Sandbox

    If you’re going to let an AI write code that lives on your WordPress site, you should know exactly what that code can and can’t do. “Trust me, the model’s pretty good these days” is not an answer.

    DesignSetGo Apps was designed around a simple principle: a misbehaving app should crash itself, not your site. Here’s how the runtime enforces that.

    The runtime model

    Every DSGo App runs inside a sandboxed <iframe> with sandbox="allow-scripts" and a strict Content Security Policy. The iframe is served from your domain, but the browser treats it as an isolated context. That means an app can’t:

    • Read or modify the parent page’s DOM (your wp-admin, your theme, your other plugins)
    • Read cookies or localStorage belonging to your site
    • Make arbitrary network requests; the CSP whitelists only what the manifest declared
    • Spawn popups, navigate the parent frame, or load plugins
    • Touch your file system, your database, or any server-side state directly

    If an app gets stuck in an infinite loop, throws unhandled errors, or tries to mine cryptocurrency, the only thing affected is that app’s iframe. The rest of your site keeps working. Close the tab and the problem’s gone.

    The bridge: explicit permissions, not ambient access

    Sandboxing alone would make apps safe but useless. The whole point is that apps should be able to do things: list your posts, look up the current user, save preferences. So we built a postMessage-based bridge between the iframe and the parent page.

    It works like a browser extension. Each app ships with a dsgo-app.json manifest declaring which read scopes it needs:

    {
      "manifest_version": 1,
      "name": "Post Index",
      "permissions": {
        "read": ["posts", "user"],
        "write": []
      }
    }
    

    Each scope unlocks a specific set of bridge methods. posts covers dsgo.posts.list() and dsgo.posts.get()user covers dsgo.user.current() and dsgo.user.can(), and so on. When you install the app, you see those scopes. You approve them (or you don’t). At runtime, if the app calls a method whose scope isn’t in the manifest, the parent hard-fails the request with a permission error. There’s no implicit access.

    In v1, permissions.write must be empty. The bridge is read-only. Write access is reserved for a later version because it’s a different trust conversation, and we’d rather ship a tight v1 than a permissive one.

    Every method is documented, every error code is documented, every permission scope is named. If you want to know exactly what an app can reach, you read its manifest. No surprises.

    What’s NOT in v1

    A couple of things deliberately aren’t here yet, because they expand the trust surface:

    • Arbitrary outbound HTTP. Apps can use the AI bridge (dsgo.ai.prompt) to invoke the model your site is connected to via the WordPress 7.0 Connectors API, but they can’t make raw fetch() calls to wherever they want. Each app’s manifest declares an explicit runtime.external_origins allowlist (full HTTPS origins, no wildcards), and the CSP enforces it. If an app needs to talk to an external service, it goes through a declared, reviewable path.
    • Server-side execution. Apps are static bundles. Nothing in an app runs on your server. Your server-side blast radius is zero.

    Defense in depth

    Sandboxing the iframe is the first layer. Permission-gating the bridge is the second. There are a few more:

    • Manifest validation on install. Bundles get parsed, permissions get checked against the v1 schema, anything malformed gets rejected before it ever lands on your site.
    • Nonce-authenticated bridge requests. Each session gets a fresh nonce. Replay attacks against the bridge don’t work.
    • Capability checks on every call. Even with a permission granted, the parent enforces the underlying WordPress capability. An app with posts.list can’t see drafts the current user wouldn’t see anyway.
    • No ambient credentials. Apps don’t get your API keys, don’t get your application passwords, don’t get your session token. They get a scoped bridge and that’s it.

    The plain-English version

    You can install an app written by an AI you don’t fully trust, generated from a prompt you didn’t sweat over, deployed by a teammate you barely supervised, and the worst case is the app doesn’t work and you delete it. Your site stays up. Your data stays put. Your other plugins don’t notice anything happened.

    That’s the bar. Everything else is implementation detail.

    For the full normative specs, see the manifest reference and the bridge API reference. Both are frozen at v1, with additive-only changes going forward.

  • Introducing DesignSetGo Apps

    Introducing DesignSetGo Apps

    I’ve been using WordPress since people still argued about whether self-hosting was worth it. I’ve also spent the last year watching AI coding tools turn what used to be a weekend project into a fifteen-minute one. Those two worlds aren’t talking to each other, and that bothers me.

    The vibe-coded stuff lives somewhere else. Vercel. Netlify. A Lovable URL someone pasted in Slack. A standalone site that doesn’t know your customers, doesn’t share your auth, doesn’t show up in your sitemap, and doesn’t get backed up alongside the rest of your business. You build a thing on Tuesday, and by Friday you’ve forgotten the URL.

    Meanwhile WordPress, the actual home of your content, your users, your domain, is still treated like it’s only good for posts and pages. Want to add a tool? “Find a plugin.” Want a custom calculator? “Hire a developer.” Want to drop in something Claude wrote you? Good luck.

    DesignSetGo Apps is the bridge.

    What it actually is

    A WordPress plugin. You install it, and your site grows a new surface: /apps/{slug}. Each app is a static bundle (HTML, JS, CSS) that lives inside your site, served from your domain, on your hosting plan. Nothing new to log into.

    Apps run in a sandboxed iframe (or, optionally, an inline-rendered mode for SEO-friendly multi-page apps). They can’t touch your theme, your other plugins, or your database. What they can do is talk to a permissioned bridge: a postMessage protocol that lets an app ask “give me the latest 10 posts in this category” or “who’s the current user,” and the plugin answers if (and only if) the app’s manifest declared it needs that data, and you approved it at install time. Same model as a browser extension.

    That’s the whole pitch in one sentence: sandboxed enough to be safe, connected enough to be useful.

    And if you want, the app can be the website

    The default lives at /apps/{slug}. But an app’s manifest can also declare mount.mode: "root", and now it serves at /, with no prefix and no slug. One app per site, and it never shadows real WordPress pages, posts, archives, or feeds (those still win). Inline-mode root apps go further: any path WordPress would otherwise 404 on falls through to a route the app declares, so a multi-page app with its own routing effectively becomes the whole site, with WordPress quietly running underneath as the CMS, the auth layer, and the data source.

    That changes what the plugin is. It’s not just “host a calculator on your blog.” It’s “build the entire front-end of your WordPress site as an app you wrote in fifteen minutes with Claude Code, while your team keeps editing posts and pages in wp-admin like nothing happened.” The /apps/ surface is for additive tools. The root mount is for replacing the whole front-end.

    Two ways to build

    You don’t have to pick a stack or a workflow. The runtime doesn’t care where the bundle came from.

    In the browser. Open the admin page, drop in an HTML file (a saved Claude Artifact, a Bolt/Lovable export, anything self-contained), and get a URL. The harness can also generate apps from a prompt via WP-CLI (wp dsgo harness generate --prompt="..."), routed through the WP AI Client so it uses whichever provider your site is configured for. No separate keys, no separate billing.

    From Claude Code locally. npx designsetgo apps init scaffolds a starter project with a typed bridge client and a CLAUDE.md that teaches the agent your manifest format. Build, iterate, test. When you’re ready, npx designsetgo apps deploy --build pushes the bundle to your site. It’s there in seconds.

    Same runtime. Same permissions. Same /apps/{slug} URL. The casual user and the Claude Code power user end up in the same place.

    Why now

    WordPress 7.0 (May 2026) shipped three things that change what’s possible in a plugin:

    • Connectors API: site admins configure their AI provider once, every plugin inherits it
    • WP AI Client: provider-agnostic PHP API for invoking the model
    • Abilities API: a way to register functions the site’s AI agent can call

    Together, those mean a plugin can offer real AI features without holding API keys, without paying for inference, and without locking users into one provider. DSGo Apps is built on top of that stack. Apps you build can both consume AI (via the bridge) and publish their own abilities for the site’s agent to invoke. (More on that one in a later post.)

    What’s next

    The plugin is real. The bridge spec is frozen at v1. The CLI ships, the importer ships, the AI surfaces ship. There’s plenty more to build (block embed, write permissions, more bridge methods), but the wedge is in.

    If you’ve been waiting for a way to let WordPress hold the things AI is helping you build, this is it. Try it, break it, tell me what’s missing.

    The docs are the place to go deeper: manifest schema, bridge API, the works. The follow-up posts in this series cover the safety story, the Claude Code workflow, and how apps can publish abilities your site’s AI agent can call.

    Justin