Tuesday, April 8, 2025

if you see something (build finished) say something

 Sometimes our build process take a minute, so I had Co-Pilot help build me a script to use the mac's text to speech to announce when it looks like we were ready to go:

#!/bin/bash

# Check if at least one argument is provided
if [ $# -lt 1 ]; then
  echo "Usage: $0 <nx_command> [args...]"
  exit 1
fi

# Build the nx command from the provided arguments
nx_command="$@"
log_file="nx_command.log"

# Run the command in the background and capture its PID
$nx_command &> $log_file &
pid=$!

# Monitor the log file for the specific line
tail -f $log_file | while read line; do
  echo "$line"
  if [[ "$line" == *"All remotes started"* ]]; then
    say "ready"
    break
  fi
done

# Wait for the nx command to finish
wait $pid


Tuesday, April 1, 2025

print design in browser via PHP

 It's funny in how so many ways I can use a little coding mojo and not have to fiddle with Word/Google Doc, Excel/Sheets - or Wordpress for that matter, but that's a longer story.

But yeah, helping my sweetie run together some tiny bits of campaign material via PHP:

<!DOCTYPE html>
<html>
<head>
  <title>Lynette Table</title>
  <style>
  body { font-family: sans-serif; }
    table { border-collapse: collapse;  font-size:1.5em;}
    td { border: 0px solid black; padding: 10px; text-align: center;
    
    padding:24px;
    
    }
    
    .bigger{
        font-size:1.4em;   
    }
  </style>
</head>
<body>

<table>
<?php
for ($row = 0; $row < 5; $row++) {
    echo "<tr>";
    for ($col = 0; $col < 3; $col++) {
                echo <<<HTML
<td>

Vote this Sat April 5!<br>
<b class="bigger">Lynette Martyn</b> <br>
Town Meeting Member, Precinct 12<br>
A progressive voice for your neighborhood<br>
<b>www.LynetteMartyn.com</b>


</td>
HTML;
    }
    echo "</tr>";
}
?>
</table>

</body>
</html>

 slapped on this webpage gave me



and that was a lot easier to iterate on and tweak than if I had to copy and paste each time...

Monday, March 31, 2025

on not upgrading

Tim Boudreau:

About the whole DOGE-will-rewrite Social Security's COBOL code in some new language thing, since this is a subject I have a whole lot of expertise in, a few anecdotes and thoughts.

Some time in the early 2000s I was doing some work with the real-time Java team at Sun, and there was a huge defense contractor with a peculiar query: Could we document how much memory an instance of every object type in the JDK uses? And could we guarantee that that number would never change, and definitely never grow, in any future Java version?

I remember discussing this with a few colleagues in a pub after work, and talking it through, and we all arrived at the conclusion that the only appropriate answer to this question as "Hell no." and that it was actually kind of idiotic.

Say you've written the code, in Java 5 or whatever, that launches nuclear missiles. You've tested it thoroughly, it's been reviewed six ways to Sunday because you do that with code like this (or you really, really, really should). It launches missiles and it works.

A new version of Java comes out. Do you upgrade? No, of course you don't upgrade. It works. Upgrading buys you nothing but risk. Why on earth would you? Because you could blow up the world 10 milliseconds sooner after someone pushes the button?

It launches fucking missiles. Of COURSE you don't do that.

There is zero reason to ever do that, and to anyone managing such a project who's a grownup, that's obvious. You don't fuck with things that work just to be one of the cool kids. Especially not when the thing that works is life-or-death (well, in this case, just death).

Another case: In the mid 2000s I trained some developers at Boeing. They had all this Fortran materials analysis code from the 70s - really fussy stuff, so you could do calculations like, if you have a sheet of composite material that is 2mm of this grade of aluminum bonded to that variety of fiberglass with this type of resin, and you drill a 1/2" hole in it, what is the effect on the strength of that airplane wing part when this amount of torque is applied at this angle. Really fussy, hard-to-do but when-it's-right-it's-right-forever stuff.

They were taking a very sane, smart approach to it: Leave the Fortran code as-is - it works, don't fuck with it - just build a nice, friendly graphical UI in Java on top of it that *calls* the code as-is.

We are used to broken software. The public has been trained to expect low quality as a fact of life - and the industry is rife with "agile" methodologies *designed* to churn out crappy software, because crappy guarantees a permanent ongoing revenue stream. It's an article of faith that everything is buggy (and if it isn't, we've got a process or two to sell you that will make it that way).

It's ironic. Every other form of engineering involves moving parts and things that wear and decay and break. Software has no moving parts. Done well, it should need *vastly* less maintenance than your car or the bridges it drives on. Software can actually be *finished* - it is heresy to say it, but given a well-defined problem, it is possible to actually *solve* it and move on, and not need to babysit or revisit it. In fact, most of our modern technological world is possible because of such solved problems. But we're trained to ignore that.

Yeah, COBOL is really long-in-the-tooth, and few people on earth want to code in it. But they have a working system with decades invested in addressing bugs and corner-cases.

Rewriting stuff - especially things that are life-and-death - in a fit of pique, or because of an emotional reaction to the technology used, or because you want to use the toys all the cool kids use - is idiotic. It's immaturity on display to the world.

Doing it with AI that's going to read COBOL code and churn something out in another language - so now you have code no human has read, written and understands - is simply insane. And the best software translators plus AI out there, is going to get things wrong - grievously wrong. And the odds of anyone figuring out what or where before it leads to disaster are low, never mind tracing that back to the original code and figuring out what that was supposed to do.

They probably should find their way off COBOL simply because people who know it and want to endure using it are hard to find and expensive. But you do that gradually, walling off parts of the system that work already and calling them from your language-du-jour, not building any new parts of the system in COBOL, and when you do need to make a change in one of those walled off sections, you migrate just that part.

We're basically talking about something like replacing the engine of a plane while it's flying. Now, do you do that a part-at-a-time with the ability to put back any piece where the new version fails? Or does it sound like a fine idea to vaporize the existing engine and beam in an object which a next-word-prediction software *says* is a contraption that does all the things the old engine did, and hope you don't crash?

The people involved in this have ZERO technical judgement.

Now Social Security isn't as life and death as nuclear missiles, and the challenge in getting people who can make any changes with COBOL is real. But I have zero confidence the DOGE bros are the ones to get it anywhere near right.

Friday, March 21, 2025

AgGrid note

 Just a quick note, AgGrid is pretty serious about its breaking changes in 33. Like if it detects the old ag-grid.css, it aggressively unstyles itself and you can't use the new theming.

"If I press these keys in the right order, I can do anything!"

 At work, Corey G mentioned how sometimes he'll do a demo for a client and make sure EVERYTHING is a single file (like include Base64'ig images if need be) That vibe reminded me of how I like "everything in one file" for p5.js, and how it harkens back to the old days of computer magazine type-in programs, and this comic.



windows taskbars through the ages

I switched to Mac about a decade ago. For a while I thought Windows' "one task bar item per window" was smarter than Mac's "one Dock item per application". Now I'm not sure (and I noticed Windows has drifted to Mac's approach) but I do know I tend to have too many windows open.


Wednesday, March 19, 2025

Sunday, March 16, 2025

The Palm PDA Circa 2000

Around 2000, my friend Tom Much wrote an article about Day in the Life of the Palm User. Amazing how useful PDAs could be, even before they were online. (Smartphones really are the spiritual successor of these things.)


 

Wednesday, March 5, 2025

ai yi yi

The tech interview process has been pretty broken for a while. One student made a program to take on the leet code style interviews

Obviously this student's prediction is sobering:

Maybe it’s stupid of me to say this. Most human intelligence work is going to be obsolete in two years. [...] by the time I graduate, these LLMs are going to get advanced enough to the point where there is no significant intellectual work that I could produce value for society.”

I mean, more of the work will be wrangling AI, for sure, and maybe I'm just using AI wrong (or their nerfing the LLMs to save power) but over time I've become less impressed with LLMs abilities to do stuff big picture.



Monday, February 24, 2025

dragging and dropping abandoning jQuery UI and getting a handle on things

 (Sorry, title is a bad joke; basically I'm saying I'm tired of jQuery being dragged, and I'm not dropping it)

One of the centerpieces of my porchfest empire has been "the Hourtron", a drag and drop tool for putting bands on porches. I posted about its first prototype in 2015. Since then I'd rewritten it a few times.

I remembered a failed rewrite in 2019... I think one big mistake was avoiding resue of tragically unhip jQueryUI, which seems to have a particularly good drag and drop facility - I didn't search THAT hard for a lighter replacement this go 'round, but the ones I did try didn't have a simple "snap" facility.

Also I'm not sure if in 2019 if I was doing Declarative drag and drop "properly", but here I have the server endpoint (in PHP) return the entire state of band, porches, and the gigs linking them, and I remove the whole grid and build it up again in Vanilla JS every time a band is dropped (avoiding "batch processing" was THE major impetus for the rewrite; some porchfests now have porches scheduling their own bands, and i didn't want to risk overwriting that information. The other big win was the "untimed" column where you could show a band was associated with a porch (often at the band's request) while holding off on figuring just when the band would play.

Honestly I love the conciseness of old school jQuery in messing with the DOM; $(".someclass").css() or $("id") is so much more concise than the vanilla equivalents, making `map()` like behavior over all matching elements effortless, years before JS had .map() built in

and the drag and drop code was similarly concise, here are some bits and pieces for the flavor:

Setting up each band:

$band.draggable({
    snap:".target",
    snapMode: "inner",
    snapTolerance: 10,
    cursor:"move",
    start: function() {
        $(this).addClass('dragging');
    },
    stop: function() {
        $(this).removeClass('dragging');
    }
});

Making the drop targets:

$(".timeblock").droppable({hoverClass:'over',drop:bandDropOnTime});
$(".untimedblock").droppable({hoverClass:'over',drop:bandDropOnUntimed});
$(".unplacedblock").droppable({hoverClass:'over',drop:bandDropOnUnplaced});

And then getting the drop and the drag (using the jQuery paradigm of stuff things as ".data()" on the element.

function bandDropOnTime(event,ui){  
    const $timeblock = $(this);
    const porchid = $timeblock.data("porchid");
    const hour = $timeblock.data("hour");
    const minute = $timeblock.data("minute");
 
    const $bandblock = ui.draggable;
    const band = $bandblock.data("band");
    const bandid = $bandblock.data("bandid");

    saveGigData(bandid,porchid,hour,minute,band.performancelength,band.actname);
}


One gotcha with that was not using fat arrow notation; jQuery relies on a sense of "this" (via "$(this)") when attaching an event handler - but "this" has fallen from grace (and rightfully so, it was pretty confusing) and fat arrow tends to blow away - so oldschool anonymous "function()"

Another gotcha I had forgotten about: the draggable bands were much wider than the target time slots, and dropping would often not put the band where it looked like it had been let go. So the actual "draggable" part of the band element is the size of one time slot, and then it has a child that's as wide it needs to be to indicate the desired performance length. That seemed to be the simplest workaround for making it clear where the user was trying to drop the band (as you can see in the screen shot I use an extra shot of "red" for the start time- I started that as a diagnostic placeholder but actually highlighting the start time is a reasonable UX thing to do.)

tracking the field

 Software engineering job openings hit five-year low?

It's been a grind for so many developers. A thoughtful article about some of the possible causes of dot bomb 2.0.

Thursday, February 13, 2025

not the one from 2001

Sometimes I get a little concerned about the explosion in complexity around UI and software architecture in general. Interesting hot take from Martin Fowler here, that many successful miroservice architectures start up as a monolith, and then break parts out as the needs become more apparent.

Sunday, February 2, 2025

snip snip

6 CSS Snippets Every Front-End Developer Should Know In 2025. All good stuff, but sometimes I can't shake the feeling that animations and transitions were easier in JQuery - admittedly CSS is much more declertive but things like transitions can seem a lot more intuitive to write as a verb of script - stuff that happens - rather than a noun of styling, stuff that is.

Thursday, January 30, 2025

npm note

 At my new job we use veracode to scan for vulnerabilities, including npm packages that are children of packages we use.

those seem a little tricky... I think the options are:
1. tell veracode to ignore it IF its dev dependency/internal tool only
2. upgrade the parent package(s) and hope it pulls a more recent version
3. use { "overrides" : "foo":"1.0.0" } (new since 2021 NPM v8.3.0)

everything is a little fraught but that last one seems the most promising to me.

(via Stack Overflow What is the role of the package-lock.json? )

Tuesday, January 21, 2025

surveying eslint-disables across a code base

An architect was mentioning some counts of eslint-disables in our code base, and I wanted to take a closer look.

(I know different devs have different intuitions about the criticality of some of these rules: I know for me, if there's a trade off between "concise, more readable code that keeps attention to what's actually new" vs "well-enforced lint standards and rigorously enforced strong typing at every level that MIGHT help turn a runtime error into a buildtime problem", I lean towards the former.)

I started by recursive grepping for those lines, and putting them into a .txt file so future steps would be faster:

grep -r "eslint-disable" | grep -v node_modules/   >  ~/raw.txt

I then used CoPilot to come up with a node.js script to break up the lines and do counts by subproject in the monorepo:)

const fs = require('fs');
const readline = require('readline');

// Create a read stream for raw.txt
const fileStream = fs.createReadStream('raw.txt');

// Create an interface to read the file line by line
const rl = readline.createInterface({
  input: fileStream,
  crlfDelay: Infinity
});

// Initialize the counts and totalCounts objects
const counts = {};
const totalCounts = {};

// Process each line
rl.on('line', (line) => {
  // Split the line on whitespace
  const sections = line.split(/\s+/);

  const regex = /\.\/([^\/]+\/[^\/]+)\//;
  const match = sections[0].match(regex);

  if (!match) {
    console.log(`NO MATCH FOR ${line}\n`);
    return;
  }

  const appOrLib = match[1];

  const offset = sections.findIndex(section => section.startsWith('eslint-disable'));

  if (offset === -1) {
    console.log(`NO MATCH FOR ${line}\n`);
  } else {
    // Join the remaining parts on " ", ignoring "*/"
    const remainingParts = sections.slice(offset + 1).filter(part => part !== '*/');
    const joinedParts = remainingParts.join(' ');

    // Split each remaining part on "," and treat them separately
    const finalParts = joinedParts.split(',').map(part => part.trim());

    // Initialize the appOrLib object if it doesn't exist
    if (!counts[appOrLib]) {
      counts[appOrLib] = {};
      totalCounts[appOrLib] = 0;
    }

    finalParts.forEach(part => {
      const key = part || '[EMPTY]';
      if (!counts[appOrLib][key]) {
        counts[appOrLib][key] = 0;
      }
      counts[appOrLib][key]++;
      totalCounts[appOrLib]++;
    });
  }
});

// Print the counts object when done
rl.on('close', () => {
  for (const appOrLib in counts) {
    console.log(`${appOrLib}: ${totalCounts[appOrLib]}`);
    const sortedKeys = Object.keys(counts[appOrLib]).sort((a, b) => counts[appOrLib][b] - counts[appOrLib][a]);
    sortedKeys.forEach(key => {
      console.log(`\t${key}: ${counts[appOrLib][key]}`);
    });
  }
});

Thursday, January 2, 2025

thumbs way up

When you enjoy thinking about UI/UX, it's fun and instructive to see evolutions in products you use every day, and think about what their designers were trying to accomplish, and what you might think they could do better.

So in FB Messenger, they recently changed the "send message" button - I believe that previously it was inactive until you typed some message content. Now it's a "thumbs up" button when the message body is empty, which sends a big thumbs up (btw, isn't that a rude gesture in some cultures? I was just googling to see if FB uses anything different elsewhere...)

So I noticed this on accident, when I see I had sent a big thumbs up between 2 of my own messages... (I quote "The Tao of Programming": "A program should follow the 'Law of Least Astonishment'. What is this law? It is simply that the program should always respond to the user in the way that astonishes him least.")

So, on the one hand, I could see that an easy general "OK" response is potentially useful, and a little easier to get to here than the usual "reaction" picker for previous messages. On the other hand... it seems weird to just make that the default message - especially to ones own messages. When you accidentally send one against your own message, it seems weirdly insecure or self-aggrandizing.

I wonder if they considered changing the "just send a thumbs up" function to activate only after the other person has written something back? That seems like a more useful scenario. But I guess would make the UI even more chaotic and unpredictable.