Category: AI

  • More Cursor Programming in WordPress

    I just spent about thirty minutes creating a couple of functions. I can’t really say I wrote them; it was almost entirely done by AI. I think it’s a nice example of ways in which Cursor and AI can really shine, so I thought I’d spend a couple minutes walking through how it went.

    PeakZebra (the system, not the company) has a SQL table that stores data about each of the other SQL tables that PeakZebra creates and uses. The format (for better or worse, and I suspect it’s probably worse) is that the string you’d hand off to the dbDelta function is stored in its entirety.

    The table of tables table

    While initially building PeakZebra, this made it easy for me to quickly change the fields in any of the (now rather numerous) tables I use. I’d just jump into a PHP file where each of these strings was added to the pz_table_str table. An example looks like this;

    // interaction table
    $table_name = $wpdb->prefix . "pz_interaction";
    
    // interaction table
    $item = array();
    $item['id'] = null;
    $item['table_name'] = 'pz_interaction';
    $item['field_string'] = "CREATE TABLE $table_name (
      id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      per_id bigint(20) NOT NULL DEFAULT 1,
      summary varchar(255) NOT NULL DEFAULT '',
      details varchar(800) NOT NULL DEFAULT '',
      created varchar(12) NOT NULL DEFAULT '',
      PRIMARY KEY  (id)
    ) $charset;";
    
    handle_def_record($item);
    dbDelta($item['field_string']);

    the handle_def_record($item) call writes the string to the definition table. Then dbDelta actually creates the file.

    If you’re not familiar with dbDelta, it’s pretty handy. It creates the file you’ve described if it doesn’t exist. But if it does exist, it examines it and adds or deletes fields as needed based on comparing the existing file to your CREATE TABLE string.

    I wanted to add a way for users to add fields to tables (and perhaps to create new tables), so I wanted to grab an existing string from the table with the defining strings in it, parse it into the individual fields, and then present a form where new fields could be added (deciding to worry about deleting fields later).

    Toward that end, I opened the PHP file of a “table” block I’d recently created and gave Cursor this prompt:

    let's add a function called read_table_def that reads the record with the table_name field equal to 'pz_person' and returns the field_string value for that row. 

    Cursor gave me this:

    // ... existing code ...
    
    function read_table_def() {
        global $wpdb;
        $table_name = 'pz_person';
        
        $result = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT field_string FROM {$wpdb->prefix}pz_table_def WHERE table_name = %s",
                $table_name
            )
        );
        
        return $result;
    }

    Some things of interest here:

    1. Although it could confer that I was working within WordPress from the file I’d opened, I didn’t explicitly say so.
    2. It figured out and knew enough to know that it would need the $wpdb global.
    3. It wisely uses $wpdb->prepare(), something I’ll admit I’m often to lazy to bother with.

    Now, the code it supplied was correct, but I’d screwed up and told it the wrong file name, insofar as I’d forgotten the WordPress database prefix.

    Debugging and debugging the debugging

    As a result, I asked it to add a bunch of debugging code, which it did, leading me to realize, within ten minutes or so, that I’d screwed up.

    At that point, I asked it to add the prefix, and it did:

    function read_table_def($table_name) {
        global $wpdb;
        
        // Debug: Print the actual query
        $query = $wpdb->prepare(
            "SELECT field_string FROM wp_pz_table_def WHERE table_name = %s",
            $table_name
        );
        echo "Query: " . $query . "<br>";
        
        // Debug: Print the table contents
        $all_records = $wpdb->get_results("SELECT * FROM wp_pz_table_def");
        echo "All records: <pre>";
        var_dump($all_records);
        echo "</pre><br>";
        
        $result = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT field_string FROM wp_pz_table_def WHERE table_name = %s",
                $table_name
            )
        );
        
        // Debug: Print the result
        echo "Result: ";
        var_dump($result);
        echo "<br>";
        
        return $result;
    }

    Of course, I didn’t want the prefix hardcoded. An easy fix. I asked Cursor to remove the debug code, which it did. Then I moved on to a function that would take the string and parse it into an array of individual field definitions.

    I’ll skip over the details, but once again, I’m fascinated by the things it “figures out” on its own, for example, telling me that it’s making the assumption that the fields are comma delimited, but it’s an easy fix if it’s not right about that.

    It’s strangely like working with an actual mental process. It’s probably terrible news for junior programmers, if for no other reason that it pretty much never screws up things like the parameters and formats of arcane system function calls.

  • 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…

  • 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.

  • 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…


  • AI Coding and Context

    It’s clear that the way to get code “as if you’d coded it yourself” is to include your coding in the context that your LLM is using. Cursor, which I’ve been experimenting with of late, gives you several layers of potential customized context, and I finally found a few minutes to explore how this works.

    In case you’re wondering, Cursor is a new kid on the block and the documentation is correspondingly minimal. Note, though, that you can simply ask the AI for an explanation…

    A screen clip where the AI is prompted about setting context and replies that it can't "directly help with Cursor's functionality"

    Oops…actually it doesn’t include Cursor itself in its context. I’m not actually knocking this — it makes sense from a context management point of view.

    how can it not know what it is?

    Anyway, the basic idea is that there are some @keyword directives that allow you to include things in the context of the current chat (and, of course, different chats can and will use entirely different contexts.

    So if you type @ in your chat window, you get a list of options that includes @folders, where it suggests the current working directory, but you can put anything you like in there, folder-wise. Interestingly, it will tell you if it doesn’t think you’ve picked useful files for context:

    Looking at the code, I don't see any files that would be relevant to the user meta functionality we were discussing. The files shown are mostly block-related files from the pzdata plugin, containing JSON configurations, CSS styles, and asset dependencies.
    
    To properly help with the user meta issue, we'd need to see:
    
    The file where get_user_help_preference() is defined

    The world inside an AI-powered IDE is a weird place, though. Consider, for example, that it was Cursor’s AI (channeling claude-3.5-sonnet) that wrote get_user_help_preference() in the first place.

    For my own purposes, I want to be able to say, listen, when asked to add this or that capability or configuration to the program in question, please solve it using this algorithm, inside a function named according to this pattern, and invoking this function from this kind of callback.

    Straight out of the box it seems pretty darned good at figuring out that it should follow your naming conventions (though I suspect this relies on your having been disciplined about following them yourself), but what about making it expect to be on the business end of a callback?

    Intriguingly, there’s an @ directive for @docs. As is the case absolutely everywhere in the documentation, the explanation of what it does is pretty sparse: you can point it at the documentation for…whatever you like. They are assuming regular framework or library documentation, things like the docs for NextJS, but presumably you could try adding something like a coding standards document or rules like “make these changes by hooking the ‘init’ event and making this a callback function.”

    I thought there might be a deeper dive into this in Cursor (the company)’s blog, but things are pretty sparse there, as well. That’s not a knock, either–these folks are busy making the donuts. It’s interesting to read their post of a year ago about the problems they hope Cursor will solve. The vast majority of what they’re taking on is, in one way or another, a question of managing context (or to put it another way, managing tokens).

    Just to give this a preliminary once around, I created a file in my current project called instructions.txt:

    If you are asked to create a function called "my_response()", make it a function that writes a short message to console.log.

    No sooner had I saved this than it (because it was in the current working directory, which I’d included in its entirety) was picked up and my edit window was prompting an edit completion for the function header, but I could also use the “generate” option in the editor, where I asked for a function with the magic name:

    a chat box asking for a function called "my response" and offering the complete function as an update.

    It’s a trivial case to be sure, but oh my god does this seem promising.

    (Final note: I don’t think I’ve ever used a meme GIF in my writing before. I don’t know what came over me.)

  • Salute Your Cursor AI Overlords

    I’m not sure yet, but I’m pretty close to sure that AI can’t do anything genuinely complicated when it comes to writing code. Or, more accurately, it’s happy to do immensely complicated code creation, but most of the complex stuff doesn’t work and is more trouble than it’s worth to fix.

    But a recent return to experimentation with ChatGPT impressed me enough that I think, within a narrow context and with requests that mimic other code in the codebase, AI should be able to handle requests like “change the blah field verification routine to add a check of whether blah.count > 5 and if it is, then return fail”.

    My being convinced won’t buy you very much at the programmer store, but I’m nevertheless convinced. What’s more, I think there are huge tracts of PeakZebra that, with some careful refactoring, can be structured so that AI makes reliably accurate changes on request.

    I’m saying ‘AI’ and not ChatGPT or some other LLM model/service because I simply don’t know enough just this moment to say which AI is mostly likely to do what I’m after.

    Cursor buzz

    That said, Cursor is the current candidate. There’s a positive buzz out there on the pipes but, far more important than that, Cursor looks at your codebase as it formulates its answers. My hope is that this will enable it to handle “basically the same thing that we did over here” type requests.

    I’d love it if I could set up some rules for determining which kind of solution is best, but I simply don’t know nearly enough to know what that would look like, if any generally available LLC out there also supports traditional rule-guided expert system elements, and whether the real answer is to build a custom, local machine learning system to do the coding instead (unlikely).

    To take one example of the sort of thing I’d like to be able to do, remembering that I’m doing this in the context of WordPress installations, I’d like to be able to say “add a hook to the blah action, write a callback function for it that follows my function naming conventions, have the function do blah, and then store this function in the client.php file.

    In an ideal world, you’d then ask the AI to write a function that tests the new function and can be added to existing test regimes. No particular reason, with the correct (but perhaps not yet existent) connectivity, it couldn’t then run the test against the procedure to make sure it works as expected.

    To me it sounds a little “out there” when I consider my wishlist as a whole, but then again, aren’t the various pieces of all these functions more or less already out there? Especially for use in a fairly well declared framework like WordPress?

    Cursory beginnings

    So I downloaded Cursor and am giving it a spin to see how much it can do for real world programming before it breaks up on the rocks of code complexity. I’m midway in the free trial period, and things are looking extremely promising with it as a way to let the magic box product a lot of the code-talkin’.

    Downloading Cursor was something I recently threatened to do, so if nothing else I’m leveling up on blog promise follow through. I won’t spend time describing Cursor, because odds are good you’ve already heard something about it. In any case, lots of other great general writeups are already out there.

    WordPress Knowledge?

    My big first question was whether it would be accurate in its understanding (?!) of the WordPress context. So I opened up the plugins directory on a local instance where I’ve been working. I can’t really say how well it has a grasp on the relationship of the several plugins I have in there, but it’s definitely looking at all the code in the directory.

    While context is king in AI, it occurred to me that even on a dev installation, you may well have several third-party plugins installed. Best not to waste context on what could amount to a whole lot of code that has nothing to do with your dev efforts. So, probably better where Cursor is concerned to mash everything into one plugin and open that single plugin’s top folder, rather than the plugins folder itself.

    Better yet, perhaps one can simply move the third-party stuff to the MU (must use) plugins directory. I’ll give that a spin and report back.

    Working in Ignorance

    I’ll confess that I haven’t yet taking the time to really learn what’s going on in the Cursor interface. Even so, I figured out how to open a chat window alongside a file I was working on and have a chat that was aware of the open file(s) I was working on. (This isn’t actually the best way to generate code within existing files and codebases.)

    It seems to work pretty darned well to have a specific thing in mind that can be packaged as a function. So you can prompt things like “write a function that takes the task and the rate, consults a table for the standard number of hours that should be estimated for that task and returns the estimated revenue for the task. It seems generally able to figure out things like “task and rate” will need to be variables. It even seems to put together a lot of things like “rate times hours” is what the answer is. There’s a fair likelihood that it will understand that, absent any further context, it will need to create a dummy table with some sample tasks and hours and it will give you a vaguely pedantic explanation in its response that says all you need to do is replace the dummy values with your real ones.

    For example

    I wanted some help text that appeared on relevant pages and sections in PeakZebra and I wanted each user to decide when the help text boxes could be dismissed and not be shown anymore. Plus I wanted any individual user decide they needed the help again and be able to toggle the help boxes back on.

    I did this in a few discrete steps. First, I asked for a function that would add a flag variable called is_help to each user’s metadata. Then I requested code to add the field to each user’s profile page. Cursor did this with no issues.

    I asked it to create a block for my help text:

    It gave me a two-file setup that looked pretty good but didn’t quite work:

    help-text-block.php

    <?php
    /**
     * Plugin Name: Help Text Block
     * Description: A block that displays text only to users with is_help meta field set to true
     * Version: 1.0.0
     * Author: Your Name
     */
    
    if (!defined('ABSPATH')) {
        exit;
    }
    
    function help_text_block_register() {
        wp_register_script(
            'help-text-block-editor',
            plugins_url('block.js', __FILE__),
            array('wp-blocks', 'wp-element', 'wp-editor')
        );
    
        register_block_type('help-text/conditional-text', array(
            'editor_script' => 'help-text-block-editor',
            'render_callback' => 'help_text_block_render'
        ));
    }
    add_action('init', 'help_text_block_register');
    
    function help_text_block_render($attributes, $content) {
        // Get current user
        $current_user = wp_get_current_user();
        if (!$current_user) {
            return '';
        }
    
        // Check if user has is_help meta set to true
        $is_help = get_user_meta($current_user->ID, 'is_help', true);
        
        // Only show content if is_help is true
        if ($is_help) {
            return $content;
        }
        
        return '';
    }

    and block.js

    (function(blocks, element) {
        var el = element.createElement;
    
        blocks.registerBlockType('help-text/conditional-text', {
            title: 'Help Text',
            icon: 'text',
            category: 'common',
            attributes: {
                content: {
                    type: 'string',
                    source: 'html',
                    selector: 'p',
                }
            },
            
            edit: function(props) {
                return el(
                    wp.blockEditor.RichText,
                    {
                        tagName: 'p',
                        className: props.className,
                        value: props.attributes.content,
                        onChange: function(newContent) {
                            props.setAttributes({ content: newContent });
                        },
                        placeholder: 'Enter help text here...'
                    }
                );
            },
    
            save: function(props) {
                return el(
                    wp.blockEditor.RichText.Content,
                    {
                        tagName: 'p',
                        value: props.attributes.content
                    }
                );
            }
        });
    }(
        window.wp.blocks,
        window.wp.element
    ));

    I’ll use subsequent posts to talk more about what does and doesn’t work with Cursor, but I want to highlight something here and right off the bat: this is arguably the “old school” way of creating a block. These days, you’d expect most of the block to be declared in a separate block.json file. And you’d expect a javascript file that needed to be run through npm start to generate a finished version in a build directory.

    Does it dream of electric sheep?

    And here’s the weird thing about working with something like Cursor (and the various LLM models it uses in turn): it’s possible that this is a savvy choice. After all, it avoids the whole process of processing. It’s ready to go as soon as it turns up in the plugins directory.

    One argument in favor of seeing it as savvy is that it correctly wrote the JavaScript file in vanilla script. That’s why it calls createElement right out of the gate and why it looks weirdly overcomplicated.

    On the other hand, maybe it’s just modeling outdated stuff on the internet. There have been lots of changes in the “typical” block build over the past year and thus there are lots of bits of sample code out there that do things in ways that work but aren’t really what developers are currently doing.

    I was thinking it was possibly picking up older blocks in my code base, because some of them still have earlier techniques in them. But it occurred to me that I’ve never not used webpack in my block building, so presumably not.

    In any case, you can’t tell what it was thinking or where it was getting its thinking from, as far as I’m aware. I think chat context is probably the way to help control where the magic gets its smarts, but I have a lot to learn on that front.

    I’ll discuss actual code quality (as well as having Cursor debug its own code) in the next couple of posts.