Author: Robert

  • A Twin-Star Site Model

    I don’t love the name, but I think the creator economy is a real-enough thing. There are at least a couple million creators on the web, I read somewhere, who make a living at it.

    My impression is that they either go with something like Patreon or Substack as a way to platform themselves, or use Gumroad to sell things, or exist more or less entirely on YouTube, raking in the ad money. Maybe you can run your whole shop on Patreon (they’re introducing a community capability, founder Jack Conte even has a whole theory about how this makes sense and indeed, he does make some sense).

    Creators and cobblers

    It seems like most creators cobble both their online presence, the tools they use to manage that presence, and the back-end tools for accounting and stuff out of various pieces.

    I suspect a lot of time gets wasted in the cobbling and the learning involved with these tools. Fact of the matter is, for most creators, they typical tools are just way to feature rich and, as a result, complex.

    I’m not a creator in the sense that I’m talking about in this post, but I have done some of my own cobbling, enough to notice that, for instance, Quickbooks is just way, way, way more capability than I need, because all I need is to send out and track a few client invoices. And at nearly $40 a month for Quickbooks, I’m paying way too much for the privilege of letting them email my invoice forms.

    So I’m dropping Quickbooks in the new year and the general plan is to eat my own dog food. (I hate that expression, I think because I find life analogies that use food inherently coarse. But I can’t think of a better alternative–send help.) I’ll use PeakZebra to knock out a dead-simple invoicing system.

    PeakZebra(s)

    Whatever I pull together using PeakZebra, my plan is to evolve PeakZebra to support a “twin site” scenario where one site (PeakZebra.com) is the public facing site and another site (PeakZebra.something_else, presumably) will handle the back end things.

    This means that some elements will reside on the front-facing site, things like newsletter signup forms, to pick the simplest example. But when that signup form is submitted, it will be sent via an API call to the PeakZebra code on the backend server. The data from the form will be stored in the backend server’s SQL database, and when it comes time for me to do something with the data stored there, I’ll log in and use apps on the backend, while the front end site hums merrily along.

    Enhanced security

    With the right setup, this is a more secure approach to managing things like subscriber data, because you can put a lot of controls in place around who access that site than you can on a site that you want anyone and everyone to be able to at least see. And while WordPress is secure when properly configured, it’s safer still if the data isn’t even on the visible site.

    We’ll see how this goes–it’s not an immediate priority to have a “twinned” site arrangement, but I can still work on the sorts of simple tools I want, running them for now on PeakZebra.com but eventually migrating the backend stuff to a backend WordPress install.

    Is a twin site actually more secure? I think so, but I also think the crux of the question comes down to how secure you think the API calls that the front will make to the back will be. For my money, those can be locked down pretty darned tightly.

    You wind up with an arrangement that most attackers won’t have encountered before, with API calls being made from server to server. The attacker will not ordinarily have any way to see the API request data, nor will they be able to see the second server on the net unless they find themselves within a fairly narrow IP address range.

  • Imagining a WordPress Greenfield

    Just suppose, just for a moment, that you and I were tasked with creating something every bit as wonderful as the wonderful parts of WordPress, but starting with a (mostly) blank slate.

    It’s the plugins

    Well, one thing we have to get out of the way right away is what to do about the huge number of useful plugins that are the particular strength of the WordPress platform. Do we want to walk away from the ecommerce plugins, the learning management kits, the membership tools?

    If we want our cake and the eating of it, we’ll have to reckon with the immutable fact that all of that stuff is written in PHP and it all runs on the server. There are no lambda plugins. There are no client-side plugins.

    And it’s PHP. Some folks argue that PHP is antiquated, but I think that’s about fashion more than sense. It’s a fairly sophisticated language, performant, all that.

    All JavaScript?

    But as long as we’re declaring a fresh start, you’ve got to reckon with the hard truth that PHP doesn’t run on clients. Essentially, JavaScript is the only choice in that regard. And if you’re running JavaScript on the client, it makes life a lot easier to be running the same language up on the server.

    If we change gears and use Node.js on the server, then the challenge is finding some way to continue using existing plugins.

    I’ve turned this over and over in my mind. On the one hand, it seems pretty likely that a WordPress-specific translator could be built to turn plugins in Node.js plugins. If we do that, though, then we have to support all the action and filter hooks.

    So, on the other hand, maybe what we want is to make it easy for plugin providers to rewrite their code bases anew. Assuming an approach that was generally similar to WordPress’s approach, developers would have a pretty good sense of how they should approach various tasks.

    For example, you’d probably want get_current_user() to be getCurrentUser() and you’d probably want it to return a user ID. And you’d want to have roles and the roles would be collections of capabilities.

    Post much?

    That’s easy enough (maybe), but do we really want to commit to preserving the concept of everything being a post? We’d have to give that one some thought, but maybe the easiest way forward is to stick with posts and pages and custom posts and such.

    There’s a lot of complexity in core, though, the inevitable result of organic growth over twenty years, and maybe we just carefully sort through and discard a lot of the baggage that still works but probably was never such a great idea. And maybe we tack on a new concept or two, like having a React-like routing system be the default.

    But what we want is for WooCommerce defectors to have a straightforward sense of how to write a new and significantly more straightforward ecommerce solution. NewCommerce?

    Astro adoption?

    There’s another question to consider: given the complexity of building this sort of system, possibly the best way forward is to start with an existing system and then add on whatever stuff is needed to support critical plugin rewrites.

    This is the line of thought that has landed me at the front door of the Astro community. If you’re unfamiliar with Astro, it does content sites, like our friend WordPress. And it’s server centric, like WordPress. But it’s also vastly newer and thus not yet covered in all those rustic barnacles.

    It appears to have themes. It doesn’t, as far as I can tell, have plugins in the sense one has them in WordPress, but it seems at least conceivable that an interface to a plugin system could be created. It doesn’t have an in-built editor, but again, that seems like something that could be whipped up. Or, in a funny little twist, I feel pretty darned confident that the WordPress block editor could be pressed into service.

    So I’m going to explore Astro. Not because I’m so convinced that the current troubles in the WordPress world are going to lead to WordPress falling apart, but because I think we all need to think about hedging our bets. And, frankly, because there may be better options out there in the blog-and-content-creation universe.

    So, more to come on this. One clear takeaway, though, is that WordPress has built up an enormous and enormously useful ecosystem and feature set over the years. Leaving would be painful.

  • Headless WordPress and why it matters

    You know there’s headless WordPress, but may not be clear on how you’d make it happen. Or, more importantly, why you’d make it happen.

    What is headless WordPress?

    Let’s start with a quick rundown of what makes a WordPress site headless, why the naming in this case is exactly backwards, and just generally get ourselves on the same page.

    The conventional headful approach

    Normal WordPress is a world in which the action happens on the server. A website visitor requests a page from the server and the server assembles the page components (header, body, footer) from the database and any relevant templates. This is sent to the browser, any browser at all. And if something happens down there on the browser, it will result in a new page being requested from the server.

    Where this basic operation is perhaps most clearly visible is when the site provides some kind of data application. Maybe it’s a CRM application, so you might request a list of clients in the system. You get a display of the first 25 of them from the server, say. If you want to see the next page of clients, a new page will be requested from the server. If you want to see a particular client, a new page will be requested to display that client’s information. If you change the information for that client and want to save it, you’ll submit a form to the server and a new page will be delivered to show the update.

    Meanwhile, in the rest of the universe

    For most of the rest of the web, this isn’t typically how an application works, however. If you start with an application that shows a list of client records, then when you want to see the next page of them, a request will be sent to the server to retrieve only the data for the clients that need to be shown. The page with the client list won’t be replaced; rather, the new set of clients will be displayed on the existing page where the previous clients were listed.

    It’s possible that you can edit any of the client fields you can see on each row of the listing. Let’s say you do this and press a save icon at the end of the row you’ve changed. Again, this doesn’t result in a new page being requested. Instead, the listing continues to show the change you made and the change is sent as an update request to the server.

    The server, in other words, is just supplying data at this point, not pages (though, in our scenario, it probably supplied the initial listing page).

    Decouple this

    There are two things we should notice about this scenario. First, there’s got to be some kind of back end that answers requests for data and updates, even if it’s not supplying the pages. Second, the pages still have to come from somewhere. But the pages and the data don’t really have to come from the same place, and thus we can say that the presentation and the data have been decoupled.

    When you decouple the head of a thing, well, it becomes headless. So the baseline idea of headless WordPress is that there’s a WordPress server running, but it’s not supplying the pages that the website visitor is seeing.

    So where are the pages coming from? That depends, but most scenarios out there on the web right now fall into the basic pattern of using React (or some React framework that extends React) to create pages that can be retrieved from web servers as plain HTML and JavaScript files. These pages aren’t assembled or calculated on the server end, they are simply sent to the client as they stand. You’ll hear these scenarios referred to as static sites. That’s because the server doesn’t muck around with them–they can be plenty active once they are displayed in a browser window.

    One question that may already have popped into your mind is: what is React? And that’s an excellent question, but not one that we’re going to answer in any detail here. Suffice it to say, it’s a pre-built set of capabilities implemented in JavaScript, where the capabilities mostly have to do with user interactions.

    The key thing is that JavaScript and React are capable of asking the server (or more than one server) for data that it needs to display. The server that sends the data down to the browser in the headless WordPress scenario is, you guessed it, a WordPress server.

    Headless

    There are plenty of headless scenarios where the server isn’t a WordPress server and there are even scenarios where there arguably isn’t a server in the traditional sense.

    But we’re talking WordPress here. In that scenario, there are two primary ways that WordPress might interact with whatever’s going on down there at the browser window. It may, in the older and more widely adopted approach, use a REST API to make requests for data (or requests to place or update data on the server). Making a REST call is based on requesting a particular URL and it either places any changeable data at the end of the URL (as parameters) or it arranges them in the same way you might arrange data when posting a form to a web server.

    The other approach out there these days involves using a Graphql interface. This is more like opening a window directly into a database and making queries. The details of this don’t much matter for this discussion, the point is that it’s possible to install a plugin that creates a Graphql access point for a WordPress site.

    Wait, but why?

    Why would you take this headless approach, though?

    The obvious first answer is that it enables you to have a different language and framework running on the client side of things. If you want a React application that serves up a lot of server-side content, using WordPress as your CMS might very well make sense.

    Additionally, though, it gives you the capability to render and rerender a page in sections, so that you aren’t necessarily requesting a whole new page from the server every time anything happens.

    Now, as it happens, you can pull off this same trick using the new Interactivity API in WordPress, because it makes the front end capable of doing various things on its own. It let’s you build a “headless-seeming” user experience completely within a WordPress context.

    It’s not clear yet how well the Interactivity API will fare, as it’s still relatively early days, but it’s an interesting option for dynamic front ends (plus it’s in use within WordPress core, so it’s not likely to go anywhere anytime soon).

    WordPress makes a pretty solid CMS, particularly where the content is of the human-readable sort.


  • Creating a Block with Cursor AI

    I’ve been trying to figure out what the best approach to getting the most productivity out of AI-assisted coding in Cursor. Some things work jaw-droppingly well. Others create a rabbit hole of inexplicable coding failures that are more trouble than they are worth to debug and make work.

    Here’s a very simple example of something I was working on this morning: I wanted a WordPress block that would show an alert on a page with whatever message I put into it, and I wanted it to disappear on its own after it had been on screen for ten seconds.

    Keep it simple

    The takeaway, if you don’t care about the details, is this: you should stick to relatively discrete steps you’re asking it to achieve and you should check each bit carefully as you assimilate it into the code.

    As a placeholder, I’d been using a block from the WordPress repository called simple-alert-blocks. The simple part of the name doesn’t mislead. Nothing wrong with that, but it doesn’t fade.

    So I thought, I’ll add Cursor to make changes to it so that it always disappears after ten seconds.

    Only too happy to oblige

    Cursor happily did all this–it even looked pretty good as code, just scanning over it–and it didn’t work. Absolutely nothing happened. I started by asking it to debug the problem and it happily provided me with a “fix,” except that the fix simply re-applied code that was already there. So this improved nothing.

    I took a few minutes to look at it, but wasn’t seeing the problem. Later I realized that it had probably created a mismatch between the class name it was applying to the message box and the class name it was using in the css file. The class in the css file was .wp-block-pz-alert whereas the actual element in the DOM was using .wp-block-create-block-pzalert.

    I say this was most likely the problem because I didn’t have the code by the time I figured it out, but that CSS mismatch was a theme throughout the whole process.

    Keep your context ungoofed

    Leading to another takeaway: if Cursor does something substantially screwy, don’t just fix the problem, reset your context so that it isn’t still chewing away with the wrong idea in the back of its mind somewhere.

    So then I asked it to just create a block that did what I wanted from scratch. This one also looked pretty good, but wasn’t registering the block once I’d activated the new plugin (for once I didn’t forget the activation step). Again, strong suspicion that the mismatched CSS was in play, but there was also some idea that I’d inadvertently introduced because the original simple alert box was still in context that there should be a separate .JS file that had a “fade” function in it rather than just including this in the view.js file.

    Doublechecking

    At this point, I decided to take the AI part in far smaller steps and now created a new block using the create-block script. From there, I asked for specific steps and checked that each step worked. It occurs to me that Cursor currently makes about the same number of mistakes as I do, so I should doublecheck my progress in more or less the same way.

    This doublechecking process eats away at the time advantage of having Cursor just blast out a whole plugin, but is still way faster than my usual process. This is in part because it remembers all the function definition and syntax details that I routinely forget. It’s massively more capable than the sort of autocomplete you get with something like Github’s Copilot.

    Even in this process, it fouled up the CSS again. But it did lots of other things–using details that are very specific to WordPress block development–and had no difficulty with it.

    I asked it to add an attribute to the block, one called “messageText”:

    Note, though, that it decided to freelance on the “name” attribute. I did not tell it to create a pz domain and there were lots of other blocks in context that do it the way I wanted it, so conceivably it might have figured it out.

    Anyway, adding the attribute worked just fine, but in typical computer fashion, there were plenty of obvious related tasks that I had to specifically ask for.

    Initially, the suggested changes didn’t include importing the TextControl component, so it didn’t work and this is one of those problems that doesn’t really throw any errors, it just quietly refuses to register the new block.

    I asked it to fix this and it replied with the cheery bullshit one sometimes gets from LLMs:

    And then the line was there. But you have to wonder…

    Anyway, when I really got down to brass tacks and was explicitly asking it to make changes in chunks that I could quickly doublecheck, the process went quickly. It would have gone even quicker if I’d reset the chat and started with fresh context.

    More reports from the coding trenches to come…

  • World’s Simplest CRM

    Here’s a use case for PeakZebra that I think makes all the sense in the world.

    We begin with the thought that most of us actually don’t need lead scoring automation.

    Just the facts

    You do need to be able to store information about your prospects. And if they they become customers, you need to make a note of this, either within the same database or by transferring them to some other system. And you need to track their payments and so on.

    For a lot of companies, there’s not actually all that much distance between prospect and customer. For instance, someone contacts you to ask for details on the professional service you offer. You talk with them, gather some info. Maybe you make them a written proposal. They accept the proposal or they don’t, but even if they don’t they aren’t, should some future opportunity lead to a new proposal, exactly a run-of-the-mill prospect anymore.

    Whether a potential customer or an actual customer, you need their contact information and you need to keep a record of your interactions with them. If they send you an email with a pre-sales question, you want to keep a copy. If you give them a Zoom tour of the product, you want to have a record of when and what they thought about it.

    You need to be able to track any deliverables you’ve promised and you need to be able to set reminders for future follow up.

    If you’re really on your game, you can also be learning about them as they interact with your web site and you can build up a segmentation model for your customers (and your prospects, which is arguably almost more important). That work happens on your customer-facing websites, of course, but PeakZebra supplies some great tools for that.

    Not Rocket Science

    The key point here, though, is that none of this is actually complicated. Salesforce will do the job, but you’re paying for the whole lot more that it can also do. Like lead scoring. Even at the small business entry level version, this is $25 per user per month. The next step up is $100 per user per month.

    The PeakZebra approach is dead simple. There’s a “Person” grid that shows contacts. If you want, you can easily have different grids for prospects and clients. If you click “Add a New Person”, you get a form for that.

    Out of the box, you can associate each contact with a particular internal team member. You can tag each person with whatever tags you like (you create them), and you can make a note of each interaction you have with them over time.

    Simple, then customized

    And that’s it. Unless you need something else. In some cases, you can simply add on things you want, if that’s your preference. But you can also queue a request for the change. We generally deliver them within two business days and at a very low cost compared to hiring a conventional developer to jump in cold.

    You can learn more about the pricing model for this sort of change by signing up for our occasional email updates.

  • Naming the Business Model

    First and foremost, PeakZebra is a SaaS offering. It’s the familiar online model, where you pay monthly, except maybe I’ll do what it seems like everyone else is doing these days and force you to pay for a year (which, even if I wind up doing it, I hate).

    But there’s a second layer, where you can, if you choose, pay for individual tweaks to the applications you’re using (well, tweaks right up, theoretically at least, to wholesale creation of full-blown new apps). The model here is what’s been called “productized services,” which is an odd name but one that does manage to convey a sense of the value proposition, which is that you pay a controlled and fixed amount for your use of what otherwise might have been charged on a project or hourly basis.

    Agency?

    The “thing” you get from PeakZebra is a customizable website, so another way of thinking about this is to call it an agency. But it’s really not an agency in the traditional sense where you show up and request a customer-facing site that represents and explains your business to the internet.

    Why do I care what to call the business model? I only care because I want a quick way to communicate what’s on offer. “Internal tool agency” doesn’t do it for a few reasons, I’d say.

    Oh, and it seems obligatory that I convey the idea that AI is involved, because that seems to be the only thing that anyone is paying attention to in new products and services these days. I noticed just now that Airtable’s H1 is now “Digital Operations for the AI Era.” To be honest, this makes me instantly just the tiniest bit suspicious. And to be fair, it’s not like I even know what it is that they’re using AI for.

    AI Magic Broker?

    The irony is that I do absolutely plan to be using AI behind the scenes (as per several blogposts by now), but I’m actually more inclined to be relatively quiet about it. It’ll make things fasters for developers handling requests, but the key point is that there’s an actual developer involved.

    So maybe I focus on it being a SaaS for ad-hoc apps, but a subscription that makes it seamless and easy to mold the app to your needs. If you have a real management job already, you might very well be not even the least bit interested in spending time learning how to do this and that in Airtable or Notion (or pick some other preferred magic no-code SaaS).

    Maybe “guided low-code automation at a fixed subscription price.” OK, that’s too long, so maybe “subscription app refinement.”

    Low-code Whatever

    Actually, I hate that one. I still have some noodling to do on this, I guess. I think getting it right will give me greater clarity when I try to tell people what the “thing” is that should grab their attention about PeakZebra. Sure, it’s a way to pay for the world’s least-featured CRM, but there’s a little more to it than that.

  • Is the WordPress Plugin Economy Played Out?

    People have made some very impressive business successes by offering non-free plugins and plugins that are extensions to other plugins that are big enough to have extensions of their own (WooCommerce being the primary example). But I think it’s increasingly unlikely that this is a good way to launch a successful business.

    I’m thinking primarily of bootstrapped businesses, but I also suspect that bootstrapped startups may be all there is in the WordPress space, at least for a while, because Matt Mullenweg’s takeover of Advanced Custom Fields (and his subsequent takeover of parts of the premium version of ACF) has, I’d imagined, significantly cooled the enthusiasm of venture capital investors for premium plugins.

    In the bootstrap world, there’s probably less reason to fear that some Matt out there will steal your goodies, but that doesn’t mean someone else couldn’t. The ACF heist was a poignant reminder that you really don’t own your own shit in the world of open source (which, come on, was the whole point of open source in the first place, so don’t act shocked).

    As I write this, there’s more of a serious movement afoot to revise WordPress governance so that it’s more of a community driven project (instead of a one-guy project that has a huge community). But who knows how that will play out.

    SaaS as a solution

    As I’ve mentioned elsewhere, one approach that protects whatever secret sauce you bring to the meal is keeping the sauce on the server side of a SaaS offering. For a lot of plugin builders, the trick will be simply not to package the plugin as a plugin.

    But I think there’s another issue, and I even think it’s one of the inherent strengths of open source as a concept: over time, free versions of things get better and better. You may have a feature-laden free version and a whole bunch of great goodies to hold in reserve for the premium version, but over time you’ve going to see other developers make competing products that offer some of your premium features for free.

    Freemium limps along

    I don’t mean to say this kills the freemium model, only that the tendency is toward making the premium side of the equation less valuable over time. Given that pricing in the WordPress ecosystem is arguably too low, this seems likely to make would-be entrepreneurs not exactly thrilled to jump in with both feet.

    This is too bad, because there’s nothing quite like the WordPress plugin ecosystem (or the theme system, for that matter) out there in the software world, and it’s offered a way for a lot of smaller businesses to get a foothold and make a go of it.

    But I think this shrinking premium benefit phenomenon is inherent in current-day WordPress and open source. There’s less and less incentive to spend a year rolling out a paid plugin. If you win, you don’t necessarily win much, your potential winnings aren’t secure, and competition from free alternatives is going to slowly erode your niche.

    Will the wolf survive?

    So do people like me stick with WordPress? Well, if we’re specifically looking at my case, then yes. I stay in WordPress, trying to leverage opportunities that take advantage of the platform’s enormous market share without getting bitten by the current growing risk to WordPress business.

    (I will say I’m tempted to just switch up and go full time on whatever Joost and company wind up concocting–but that too will be WordPress in some form or fashion.)

    I’ve got to believe that when a framework on the Internet has 40% market share, there’s still lots of opportunity to supply things people need, even if it’s a smooth, easily understood offramp from WordPress.

    All that said, though, I’m tempted to answer the question I started with–is the plugin business played out?–by conceding that, yes, it may well be.

  • The Current Site Versioning Plan

    I was thinking about my current plan for keeping track of lots of slightly variant iterations of a baseline WordPress site blueprint, and it hit me. Duh. Disk space doesn’t cost much. You can rack up dozens of terabytes of storage without maxing out your credit card.

    Indeed, this is almost the definition of what a hosting service does. Thousands (tens of thousands) of sites saved on a lot of cheap disk space.

    So yeah, just make a full copy of everything. (Actually, there are incremental backups for WordPress these days and I wonder how effective they are or aren’t. Must explore.)

    A million full images!

    So every time I change one of the changed sites, I just make another copy of it on disk?

    Well, no. Not that simple. For one thing, if you make hundreds of individual changes to thousands of sites, even your cheap terabytes are going to whimper and demand higher pay. Copying all that stuff around is going to give you performance fits, too. As per the above, though, if incremental backups shave off enough of the bulk of each backup, maybe this works.

    Still, I want to be able to answer questions like: what’s the difference between customer X’s current version of the blueprint and customer Y’s? So I need some kind of artefact that I can run a diff against.

    Starting positions

    So imagine we start with a blueprint site. What’s in it or how it got to that state doesn’t matter, but it’s a known starting point and we won’t do anything to any site using this blueprint without tracking the changes.

    There are perhaps five categories of things we might do to a given iteration of a blueprint site:

    1. Add or delete a third-party plugin
    2. Add, delete, activate or deactivate a third-party theme
    3. Change configuration settings
    4. Change code for plugins or themes we’ve created
    5. Make changes to posts (of any type), create new posts, delete existing posts

    However it is that we wind up tracking these things, we’d also like to manage questions around whether the changes we’re making on a given day will remain unique to this client or should be incorporated back into a future version of the blueprint.

    Let’s start, though, with how to do the tracking in the first place.

    The first three items on our list are things that WordPress folks often do manually. But they can also be done programmatically from, say, a plugin. And they can also be carried out by WP-CLI (and you can write your own WP-CLI commands, so there’s a lot of potential power here). So you can imagine batching commands into a bash file and version-controlling that file.

    Keeping track

    The problem with this is that the way developers are used to working with version control is that they make a bunch of changes to some code and then commit it to the repo. You can’t manually add a plugin and then commit that to the repo. You’d have to figure out what you were trying to do, add the needed ingredients, establish that it worked, and then edit a command file so that it made those changes.

    Then you’d have to restore your test site back to where you initially started, run your automated update, and then test again to make sure you didn’t leave anything out of the instructions.

    This sounds like a wonderful discipline for making sure you really have accounted for every change you’ve made, but this is going to be twice the time and work. Not ideal.

    Since I control the environment overall (the blueprints and how they are used), one tactic for plugins and themes is simply to have a list of all of them that might be in use. An easily-produced tool can then simply take a look at the site and make a plugins.json file (or something like that) that has all the installed plugins, whether they are activated or not, and potentially all of their configuration information that’s not stored in standard WordPress database tables (we thought we might be able to leave that part to VersionPress, about which more in two secs, but no)

    Changes to our own code we know how to track using git and related tools, though we’ll also need to track the resulting distributable versions (zip files) of these plugins. (There may, actually, be a staging environment where the full development environment is installed for each client. In fact, I’m fairly sure we’ll need to do that, as I sit here thinking about it.)

    Alas, VersionPress

    Changes to the database (where the layout in posts, current changes to FSE themes, and so on) I had hoped I could at least attempt to track with VersionPress. My limited understanding of VersionPress was that it would work for standard WordPress database changes and plugins that behave nicely within the WordPress universe (by which I primarily mean that they only write to existing WordPress tables).

    If you go back and look at VersionPress information online, you can see that it was basically the magic bullet. There’s even a slight possibility that it still could be. But when I started poking around the Github repo for the project, I learned the sad truth that VersionPress’s creators stopped work on the project back in 2020. It’s still fully available via the repo, but there’s no support other than digging in yourself.

    This is too bad, because it looks like it did precisely what I wanted. A potential saving grace is that it might already do, in unfinished form, what I need. But I don’t want to take a big detour learning my way through the VersionPress codebase, so it either works on install for the changes I’m trying to track, or we’re looking for another approach. (I mean, who’s really got time to fork a complicated project?)

    Since virtually every PeakZebra plugin writes to tables that are outside the standard WordPress tables, changes there wouldn’t be tracked by VersionPress without some (probably fairly major) alterations to the VP code. But I’m pretty sure that’s OK. What’s being written is mostly end-client data (prospect names and addresses, for example) and we’re absolutely not trying to track the customer’s data (and, just in case you’re worried, won’t be using it on development copies–we will, though, be tracking changes to add edge cases and such to a pair of fake customer and other data we’re using for testing).

    I think this could work, but a huge number of changes have occurred in WordPress since work stopped on VersionPress.

    If VersionPress isn’t the answer, tracking database changes becomes a harder task. If variations from the blueprint code that we provide are stored in a separate client.php file that registers callbacks to fire at the appropriate moment, we’ll get lots of mileage from just tracking the dev source for the plugins and then the client.php file for each client. But hopefully we can do the more complete version I’ve outlined here. I’ll report back, and in the meanwhile I need to go install VersionPress and see what it can (still) do.

  • Structuring Code for Cursor and Other AI Tools

    I think this is a post that’s going to evolve over time. That’s partly because I don’t yet–as I’m starting this–understand what I’m talking about. But the thinking here is that while most of the talk of the tubes has been about whether AI can code the way we do, I think the biggest present-day potential win is to find a way to code (and organize code) in a way that makes it easy for AI to digest and do the right thing.

    Part of it is establishing the right context. The Cursor IDE gives you more control over context of any aided-coding tool I’ve seen and I still have a lot of experimentation to do to see how far that can be pushed.

    Give AI a Chance

    But I also think it’s important to think about making requests for code in a way that an AI is likely to get right. I suspect that the basic rule there is going to be keeping things enclosed in very simple functions.

    The other thing I’m thinking about is how best to create tests for these functions. Of course, I’d like the AI to write the tests at the same time that it writes the functions themselves.

    So I asked Cursor to create a function for me. I wasn’t thinking too clearly when I asked, because I wound up asking for something that there’s (duh) already a native core WordPress function for. That said, I convinced myself I’d actually been really clever about because now part of the test was whether the AI would recognize that the native code just needed to be wrapped in the function.

    The prompt:

    write a function for a WordPress plugin that returns true if the current user can manage users and false if not.

    As an aside, I learned that when you use control-K to bring up the prompting box, you can use arrow keys to move up and down through your prompt history (which it gives you a hint for, so I’m not exactly telling you something you couldn’t have found out on your own). I’m pretty sure this will prove useful in life.

    Anyway, the code:

    function pz_can_manage_users() {
        return current_user_can('manage_options');
    }

    Honestly, I think it’s pretty impressive that AI picked up that my request described an already-existent function. We shouldn’t overlook, however, that it is testing the wrong capability. The manage_options capability shows whether you are allowed to make changes to settings in the Settings submenus.

    So that’s a problem. So then, for kicks, I asked:

    now write a function that tests whether pz_can_manage_users works properly.

    And the resulting code was:

    function test_pz_can_manage_users() {
        // Test with admin user
        $admin_id = get_user_by('role', 'administrator')->ID;
        wp_set_current_user($admin_id);
        if (!pz_can_manage_users()) {
            error_log('Failed: Admin user should be able to manage users');
            return false;
        }
    
        // Test with subscriber user
        $subscriber = wp_create_user('test_subscriber', 'password', 'test@example.com');
        if (is_wp_error($subscriber)) {
            error_log('Failed: Could not create test subscriber');
            return false;
        }
        wp_set_current_user($subscriber);
        if (pz_can_manage_users()) {
            error_log('Failed: Subscriber should not be able to manage users');
            wp_delete_user($subscriber);
            return false;
        }
    
        // Cleanup
        wp_delete_user($subscriber);
        return true;
    }
    
    

    Given that the original function is testing for the wrong capability, but a capability that admins have and subscribers don’t, the test function will pass pz_can_manage_users() with flying colors, even though it’s totally not correct.

    I prompted Cursor to fix the first function and it did so correctly. I note also that it did correctly adopt the ‘pz’ prefix I use for functions that aren’t cordoned off within class definitions.

    I prompted:

    in the test function, please add a check for the case where a subscriber has been granted the capabilities to add and delete users.

    And this was added to the test function:

      // Test subscriber with elevated permissions
        $subscriber_user = new WP_User($subscriber);
        $subscriber_user->add_cap('create_users');
        if (!pz_can_manage_users()) {
            error_log('Failed: Subscriber with create_users capability should be able to manage users');
            wp_delete_user($subscriber);
            return false;
        }

    No doubt you’ve noticed that this isn’t explicitly checking for all the capabilities involved in managing users, but I think it’s probably OK to use create_users as a proxy for the full set.

    So, mixed results, but lots of typing saved and, since I’m always forgetting my core system calls, it’s nice that Cursor is capable of figuring out when one can be used and suggesting it with the right syntax (if, in this case, not the right capability to test).

    Constrained requests

    Overall, I think where I want to land with PeakZebra’s code is having pretty much every AI-addressable change request handled by way of creating a callback function and having the right amount of context that the AI can understand what the callback function needs to do (and can do) given the way the codebase is designed. Experiments to come…


  • A Real-World Example of Avoiding Unneeded Prelaunch Dev

    You hear a lot of blather about what the “minimal” in Minimal Viable Product means. And YCombinator has made an industry out of reminding people to launch before they think they’re ready. The trick of course is figuring out where this does and doesn’t apply without kicking the fledgling from the nest while the poor chick is still featherless.

    So I’m preparing to launch PeakZebra in a matter of a handful of weeks, have a list of things that have to get done before that launch can happen, and realized just a couple days ago that the list contained a whole subsection (requiring substantial work) that I can cut without impacting the product release at all.

    Extra work for fun and profit

    I want to share some of the specifics of this discovery because, for all the MVP blather, there aren’t that many useful examples of actually making decisions around just how much P is needed in an MVP. This is also a case where what I’m eliminating doesn’t really have much to do with the product (at least as it’s released).

    Like any SaaS, I need a self-service signup process for new customers. In the particular case of PeakZebra, the way I want to handle that is by having a WordPress site dedicated to the process of signups, billing, and account management. The product itself resides in multiple instances of WordPress with the necessary functionality preloaded into them.

    I spent perhaps half a day thinking about what I’d need on the signup site, debating using an existing third-party plugin (a SaaS subscriber is pretty much the same thing as a “member”, so plugins that handle paid memberships come to mind). I spent a couple hours wondering what should be on the front page of this site.

    Admin can wait

    But then, enlightenment. I don’t need any of this to launch. For early access, I’ll have a form on PeakZebra.com (and yes, this will be a PeakZebra form) and I’ll enter the necessary data in Stripe by hand.

    I’ll need the signup site later, particularly as clients start wanting to manage their account information. I’m kicking a can, but it’s clear to me that none of the work on this should happen before the service itself is feature complete and out in the world with a few customers.

    If this were going to be a high-volume, consumer-facing business, I couldn’t have made this particular choice.

    And by way of counterexample, I’m not foregoing simple onboarding help out past launch. I think “ease of entry” is absolutely crucial to the PeakZebra experience. I’ll keep it to the bare minimum, but the bare minimum is somewhere above the zero mark.

    For every decision to tackle something prelaunch, I need religiously to ask myself: “Why do I need to do this before I have customers?”