Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: development

  • Interlude: Gutenberg Moves Fast

    Interlude: Gutenberg Moves Fast

    I’m taking a pause on my plugin posts to talk about Gutenberg.

    I really love Gutenberg. I’m not kidding! I find it far more enjoyable to write (stories) in plain apps (I used Apple Pages because it syncs between laptop and iPad, yes, I am often using my iPad to write my novel, yes, I will let the world know when it’s done). But when I write for the web, it’s a more visual medium, and Gutenberg is fantastic to represent what I’m writing as it will be properly seen by all!

    But.

    Gutenberg moves fast. Hella fast. So fast it can leave you in the dust, and it has a critical flaw that I feel has been stifling it’s growth and usage among developers.

    JS isn’t your Momma’s PHP

    This is obvious. Javascript ain’t PHP. PHP is a simple language that can be coerced into doing complex things if you understand basic algebra. Surprise! Everyone who considers themselves good at PHP? You’ve mastered the concepts of algebra! Alegrba is one of the easier ‘complex’ mathematic concepts to wrap your head areoud. You get “if a + b = c and a = 10 and c = 11 then b = 1” and you win!

    Javascript though, it’s a little more like calculus and trig, in that you have to understand the formulas a little deeper, and they have that thing where not just numbers and letters appear, but weird symbols.

    [nb: Like all analogies, this falls apart at scale, don’t read too much into it.]

    For the thousands of developers who whet their teeth on PHP, jumping into JS feels like you’re a first-year high schooler in senior maths! It’s scary, it’s complicated, and worst of all … it isn’t actually documented at the micro level, because it’s generally compiled.

    Micro vs Macro / Interpreted vs Compiled

    The macro scale is, more or less, the big picture of what your code is supposed to do. Micro would be each individual element. For PHP, you can clearly identify both the macro (the overall function) and the micro (each teeny process in that function). This is less so for JS because the languages are different.

    There are two primary types of code languages. PHP is what we call an interpreted language, because while the PHP binary is a compiled app, what you write is interpreted by the compiler. Basic JS (like jQuery) is also an interpreted language!

    Compiled languages need a “build” step – they need to be manually compiled first. And if that suddenly made you think “Wait, Gutenberg is JS but I have to build it!” then you have spotted the quirk! The JS we use in Gutenberg is actually JSX!

    JSX was designed for React (which is what we use to build in Gutenberg) and while it may contain some plain Javascript, it’s impossible to use the code without React. That’s why we have the build process, it takes the JSX, compiles it into JS, and saves it to a file.

    The Compilation Downfall

    This is where it gets messy … messier.

    When there’s an error in PHP, we get the error message either on the page or in our logs, depending on how we set up our environment. I personally pipe things to debug.log and just keep that file up as I bash on things. Those errors tend to be incredibly helpful!

    $mastodon not defined on /path/to/file.php:123

    In that example, I know “Ooops, I’m calling the variable $mastodon on line 123 of file.php and forgot to declare it!” Either I need an isset() check or (in this case) I brain farted and copied a line but forgot to rename the variable so I was setting $tumblr twice. Mea culpa, pop in, edit, save, done.

    On the other hand, I was testing out some blocks and modernizing them a little when suddenly … the block didn’t load. I got the WP notice of the block had an error. You’ve probably seen this if you’re a dev:

    Example of an error which says "This block has encountered an error and cannot be previewed"

    or this:

    An error: This block contains unexpected or invalid content.

    And if you’re like me, you used foul language and wondered ‘well… now what.’

    Enter the Console

    Unlike PHP, the errors don’t go to a nice debug.log file, it goes to your in-browser console. This is because, again, PHP is being directly interpreted on the server, and the server happily converts the PHP to HTML and Bob’s your uncle.

    JS (and JSX in this case) aren’t processed by the server. They’re processed on the fly in the browser. If you’ve ever wondered why too much JS, or bad JS, cause your browser to hang, that’s why. We moved the processing from the server (PHP) to the browser. On top of that, it’s also why JS content isn’t really cachable by traditional methods! But that’s another story.

    In this case, I got the first error (cannot be previewed) and being somewhat savvy with the world of Gutes, I popped open the console and saw this gem:

    wp.blockEditor.RichText value prop as children type is deprecated

    The rest of the message was warning me that the thingy would be removed in WP 6.3, and it had a link to ‘help’ resolve it. Spoilers? It didn’t. But take a deep breath. Let’s debug.

    Debugging Gutenberg

    The first issue was that the error came on a page with multiple blocks. I happened to be using a custom plugin I wrote that contains about 6 blocks, you see, so I opened a new page on localhost and added each block, one at a time, until I determined the issue was my incredibly simple spoiler block.

    How simple is this block? It’s basically a custom formatted paragraph, so everyone could use the same design without having to remember the exact colours. I could have made it a ‘reusable block’ on the site but, at the time, I wanted the practice.

    Next I went to that link, which was for “Introducing Attributes and Editable Fields“. I admit, I was a little confused, since I was already using attributes and editable fields! But I did the logical thing and searched that page for the word ‘children.’ My thought process was that if something was being deprecated, it would have a warning right?

    Gif from Blazing Saddles, where Dom DeLuise is a director and walks up to an actor who made a mistake. He uses his bullhorn to scream WRONG! at the man, and bops him in the head with the bullhorn.

    Okay, maybe I was looking in the wrong place. This error is specific to RichText so I clicked on the link to read the RichText Reference and again, looked for “children.” Nothing. Zip. Nada. I followed the link for the more in-depth details on GitHub and still nothing.

    At this point, I ranted on Mastodon because I was chapped off. I also popped open the Gutenberg Deprecations page, and looked for “children” but all I could find was a message to use children!

    RichText explicit element format removed. Please use the compatible children format instead.

    Logically there should be a note that “children is deprecated, please use…” but there is not.

    Now, here is where I accidentally stumbled on a fix, but after I made my fix is when I found the Github issue about this!

    If you are still using “children” or “node” sources in the block attribute definition, like so:

    content: {
     	type: 'array',
     	source: 'children',
     	selector: 'p',
     }
    

    Then change it to use the “html” source instead to get rid of the deprecation warning:

    content: {
     	type: 'string',
     	source: 'html',
     	selector: 'p',
     }
    

    And in fact, that was the correct fix.

    Here’s the Flaw

    None of that was properly documented.

    The link to ‘help’ fix the error didn’t mention the specific error, it talked about attributes at the MACRO level. I was (obviously) already using attributes, else I wouldn’t have had that error at all.

    There is no proper documentation that could help someone fix the issue on their own UNLESS they happened to be trawling through all the issues on GitHub.

    As I put it to my buddy, the reasons developers are salty about Gutenberg are:

    1. It changes pretty much every release
    2. There’s no real way to tell people if it impacts you so you have to check every release and read the console logs, which is not what devs are used to
    3. The JS console won’t tell you (I don’t know if it can) what file caused the warning, so finding it is a crap shoot
    4. The documentation is high level, which is not helpful when you get micro level errors

    Okay, can we fix it?

    At this point, if you’ve made a WordPress Block for Gutenberg, make sure you test every single release with the JS console open. If you don’t do this, you will have a rude awakening until things are made a little better.

    How can things be made better? It will have to begin with a culture shift. Traditionally WordPress has used a “release and iterate” model. With Gutenberg, we’ve become “move fast and break things,” but that is only sustainable if everything broken can be documented for a fix.

    That means I see only one way to correct this, and it’s to slow down Gutenberg enough that deprecations AND THEIR CORRECTIONS are properly documented, and the error messages link to a page about deprecations.

    We need to not link to the general “here’s how attributes work” page, but instead to a specific page that lists those deprecations along side the WordPress versions impacted.

    Another matter is we should be posting in the Field Guide about these things. Currently the 6.3 field guide links to all the various pages where you can find information, but that means you have to click and open each one and hopefully find your exact usage. In my case, those links to the ten Gutenberg versions being ported to core never mention the issue I had.

    If we don’t start slowing down and paving the road for developers, we will begin haemorrhaging the very people who make WordPress a success.

  • Monitored Automation

    Monitored Automation

    One of the things I touched on in my talk at WordCamp NYC in September was the fact that automation has it’s flaws. The problems we face with automated systems flagging LGBTQ videos as ‘restricted’ or trending horrific topics or promulgating fake news is all because of automation. We did this to ourselves.

    Computers Share Our Biases

    Humans have biases, which color the ways in which we develop code. If we feel no one should be able to use a lower case P in WordPress (because that’s it’s name) then we can use our biases to programatically force that. Right or wrong, we are biased in many other ways.

    The bias of robots is limited to what we’ve taught the robot. There’s no such thing as a true AI yet. Yes, I know European Parliament has declared robots to be ‘electronic persons’ and therefore responsible for their actions. In the case of automated reviews, the robot lacks the ability to detect nuance. It lacks ethics and morals. It cannot make a judgment of what is worth more than something else.

    The flip side to this is that humans do what we’re told to do too. Sort of. If I tell you to download a file, review the code for security and sanity, but not make a moral judgement on it’s use, you will and you won’t. Oh sure, you understand it’s not your job, or responsibility, so you’ll try not to. We don’t ask the TSA to make a value judgement on what’s in our luggage, we ask them to determine if it’s allowed or not.

    Biases Drive Design

    Computers, even the most advanced, make decisions based on how we program them. If we tell them “The word Nazi is bad” then anything with that term that is submitted will be rejected. Even a tool by, say, the Holocaust Museum, talking about how their skill provides trivia about the Nazis’ rise to power.

    The reason a computer has bias is because we, the people who program the computers, have bias. We are capable of discussing the paradox of tolerance and, in many cases, of coming to an agreement as to what should and should not be permitted. It’s easy to say that guns are illegal on airplanes. It’s not easy to say that being mean will get you kicked off a plane, because that’s subjective.

    Back to the TSA. If you brought a pair of fuzzy handcuffs in your carry on, which is legal as of the time I wrote this, you would be permitted to do so, but someone would comment. You may even end up having a very public conversation about your private life. And yes, that TSA agent is totally making a value judgement about you.

    Who Watches the WatchMon?

    The answer, the solution to these problems, is difficult. On the one hand, a computer won’t judge you for the fuzzy handcuffs. On the other, it may also decide you’re a criminal for having them in your luggage. A human would understand the purpose of a skill that talks about Nazis not being hate speech, but they also may judge you for having some Nazi swag in your bag for your demonstration class.

    Curiously the solution comes with a blend of automation and humanity. There are some things that should be auto-rejected. If you have a rule based on a clear technical limitation, then a computer should be relied on to process those. Except in practice, this is not the case. Instead, we need a human to check, approve or not, and move on to the next possibility. That’s right, the solution to the automation problem is human monitoring.

    Human Bias Isn’t Solveable

    We can’t stop people from being biased.

    We can meet regularly to discuss the situation, but something needs to go over all the approvals and rejections to see what people are actually doing. So then we have automation monitor the human in reverse. A computer monitors and makes its calls “It looks like X is trending.” A human checks the trends and, if they notice something abnormal (like Betty White trending on a Tuesday afternoon), then can check if she’s done something.

    If a human manually removes Betty White from trending every time is shows up, a computer can flag that for their supervisor to ask why the hate for The Golden Girls. But this means someone has to sit down and talk about removing one’s personal biases from work, and I promise you, it’s harder than it looks.

    Automate But Verify

    The ultimate answer? If you’re not monitoring and verifying your automation, you’re doing it wrong. Much like we say ‘test your backups,’ you have to test everything you task a computer to automagically do.

  • Context is Everything

    Context is Everything

    In the uptick of automated scans, we come to the place where we realize it’s not just the quality of content that matters in our success, but the context.

    Context in Content

    When you write content, the body of your work depends on the literary context of the words. Writing about technology on a non-tech site requires you to step back and explain the tech in a little more detail than you normally might. For example, if I were to post about shortcodes here, I would not bother to give you the history of what they were or why they’re used. I would trust you to know those things, or be ready and able to research them.

    By contrast, when writing about code used on a journalism site, and explaining we had a nifty new shortcode to do a thing, I absolutely would take time to explain. I would not expect my readers there, who care about the goings on of television, to understand about the weirdness of a shortcode. At the same time, I may not need to delve into details quite so much. I could just say “We have a new, faster way to add whatever, which will make it easier for us to report on X.”

    In short, I consider the audience when I write the content. I write contextually.

    Context in Code

    When it comes to writing code, there is a similar mindset. The code should make sense contextually and be consistent. If you’re using underscores for filenames, always use underscores, just to give one example. But this goes further than having a same prefix or formatting (tabs or spaces, eh?). It also means that when data is processed, it should be done so contextually.

    If you have a form, and you allow people to enter data to send to you, and that data is saved to a database, you have to sanitize the data. That’s a no-brainer for every developer worth the time of day. Never save unsanitized data, and sanitize as early as possible to minimize the possible damage. But deciding how best to sanitize can be tricky. PHP comes with stripslashes() for example, however consider that PHP says this:

    An example use of stripslashes() is when the PHP directive magic_quotes_gpc is on (it was on by default before PHP 5.4), and you aren’t inserting this data into a place (such as a database) that requires escaping. For example, if you’re simply outputting data straight from an HTML form.

    In other words, you shouldn’t use that to save data. Thankfully in WordPress (and Drupal and everything else) there are many ways to sanitize your inputted data based on … you guessed it, context. You don’t sanitize a URL as a plain text field, and you don’t sanitize an HTML form as a filename.

    When you write your code, sanitize, validate, and escape it contextually based on what it is.

    The Bottom Line: Context Matters

    This is the thing that automated checkers can’t quite do. They don’t know what the input is supposed to be unless you tell them, so they can’t verify your sanitization as well as a human can. Even grammar checkers can’t tell you when it’s okay to use slang and when it’s not, when you’re trying to explain a new concept.

    In the end? We need humans.

  • Gutenberg Categories

    Gutenberg Categories

    No, this isn’t something snazzy about doing cool things with categories within Gutenberg (although you could). This is about making a section for your own blocks. A category.

    What’s a Category?

    I’m incredibly lazy, so when I use blocks, I type /head and make sure I get the right block, hit enter, and keep on typing.

    The slash command at work!

    But. Sometimes I think I need a specific block and I don’t know the name, so I click on the circle + sign and I get a list of categories!

    Block categories

    You won’t see reusable if you didn’t make some, but the point is that you could make a custom category for your custom blocks.

    Show me the code!

    Glad you asked! You’re gonna love this one.

    add_filter( 'block_categories', function( $categories, $post ) {
    	return array_merge(
    		$categories,
    		array(
    			array(
    				'slug'  => 'halfelf',
    				'title' => 'Half-Elf',
    			),
    		)
    	);
    }, 10, 2 );

    Yep. That’s it! It’s an array, so you can add more and more and more as you want. Don’t go too wild, though, or you’ll make it too long.

  • A Name is Not A Description

    A Name is Not A Description

    One day, you found a app or plugin or add-on for something. It was a feature you always wanted, did exactly what you needed, was well written and supported. It was that panacea of perfection. You loved it. Then you had a computer crash, or a house fire, or moved, and you forgot what the name was. All you could remember was the name was something about what it did. So you decided to Google for it, and quickly found a billion things that fit the bill.

    SEO vs Generic

    When you’re naming your product or company, you work very hard to think of a name that encapsulates what you are, what you do, and what makes you unique. For example, you don’t name yourself “Shoe Company” and expect people to be able to find you. With very few exceptions (and really only No Name comes to mind), if you want to stand out, you pick a good name where you are prominent.

    This directly relates to SEO, and people’s ability to find you. Ever used Apple Pages or Sheets and tried to Google something? Like “How do I make Pages Templates” perhaps. You often feel damn lucky when you get the right result immediately:

    A google search that has useful results!

    But you’re not Apple, are you? So if you named your product “Foods,” you’d probably have a devil of a time getting ranked so people could find you in search!

    Unique vs Memorable

    Take a look at WordPress. Pretend you’re looking for a slider plugin. Hush, just come with me here. Now. You remember a really cool slider plugin, but all you remember is it was named something like “Best Slider Plugin.” Yeah. You ain’t gonna find it. Probably ever. But what if you were looking for a lightbox plugin, and you remembered the name as “Foobox Lightbox” … Hang on a second. That’s one you’re going to be able to find. It has a unique name, but better than that, it has a memorable name!

    The only reason Apple Pages actually works is that Apple is huge and also the fact that most of us Google “Apple Pages whatever” and not just “Pages.” It’s the same with the Apple Watch. It’s nice they call it “Watch.” We call it the “iWatch” because we have to be able to find it, and they picked stupid generic names. Being Apple, they can get away with it.

    To their credit, the name is memorable. It’s not unique, but you will remember it. Even if you remember it as “That stupid Pages app Apple made.” You remember Microsoft Word, but you also will remember WordPerfect, and possibly WordStar. But if you listed four Twitter apps, could you remember what differentiates each one without looking? Definitely unique names, like Tweetbot and Twitterific, and certainly memorable, but in the wrong way.

    Names vs Descriptions

    Many people make a common mistake. They remember the tools they use on their computers, like “TextEdit” and “Notepad” and they think that in order to be found, the name must be short and descriptive. That’s why we get Notepad++ and iTerm. To an extent, this works. LastPass and OnePassword are going to be memorable and unique and descriptive names. But the longer a product, or suite exists, the more likely they are to corner a market and make it harder for the little people.

    Let’s go back to WordPress. You’ve made a great popup plugin and you want everyone to know it. There are roughly 500 plugins that use ‘popup’ or ‘popups’ as a tag. There are 2500 or so plugins that show up for a search on ‘popup’ in the directory. Besides the fact that you really should use the ‘popup’ tag in your plugin, there’s no way in the world you’re going to get your new popup plugin to the top of the list in a day.

    But … users don’t look for ‘popup’ or even ‘best popup plugin.’ They look for something else. “WordPress popup plugin with call to action on page exit.” They may simply that to “wordpress popup plugin call to action page exit” but they’re going to look for what they need. And they’re going to remember the plugin named “Wait Don’t Go! Popups” that has a nice plugin description of “Grab your visitors’ attention one more time before they leave your page forever.”

    Humans vs Robots

    Putting a million buzzwords in your product’s name, the description, and the URL aren’t ever going to make you popular. The only thing that does is bring people in the yard. If they see your website is fill with upsell and hyperbole, they’re going to walk right out again. If they see features and explanations and proof that you are, indeed, the bees knees, they’ll stay. If you have a catchy or unique name, they’ll remember and recommend you to their friends.

    And then, then you will be a success.

  • Stacked Charts Part 1: Understanding Your Data

    Stacked Charts Part 1: Understanding Your Data

    There are a few different type of charts. Actually there are a lot. I find a nice bar chart fairly easy to read and understand. So when Tracy said we should generate some nice stats about nations, like how many shows there were per nation, I was able to do that pretty easily:

    An excerpt of shows by nation - USA has the most. Yaaaay.
    An excerpt of shows by nation

    And as far as that goes, it’s pretty cool. It’s really just the same code I use to generate category statistics already. This is, by the way, why using WordPress to generate your data is useful. It’s easy to replicate code you’ve already got.

    But then Tracy, who I think derives some perverse joy out of doing this to me, says “Can we find out how many trans characters there are per nation?”

    Use WordPress First

    If you heard my talks about Sara Lance, you’ve heard me tout that data based sites should always use WordPress functions first. By which I mean they should use taxonomies and custom post types when possible, because accessing the data will be consistent, regular, and repeatable.

    Ironically, it’s because I chose to use WordPress than I was in a bit of a bind.

    You see, we have three post types on the site right now: shows, characters, and actors. The shows have the taxonomy of ‘nation’ so getting that simple data was straightforward. The characters store the taxonomies of gender identity and sexual preference. That sounds pretty logical, right?

    So how, you may wonder, do we get a list of characters on a show? A query. Basically we search wp_post_meta for all characters with the array of lezchars_show_group and, within that multidimensional, have a show of the post ID of the show saved. Which means the characters are dynamically generated every single time a page is loaded. And yes, that is why I use The L Word as my benchmark for page speed.

    However by doing all this dynamically, generating the stats for characters per nation would look like this:

    1. Use get_terms to get a list of all shows in a nation to …
    2. Loop through all those shows and …
    3. Loop through all the characters on each show to extract the data to …
    4. Store the data per nation

    Ouch. Talk about slow.

    Solution? Use WordPress!

    Thankfully there was a workaround. One of the other odd things we do with shows is generate a show ‘score’ – a value calculated by the shows relative awesomeness, our subjective enjoyment of it, and the number of characters, alive or dead, it has.

    In order to make that generation run faster, every time a show or character is saved, I trigger the following post_meta values to be saved:

    • lezshows_characters – An array of character counts alive and dead
    • lezshows_the_score – The insane math of the score

    So I added three more:

    • lezshows_sexuality
    • lezshows_gender
    • lezshows_romantic

    All of those are generated when the post is saved, as it loops through all the characters and extracts data.

    Generate The Base

    In order to get the basics, we start by generating an array of everything we’re going to care about. I do this by listing all the taxonomies I want to use and then loop through them, adding each slug to a new array with a value of 0:

    $valid_taxes = array( 
    	'gender'    => 'lez_gender',
    	'sexuality' => 'lez_sexuality',
    	'romantic'  => 'lez_romantic',
    );
    $tax_data = array();
    
    foreach ( $valid_taxes as $title => $taxonomy ) {
    	$terms = get_terms( $taxonomy );
    	if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
    		$tax_data[ $title ] = array();
    		foreach ( $terms as $term ) {
    			$tax_data[ $title ][ $term->slug ] = 0;
    		}
    	}
    }
    

    That gives me a multidimensional array which, I admit, is pretty epic and huge. But it lets move on to step two, of getting all the characters:

    $count          = wp_count_posts( 'post_type_characters' )->publish;
    $charactersloop = new WP_Query( array(
    	'post_type'              => 'post_type_characters',
    	'post_status'            => array( 'publish' ),
    	'orderby'                => 'title',
    	'order'                  => 'ASC',
    	'posts_per_page'         => $count,
    	'no_found_rows'          => true,
    	'meta_query'             => array( array(
    		'key'     => 'lezchars_show_group',
    		'value'   => $post_id,
    		'compare' => 'LIKE',
    	),),
    ) );
    

    Next I stop everything as a new array. Which is where we get into some serious fun. See, I have to actually double check the character is in the show, since the ‘like’ search has a few quirks when you’re searching arrays. The tl;dr explanation here is that if I look for shows with a post ID of “23” then I get “23” and “123” and “223” and so on.

    Yeah. It’s about as fun as you’d think. If I wasn’t doing arrays, this would be easier, but I have Sara Lance to worry about.

    if ($charactersloop->have_posts() ) {
    	while ( $charactersloop->have_posts() ) {
    		$charactersloop->the_post();
    		$char_id     = get_the_ID();
    		$shows_array = get_post_meta( $char_id, 'lezchars_show_group', true );
    
    		if ( $shows_array !== '' && get_post_status ( $char_id ) == 'publish' ) {
    			foreach( $shows_array as $char_show ) {
    				if ( $char_show['show'] == $post_id ) {
    					foreach ( $valid_taxes as $title => $taxonomy ) {
    						$this_term = get_the_terms( $char_id, $taxonomy, true );
    						if ( $this_term && ! is_wp_error( $this_term ) ) {
    							foreach( $this_term as $term ) {
    								$tax_data[ $title ][ $term->slug ]++;
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    	wp_reset_query();
    }
    

    You’ll notice there’s a quick $tax_data[ $title ][ $term->slug ]++; in there to increment the count. That’s the magic that gets processed all over. It tells me things like “this show has 7 cisgender characters” which is the first half of everything I wanted.

    Because in the end I save this as an array for the show:

    foreach ( $valid_taxes as $title => $taxonomy ) { 
    	update_post_meta( $post_id, 'lezshows_char_' . $title , $tax_data[ $title ] );
    }
    

    How Well Does This Run?

    It’s okay. It’s not super awesome, since it has to loop so many times, this can get pretty chunky. See The L Word and it’s 60+ characters. However. It only updates when the show is saved, or a character is added to the show, which means the expensive process is limited. And by saving this data in an easily retrievable format, I’m able to do the next phase. Generate the stats.